import { React } from '@gravityforms/libraries';
import { isObject, slugify } from '@gravityforms/utils';
import Icon from '../../elements/Icon';
import Image from '../../elements/Image';
import Text from '../../elements/Text';

export const NEEDS_I18N_LABEL = 'Needs i18n';

/**
 * @function normalizeText
 * @description Normalizes the provided text and trims leading and trailing spaces.
 *
 * @since 4.5.0
 *
 * @param {string} text The text to normalize.
 *
 * @return {string} The normalized text.
 */
export const normalizeText = ( text = '' ) => {
	return text
		.normalize( 'NFD' )
		.replace( /[\u0300-\u036f]/g, '' )
		.trim();
};

/**
 * @function getId
 * @description Get the id from the prefix and key provided.
 *
 * @since 4.5.0
 *
 * @param {string} prefix The id prefix.
 * @param {string} key    The id key.
 *
 * @return {string} The id.
 */
export const getId = ( prefix, key ) => slugify( `${ prefix }-${ key }` );

/**
 * @function getSearchItem
 * @description Get the search item.
 *
 * @since 4.5.0
 *
 * @param {string} idPrefix The id prefix.
 *
 * @return {object} The search item.
 */
export const getSearchItem = ( idPrefix ) => ( {
	type: 'search',
	id: getId( idPrefix, 'search' ),
} );

/**
 * @function augmentListItems
 * @description Augment list items to add id to the item.
 *
 * @since 4.5.0
 *
 * @param {Array}   listItems      The list items.
 * @param {object}  args           Arguments to augment list items with.
 * @param {boolean} args.hasSearch Whether the dropdown has search or not.
 * @param {string}  args.id        The id prefix.
 *
 * @return {Array} The augmented list items.
 */
export const augmentListItems = ( listItems, { hasSearch = false, id: idPrefix = '' } ) => {
	const newListItems = listItems.map( ( item, index ) => {
		if ( item.type === 'group' ) {
			return {
				...item,
				id: item?.label?.label
					? getId( idPrefix, slugify( `${ item.label?.label || '' }-group` ) )
					: getId( idPrefix, `${ index }-group` ),
				items: [
					...( item?.label ? [ {
						...item.label,
						id: getId( idPrefix, slugify( item.label?.label || '' ) ),
					} ] : [] ),
					...augmentListItems( item.items, { id: idPrefix } ),
				],
			};
		}
		return {
			...item,
			id: getId( idPrefix, item.value ),
		};
	} );

	return [
		...newListItems,
		...( hasSearch ? [ getSearchItem( idPrefix ) ] : [] ),
	];
};

/**
 * @function getFlatItems
 * @description Get the flat items from the items provided.
 *              If the item is a group, it will recursively get the flat items.
 *              If the item is a group label, it will skip it.
 *              If the returnProp is provided, it will return the property from the items.
 *
 * @since 5.5.0
 *
 * @param {Array}            items      The items.
 * @param {string|undefined} returnProp The property to return from the items.
 *
 * @return {Array} The item ids.
 */
export const getFlatItems = ( items, returnProp ) => {
	return items.flatMap( ( item ) => {
		if ( item.type === 'group' ) {
			return getFlatItems( item.items, returnProp );
		}
		if ( item.type === 'groupLabel' ) {
			return [];
		}
		return returnProp ? item[ returnProp ] : item;
	} );
};

/**
 * @function getListItemsState
 * @description Gets the state for list items.
 *
 * @since 4.5.0
 *
 * @param {Array}   listItems      The list items.
 * @param {object}  args           Arguments to augment list items with.
 * @param {boolean} args.hasSearch Whether the dropdown has search or not.
 * @param {string}  args.id        The id prefix.
 *
 * @return {object} The list items state.
 */
export const getListItemsState = ( listItems, args = {} ) => {
	const items = augmentListItems( listItems, args );
	return {
		ids: getFlatItems( items, 'id' ),
		flatItems: getFlatItems( items ),
		items,
	};
};

/**
 * @function filterListItems
 * @description Filters list items based on the search value provided.
 *
 * @since 4.5.0
 *
 * @param {Array}  listItems   The list items.
 * @param {string} searchValue The search value.
 *
 * @return {Array} The filtered list items.
 */
export const filterListItems = ( listItems, searchValue ) => {
	if ( searchValue === '' ) {
		return listItems;
	}

	return listItems
		.map( ( item ) => {
			if ( item.type === 'group' ) {
				// Recursively filter group items.
				const filteredItems = filterListItems( item.items, searchValue );
				if ( filteredItems.length > 0 ) {
					return {
						...item,
						items: filteredItems,
					};
				}
				return null; // Exclude empty groups.
			}

			// Non-group item, check if it matches the search.
			const itemLabel = item.searchValue || item.label || '';
			if ( ! itemLabel ) {
				return null;
			}
			const normalizedLabel = normalizeText( itemLabel ).toLowerCase();
			return normalizedLabel.indexOf( searchValue.toLowerCase() ) !== -1 ? item : null;
		} )
		.filter( Boolean );
};

/**
 * @function getComponent
 * @description Gets the component from the args provided.
 *
 * @since 4.5.0
 *
 * @param {JSX.Element|string|object} args The component args.
 *
 * @return {JSX.Element|string} The component.
 */
export const getComponent = ( args ) => {
	// If args is a string or a React element, return it.
	if ( typeof args === 'string' || React.isValidElement( args ) ) {
		return args;
	}

	let component = null;

	// If args is not an object, return null.
	if ( ! isObject( args ) ) {
		return component;
	}

	switch ( args.component ) {
		case 'Icon':
			component = <Icon { ...args.props } />;
			break;
		case 'Image':
			component = <Image { ...args.props } />;
			break;
		case 'Text':
			component = <Text { ...args.props } />;
			break;
		default:
			component = null;
	}

	return component;
};

/**
 * @function convertSingleToMultiItem
 * @description Converts a single item to a multi item.
 *
 * @since 4.5.0
 *
 * @param {object} singleItem The single item.
 *
 * @return {Array} The multi item.
 */
export const convertSingleToMultiItem = ( singleItem ) => {
	if ( ! isObject( singleItem ) ) {
		// Single item is not object, something is wrong here, return single item.
		return singleItem;
	}
	if ( Object.keys( singleItem ).length === 0 ) {
		// Single item is empty, return empty array.
		return [];
	}
	// Return single item as multi value.
	return [ singleItem ];
};

/**
 * @function convertMultiToSingleItem
 * @description Converts a multi item to a single item.
 *
 * @since 4.5.0
 *
 * @param {Array} multiItem The multi item.
 * @param {Array} listItems The current list items.
 *
 * @return {object} The single item.
 */
export const convertMultiToSingleItem = ( multiItem, listItems ) => {
	if ( ! Array.isArray( multiItem ) ) {
		// multi item is not array, something is wrong here, return multi item.
		return multiItem;
	}
	if ( multiItem.length === 0 ) {
		// Selected item is empty, return first item in list items.
		return listItems[ 0 ] || {};
	}
	// Return first item in multi value.
	return multiItem[ 0 ];
};

/**
 * @function isListItemShape
 * @description Check if the item is in the shape of a list item.
 *
 * @since 5.4.5
 *
 * @param {*} item The item to check.
 *
 * @return {boolean} Whether the item is in the shape of a list item.
 */
export const isListItemShape = ( item ) => {
	return isObject( item ) &&
		Object.prototype.hasOwnProperty.call( item, 'label' ) &&
		Object.prototype.hasOwnProperty.call( item, 'value' );
};

/**
 * @function getSelectedItemFromValue
 * @description Get the selected item from the value provided.
 *
 * @since 5.4.5
 *
 * @param {string|number|Array|object} value     The provided value.
 * @param {Array}                      listItems The list items to get the selected item from, pass in flat list items if grouped.
 * @param {boolean}                    multi     Whether the dropdown is multi or not.
 *
 * @return {object|Array} The selected item.
 */
export const getSelectedItemFromValue = ( value, listItems, multi ) => {
	// If the value is a string
	if ( typeof value === 'string' || typeof value === 'number' ) {
		const item = listItems.find( ( listItem ) => listItem.value === value );
		if ( multi ) {
			return item ? [ item ] : [];
		}
		return item || {};
	}
	// If the value is an array
	if ( Array.isArray( value ) ) {
		if ( multi ) {
			return value.reduce( ( acc, val ) => {
				// If val is a string, find the list item with the value and add it to the accumulator.
				if ( typeof val === 'string' ) {
					return [ ...acc, listItems.find( ( listItem ) => listItem.value === val ) ];
				}
				// If val is an object, add it to the accumulator.
				if ( isListItemShape( val ) ) {
					return [ ...acc, val ];
				}
				// If val is not a string or an object, return the accumulator.
				return acc;
			}, [] );
		}
		return convertMultiToSingleItem( value, listItems );
	}
	// If the value is an object
	if ( isListItemShape( value ) ) {
		if ( multi ) {
			return [ value ];
		}
		return value;
	}
	// If the value is not a string, an array, or an object, return the default value.
	return multi ? [] : {};
};
