import { twMerge } from 'tailwind-merge';
import clsx from 'clsx';
import apiFetch from '@wordpress/api-fetch';

export const classNames = ( ...classes ) => twMerge( clsx( classes ) );

export const debounce = ( func, wait, immediate ) => {
	let timeout;
	return ( ...args ) => {
		const later = () => {
			timeout = null;
			if ( ! immediate ) {
				func( ...args );
			}
		};
		const callNow = immediate && ! timeout;
		clearTimeout( timeout );
		timeout = setTimeout( later, wait );
		if ( callNow ) {
			func( ...args );
		}
	};
};

/**
 * Get row number by index value.
 *
 * @param {number} index
 * @return {number} number of row
 */
export const getRowNum = ( index ) => {
	return Math.floor( index / 3 ) + 1;
};

/**
 * Get column number by index value.
 *
 * @param {number} index
 * @return {number} number of column
 */
export const getColumnNum = ( index ) => {
	return ( index % 3 ) + 1;
};

/**
 * Get the loading skeleton type by row and column number.
 *
 * @param {number} row    row number.
 * @param {number} column column number.
 * @return {number} skeleton type number.
 */
export const getLoadingSkeletonType = ( row, column ) => {
	const types = [ 1, 2, 3 ];
	const index = ( row - 1 ) % 3;
	const typeIndex = ( column - 1 + index ) % 3;
	return types[ typeIndex ];
};

/**
 * Set value to session storage by key.
 *
 * @param {string} key
 * @param {*}      value
 */
export const setToSessionStorage = ( key, value ) => {
	const sessionStorageAPI = window.sessionStorage;
	try {
		sessionStorageAPI.setItem( key, JSON.stringify( value ) );
	} catch ( error ) {
		console.error( error );
	}
};

/**
 * Get value from session storage by key.
 *
 * @param {string} key
 * @param {*}      defaultValue
 * @return {*} value
 */
export const getFromSessionStorage = ( key, defaultValue = undefined ) => {
	const sessionStorageAPI =
		typeof sessionStorage !== 'undefined'
			? sessionStorage
			: window.sessionStorage;
	try {
		const value = sessionStorageAPI.getItem( key );
		return !! value ? JSON.parse( value ) : defaultValue;
	} catch ( error ) {
		console.error( error );
		return defaultValue;
	}
};

/**
 * Clear the session storage by key.
 *
 * @param {string} key
 */
export const clearSessionStorage = ( key ) => {
	const sessionStorageAPI =
		typeof sessionStorage !== 'undefined'
			? sessionStorage
			: window.sessionStorage;
	try {
		sessionStorageAPI.removeItem( key );
	} catch ( error ) {
		console.error( error );
	}
};

/**
 * Generate a unique string of specified length.
 *
 * @param {number} length The length of the string to be generated. Default is 8.
 * @return {string} The generated unique string.
 */
const uniqString = ( length = 8 ) => {
	// Define the characters to be used in the string.
	const chars =
		'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
	let result = '';
	// Generate the string by randomly selecting characters from the defined set.
	for ( let i = length; i > 0; --i ) {
		result += chars[ Math.floor( Math.random() * chars.length ) ];
	}
	return result.toLowerCase();
};

/**
 * Recursively manipulate the block ID attribute of an array of blocks.
 *
 * @param {Array} blocks The array of blocks to manipulate.
 */
export const manipulateAttributeBlockId = ( blocks ) => {
	blocks.forEach( ( item ) => {
		if ( item?.attributes ) {
			item.attributes.block_id = uniqString();
		}
		if ( item?.innerBlocks?.length > 0 ) {
			manipulateAttributeBlockId( item.innerBlocks );
		}
	} );

	// Return the manipulated blocks.
	return blocks;
};

/**
 * Adjusts the height of a textarea element based on its content.
 * If the content exceeds a maximum height, the element will be scrollable.
 *
 * @param {HTMLElement} node      - The textarea element to adjust.
 * @param {number}      maxHeight
 */
export const adjustTextAreaHeight = ( node, maxHeight = 400 ) => {
	if ( ! node ) {
		return;
	}
	node.style.height = 'auto';
	if ( node.scrollHeight > maxHeight ) {
		node.style.height = `${ maxHeight }px`;
		node.style.overflowY = 'auto';
	} else {
		node.style.height = `${ node.scrollHeight }px`;
		node.style.overflowY = 'hidden';
	}
};

/**
 * Converts an object's keys from snake_case to camelCase.
 *
 * @param {Object} obj - The object to convert.
 * @return {Object} - A new object with camelCase keys.
 */
export const objSnakeToCamelCase = ( obj ) => {
	if ( ! obj ) {
		return {};
	}

	const newObj = {};
	for ( const [ key, value ] of Object.entries( obj ) ) {
		const camelKey = key.replace( /_([a-z])/g, ( match, p1 ) =>
			p1.toUpperCase()
		);
		newObj[ camelKey ] = value;
	}
	return newObj;
};

/**
 * Formats a number to display in a human-readable format.
 *
 * @param {number} num - The number to format.
 * @return {string} The formatted number.
 */
export const formatNumber = ( num ) => {
	if ( ! num ) {
		return '0';
	}
	const thresholds = [
		{ magnitude: 1e12, suffix: 'T' },
		{ magnitude: 1e9, suffix: 'B' },
		{ magnitude: 1e6, suffix: 'M' },
		{ magnitude: 1e3, suffix: 'K' },
		{ magnitude: 1, suffix: '' },
	];

	const { magnitude, suffix } = thresholds.find(
		( { magnitude: magnitudeValue } ) => num >= magnitudeValue
	);

	const formattedNum = ( num / magnitude ).toFixed( 1 ).replace( /\.0$/, '' );

	return num < 1000
		? num.toString()
		: formattedNum + suffix + ( num % magnitude > 0 ? '+' : '' );
};

/**
 * Get color className based on the percentage.
 *
 * @param {number} percentage - The percentage.
 * @return {string} - The color className.
 */
export const getColorClass = ( percentage ) => {
	const colorClassNames = {
		default: '',
		warning: 'text-credit-warning',
		danger: 'text-credit-danger',
	};

	if ( percentage <= 10 ) {
		return colorClassNames.danger;
	} else if ( percentage <= 20 ) {
		return colorClassNames.warning;
	}
	return colorClassNames.default;
};

export const addHttps = ( url ) => {
	if ( ! /^https?:\/\//i.test( url ) ) {
		url = 'https://' + url;
	}
	return url;
};

export const sendPostMessage = ( data, id ) => {
	const frame = document.getElementById( id );
	if ( ! frame ) {
		return;
	}
	frame.contentWindow.postMessage(
		{
			call: 'zipwpPreviewDispatch',
			value: data,
		},
		'*'
	);
};

function fallbackCopy( text ) {
	const textarea = document.createElement( 'textarea' );
	textarea.value = text;
	textarea.style.position = 'fixed'; // avoid scrolling to bottom
	document.body.appendChild( textarea );
	textarea.focus();
	textarea.select();
	try {
		document.execCommand( 'copy' );
	} catch ( e ) {}
	document.body.removeChild( textarea );
}

export const copyToClipboard = ( text ) => {
	// Copy the text inside the text field
	if ( navigator.clipboard ) {
		navigator.clipboard
			.writeText( text )
			.catch( () => fallbackCopy( text ) );
	} else {
		fallbackCopy( text );
	}
};

export const handleCopyToClipboard = ( event, text ) => {
	copyToClipboard( text );
};

export const setCookie = ( name, value, expiryInSeconds = 60 ) => {
	const date = new Date();
	date.setTime( date.getTime() + expiryInSeconds * 1000 ); // in milliseconds.
	const expires = 'expires=' + date.toUTCString();
	document.cookie = `${ name }=${ value }; ${ expires }; path=/`;
};

export const getCookie = ( name ) => {
	const cookies = document.cookie.split( '; ' );
	for ( const cookie of cookies ) {
		const [ key, value ] = cookie.split( '=' );
		if ( key === name ) {
			return decodeURIComponent( value );
		}
	}
	return null;
};

export const deleteCookie = ( name ) => {
	document.cookie = `${ name }=; Max-Age=0; path=/`;
};

/**
 * Gets the client's country code.
 * First checks if already cached in cookie, otherwise fetches from IP geolocation API.
 * Country code is stored in a cookie for 30 days to avoid repeated API calls.
 *
 * @return {Promise<string>} The country code (e.g., 'RU', 'US') or empty string if detection fails.
 */
export const getClientCountryCode = async () => {
	const cookieName = 'ai_builder_client_country';

	// Check if already cached in cookie.
	const cachedCountry = getCookie( cookieName );
	if ( cachedCountry ) {
		return cachedCountry;
	}

	try {
		// Use ipwho.is API to detect client location.
		const response = await fetch( 'https://ipwho.is/' );
		if ( response.ok ) {
			const data = await response.json();
			const countryCode = data?.country_code || '';

			// Cache for 30 days in cookie (30 days * 24 hours * 60 minutes * 60 seconds).
			if ( countryCode ) {
				setCookie( cookieName, countryCode, 30 * 24 * 60 * 60 );
			}

			return countryCode;
		}
	} catch ( error ) {
		// Silently fail - server will handle location detection as fallback.
		console.error( 'Failed to detect client country:', error );
	}

	return '';
};

export const socialMediaParser = {
	socialMediaPrefix: {
		twitter: 'twitter.com/',
		facebook: 'facebook.com/',
		instagram: 'instagram.com/',
		linkedin: 'linkedin.com/in/',
		youtube: 'youtube.com/',
		google: 'google.com/maps/place',
		yelp: 'yelp.com/biz/',
	},

	patterns: {
		twitter:
			/^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?twitter\.com\/([a-zA-Z0-9_#?&=+]+)\/?$/,
		linkedin:
			/^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?linkedin\.com\/in\/([a-zA-Z0-9-._#?&=+]+)\/?$/,
		facebook:
			/^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?facebook\.com\/([a-zA-Z0-9._@#?&=+]+)\/?$/,
		instagram:
			/^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?instagram\.com\/([a-zA-Z0-9._@?&=]+)\/?$/,
		youtube:
			/^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?youtube\.com\/([a-zA-Z0-9_#?&=+@]+)\/?$/,
		google: /^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?google\.com\/maps\/place\/([a-zA-Z0-9-+_.#?&=+]+)\/?$/,

		yelp: /^(?:http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?yelp\.com\/biz\/([a-zA-Z0-9-_#?&=+]+)\/?$/,
	},

	validate( platform, url ) {
		if ( this.patterns[ platform ] ) {
			return this.patterns[ platform ].test( url );
		}
		return false;
	},

	parse( text ) {
		const matches = {};
		Object.keys( this.patterns ).forEach( ( platform ) => {
			try {
				const match = text.match( this.patterns[ platform ] );

				if ( match && match[ 1 ] ) {
					matches[ platform ] = {
						handle: match[ 1 ],
						prefix: match[ 0 ].replace( match[ 1 ], '' ),
					};
				}
			} catch ( error ) {
				console.log( error );
			}
		} );

		return matches;
	},
};

export const isValidURL = ( url ) => {
	try {
		new URL( url );
		return true; // If the URL constructor doesn't throw an error, the URL is valid
	} catch ( _ ) {
		return false; // Invalid URL
	}
};

export const isValidImageURL = ( fileURL ) => {
	// regex only matches letters, numbers, spaces, dots, underscores, colons, slashes, and hyphens
	const validPattern = /^[a-zA-Z0-9_\-\. :~/]+$/;

	if ( ! isValidURL( fileURL ) ) {
		return false;
	}

	if ( ! validPattern.test( fileURL ) ) {
		return false;
	}

	return true;
};

const { plan_data } = aiBuilderVars?.zip_plans;

export const showAISitesNotice = () => {
	// if only 1 AI Site is remaining
	if ( plan_data?.remaining?.ai_sites_count === 1 ) {
		return true;
	}

	const usagePercentage =
		( plan_data?.usage?.ai_sites_count /
			plan_data?.limit?.ai_sites_count ) *
		100;

	if ( usagePercentage >= 60 ) {
		return true;
	}

	return false;
};

export const getPlanPromoDissmissTime = async () => {
	const data = await apiFetch( {
		path: 'zipwp/v1/get-plan-promo-dismiss-time',
		method: 'GET',
		headers: {
			'X-WP-Nonce': aiBuilderVars.rest_api_nonce,
			'content-type': 'application/json',
		},
	} );

	return data;
};

export const getTimeDiff = ( timeToCompare ) => {
	const timeDiff = Math.floor( Date.now() / 1000 ) - timeToCompare;
	return timeDiff;
};
