import { normalizeText } from '../utils';
import { SPACE } from '../../../utils/keymap';

/**
 * @function isValidTypeaheadEvent
 * @description Tests whether the event is a valid typahead event or not.
 *
 * @since 4.5.0
 *
 * @param {Event}  event The event object.
 * @param {string} chars The chars that have been typed.
 *
 * @return {boolean} Whether the event is a valid typeahead event or not.
 */
const isValidTypeaheadEvent = ( event, chars = '' ) => {
	if ( event.key === SPACE && chars.length ) {
		return true;
	}

	return (
		event.key.length === 1 &&
		! event.ctrlKey &&
		! event.altKey &&
		! event.metaKey &&
		/^[\p{Letter}\p{Number}]$/u.test( event.key )
	);
};

/**
 * @function itemTextStartsWith
 * @description Tests whether the item starts with a text string.
 *
 * @since 4.5.0
 *
 * @param {object} item The item to test.
 * @param {string} text The string to test.
 *
 * @return {boolean} Whether the item starts with text provided or not.
 */
const itemTextStartsWith = ( item = {}, text = '' ) => {
	const itemText = item.label || '';
	if ( ! itemText ) {
		return false;
	}
	return normalizeText( itemText )
		.toLowerCase()
		.startsWith( text.toLowerCase() );
};

/**
 * @function flipItems
 * @description Flips the items so the items after the active item starts first.
 *
 * @since 4.5.0
 *
 * @param {Array}  items      Items to flip.
 * @param {object} activeItem The active item.
 *
 * @return {Array} The flipped items array.
 */
const flipItems = ( items, activeItem ) => {
	const index = items.findIndex( ( item ) => item.id === activeItem.id );
	return [
		...items.slice( index + 1 ),
		...items.slice( 0, index ),
	];
};

/**
 * @function getNextItemStartingWith
 * @description Get the next item that starts with given chars.
 *
 * @since 4.5.0
 *
 * @param {Array}  items      The array of items.
 * @param {string} chars      The typed chars.
 * @param {object} activeItem The active item.
 *
 * @return {object|undefined} The next item, or undefined.
 */
const getNextItemStartingWith = ( items, chars, activeItem ) => {
	const filteredItems = items.filter( ( item ) => itemTextStartsWith( item, chars ) );
	if ( ! filteredItems.length ) {
		return undefined;
	}
	if ( ! activeItem?.id ) {
		return filteredItems[ 0 ];
	}
	if ( ! itemTextStartsWith( activeItem, chars ) ) {
		return filteredItems[ 0 ];
	}
	if ( chars.length > 1 ) {
		return activeItem;
	}
	return flipItems(
		filteredItems,
		activeItem,
	).filter( ( item ) => item.id !== activeItem.id )[ 0 ];
};

/**
 * @function useDropdownTypeahead
 * @description Adds typeahead functionality to props.
 *
 * @since 4.5.0
 *
 * @param {object}   props    The props to add handlers to.
 * @param {Function} useStore The useStore hook.
 *
 * @return {object} The modified props.
 */
const useDropdownTypeahead = ( props = {}, useStore = () => {} ) => {
	const activeItem = useStore( ( state ) => state.activeItem );
	const chars = useStore( ( state ) => state.chars );
	const charTimeoutId = useStore( ( state ) => state.charTimeoutId );
	const listItems = useStore( ( state ) => state.listItems );
	const dropdownOpen = useStore( ( state ) => state.open );
	const setActiveItem = useStore( ( state ) => state.setActiveItem );
	const setSelectedItem = useStore( ( state ) => state.setSelectedItem );
	const setChars = useStore( ( state ) => state.setChars );
	const setCharTimeoutId = useStore( ( state ) => state.setCharTimeoutId );
	const listRef = useStore( ( state ) => state.listRef );
	const triggerRef = useStore( ( state ) => state.triggerRef );
	const baseElRef = useStore( ( state ) => state.baseElRef );
	const { hasSearch = false } = props;

	const clearChars = () => setChars( '' );

	const handleKeyDownCapture = ( event ) => {
		// If has search, return early. If target is not base element, focus base element.
		if ( hasSearch ) {
			if ( event.target !== baseElRef.current ) {
				baseElRef?.current?.focus();
			}
			return;
		}
		// If default prevented from earlier propagation, return early.
		if ( event.defaultPrevented ) {
			return;
		}
		// If not a valid typeahead event, return early.
		if ( ! isValidTypeaheadEvent( event, chars ) ) {
			return clearChars();
		}
		event.preventDefault();

		// Set new timeout for clearing chars.
		clearTimeout( charTimeoutId );
		setCharTimeoutId( setTimeout( clearChars, 500 ) );

		// Add typed char to existing chars, find item starting with chars.
		const char = event.key.toLowerCase();
		let newChars = chars + char;
		let foundItem = getNextItemStartingWith( listItems.flatItems, newChars, activeItem );
		// If new chars is longer than 1 char, set new chars to typed char and find again.
		if ( ! foundItem && newChars.length > 1 ) {
			newChars = char;
			foundItem = getNextItemStartingWith( listItems.flatItems, newChars, activeItem );
		}
		// If item is found, set chars and active item.
		if ( foundItem ) {
			setChars( newChars );
			setActiveItem( foundItem );
			if ( event.target === triggerRef?.current && ! dropdownOpen ) {
				setSelectedItem( foundItem );
			} else {
				listRef?.current?.querySelector( `#${ foundItem.id }` )?.focus();
			}
		} else {
			// Otherwise, clear the chars.
			clearChars();
		}
	};

	return {
		...props,
		handleKeyDownCapture,
	};
};

export default useDropdownTypeahead;
