import { React } from '@gravityforms/libraries';
import { debounce } from '@gravityforms/utils';

const { useEffect, useRef, useState } = React;

/**
 * @module useAjaxSearch
 * @description A hook to provide an interface for searching for dropdown list items using AJAX.
 *
 * @since 5.6.3
 *
 * @param {object}   args                  The hook arguments.
 * @param {Function} args.fetchListItems   The async function to fetch and return the list items.
 * @param {boolean}  args.ignoreSameValue  Whether to ignore the same search value for repeat requests.
 * @param {Array}    args.initialListItems The initial list items.
 * @param {number}   args.minChars         The minimum number of characters before a search request is called.
 * @param {number}   args.searchDelay      The delay in milliseconds before a search request is called.
 *
 * @return {object} The list items and search change function.
 */
const useAjaxSearch = ( {
	fetchListItems = async () => {},
	ignoreSameValue = true,
	initialListItems = [],
	minChars = 2,
	searchDelay = 500,
} ) => {
	const debounceRef = useRef( null );
	const controllerRef = useRef( null );
	const lastSearchValue = useRef( '' );
	const [ isFetching, setIsFetching ] = useState( false );
	const [ listItems, setListItems ] = useState( initialListItems );

	/**
	 * @function fetchAndSetListItems
	 * @description Fetches and sets the list items.
	 *
	 * @since 5.6.3
	 *
	 * @async
	 *
	 * @param {string} value  The search value.
	 * @param {object} signal The abort signal.
	 *
	 * @return {void}
	 */
	const fetchAndSetListItems = debounce( async ( value, signal ) => {
		try {
			setIsFetching( true );
			const newListItems = await fetchListItems( value, { signal } );
			setIsFetching( false );
			setListItems( newListItems );
			lastSearchValue.current = value;
		} catch ( error ) {
			if ( error.name !== 'AbortError' ) {
				console.error( 'Error fetching list items for dropdown: ', error );
			}
		}
	}, { wait: searchDelay } );

	/**
	 * @function onSearch
	 * @description The search change function.
	 *
	 * @since 5.6.3
	 *
	 * @async
	 *
	 * @param {string} value The search value.
	 *
	 * @return {void}
	 */
	const onSearch = async ( value ) => {
		// Clear the debounce timer.
		if ( debounceRef.current ) {
			clearTimeout( debounceRef.current );
		}
		// Abort the previous request.
		if ( controllerRef.current ) {
			controllerRef.current.abort();
		}

		const controller = new AbortController();
		controllerRef.current = controller;
		const signal = controller.signal;

		// If the value is the same as the last search value, return.
		if ( lastSearchValue.current === value && ignoreSameValue ) {
			return;
		}
		// If the value is less than the minChars, return.
		if ( value.length < minChars && value.length > 0 ) {
			return;
		}

		fetchAndSetListItems( value, signal );
	};

	// Clean up controller when component unmounts.
	useEffect( () => {
		return () => {
			if ( controllerRef.current ) {
				controllerRef.current.abort();
			}
		};
	}, [] );

	return {
		isFetching,
		listItems,
		onSearch,
		setListItems,
	};
};

export default useAjaxSearch;
