import React, {useState, useEffect, forwardRef, useMemo} from 'react';
import { useCombobox, useMultipleSelection } from 'downshift';
import { debounce } from 'lodash';

interface SelectOption {
	value: any; // eslint-disable-line @typescript-eslint/no-explicit-any
	label: string;
	isCustom?: boolean;
	[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

interface AsyncSelectInputProps {

	/** The current value - can be primitive value, option object, or array for multiple selections */
	value?: any; // eslint-disable-line @typescript-eslint/no-explicit-any

	/** Callback when the value changes - receives single item or array based on maxSelections */
	onChange: ( value: any ) => void; // eslint-disable-line @typescript-eslint/no-explicit-any

	/** Function to load options asynchronously */
	loadOptions?: (
		inputValue: string,
		callback: ( options: SelectOption[]) => void
	) => void;

	/** Default options to show */
	defaultOptions?: SelectOption[] | boolean;

	/** Whether the select is loading */
	isLoading?: boolean;

	/** Whether the select is searchable */
	isSearchable?: boolean;

	/** Name for the select */
	name?: string;

	/** If true, the field is disabled */
	disabled?: boolean;

	/** Placeholder text */
	placeholder?: string;

	/** If true, positions dropdown with fixed positioning (useful inside modals) */
	insideModal?: boolean;

	/** Maximum number of selections allowed (default: 1) */
	maxSelections?: number;

	/** Whether to show remove buttons on selected items */
	showRemoveButton?: boolean;

	/** Whether to allow creating custom options when no matches are found */
	allowCustomValue?: boolean;
}

/**
 * AsyncSelect input component using Downshift with multiple selection support.
 *
 * A combobox component that supports async loading of options and multiple selections.
 * Uses Downshift's useCombobox and useMultipleSelection hooks for accessibility and keyboard navigation.
 */
const AsyncSelectInput = forwardRef<HTMLInputElement, AsyncSelectInputProps>(
	(
		{
			value,
			onChange,
			loadOptions,
			defaultOptions = [],
			isLoading = false,
			isSearchable = true,
			name,
			disabled = false,
			placeholder = 'Select an option...',
			insideModal = false,
			maxSelections = 1,
			showRemoveButton = true,
			allowCustomValue = true,
			...props
		},
		ref
	) => {
		const [ items, setItems ] = useState<SelectOption[]>([]);

		// Initialize input value - should be empty if there are already selected items
		const [ inputValue, setInputValue ] = useState( '' );
		const [ loading, setLoading ] = useState( isLoading );

		useEffect( () => {
			setLoading( isLoading );
		}, [ isLoading ]);

		// Create debounced search function
		const debouncedLoadOptions = useMemo( () => {
			return debounce( ( searchValue: string ) => {
				if ( loadOptions ) {
					setLoading( true );
					loadOptions( searchValue, ( options ) => {
						setItems( options );
						setLoading( false );
					});
				}
			}, 150 );
		}, [ loadOptions ]); // eslint-disable-line react-hooks/exhaustive-deps


		// Cleanup debounce on unmount
		useEffect( () => {
			return () => {
				debouncedLoadOptions.cancel();
			};
		}, [ debouncedLoadOptions ]);

		// Initialize items with default options
		useEffect( () => {
			if ( Array.isArray( defaultOptions ) ) {
				setItems( defaultOptions );
			} else if ( true === defaultOptions && loadOptions ) {

				// Load initial options
				setLoading( true );
				loadOptions( '', ( options ) => {
					setItems( options );
					setLoading( false );
				});
			}
		}, [ defaultOptions, loadOptions ]);

		// Convert value to array of selected items
		const getSelectedItems = (): SelectOption[] => {
			if ( ! value ) {
				return [];
			}

			// Handle array values (multiple selections)
			if ( Array.isArray( value ) ) {
				return value.map( ( item ) => {
					if (
						'object' === typeof item &&
						Object.prototype.hasOwnProperty.call( item, 'value' ) &&
						Object.prototype.hasOwnProperty.call( item, 'label' )
					) {
						return item;
					}

					// Find in items or create basic option
					const foundOption = items.find(
						( option ) => option.value === item
					);
					return (
						foundOption || { value: item, label: item.toString() }
					);
				});
			}

			// Handle single value
			if (
				'object' === typeof value &&
				Object.prototype.hasOwnProperty.call( value, 'value' ) &&
				Object.prototype.hasOwnProperty.call( value, 'label' )
			) {
				return [ value ];
			}

			// If value is primitive, try to find it in items
			const foundOption = items.find( ( option ) => option.value === value );
			if ( foundOption ) {
				return [ foundOption ];
			}

			// Create basic option object
			return [
				{
					value,
					label: value.toString()
				}
			];
		};

		// Use propSelectedItems if provided, otherwise use the state
		const currentSelectedItems = getSelectedItems();

		// Filter items to exclude already selected ones
		let availableItems = items.filter(
			( item ) =>
				! currentSelectedItems.some(
					( selected ) => selected.value === item.value
				)
		);

		// Add custom option if allowed and input doesn't match any existing option
		const canAddCustom =
			allowCustomValue &&
			'' !== inputValue.trim() &&
			( 0 === items.length ||
				( ! items.some(
					( item ) =>
						( item.label ?? '' ).toLowerCase() ===
						inputValue.toLowerCase()
				) &&
					! currentSelectedItems.some(
						( selected ) =>
							( selected.label ?? '' ).toLowerCase() ===
							inputValue.toLowerCase()
					) ) );

		if ( canAddCustom ) {
			availableItems = [
				{
					value: inputValue.trim(),
					label: inputValue.trim(),
					isCustom: true
				},
				...availableItems
			];
		}

		const {
			getSelectedItemProps,
			getDropdownProps
		} = useMultipleSelection({
			selectedItems: currentSelectedItems, // Use currentSelectedItems from props or state
			onStateChange({ selectedItems: newSelectedItems, type }) {
				switch ( type ) {
					case useMultipleSelection.stateChangeTypes
						.SelectedItemKeyDownBackspace:
					case useMultipleSelection.stateChangeTypes
						.SelectedItemKeyDownDelete:
					case useMultipleSelection.stateChangeTypes
						.DropdownKeyDownBackspace:
					case useMultipleSelection.stateChangeTypes
						.FunctionRemoveSelectedItem:

						// Return single item or array based on maxSelections
						if ( newSelectedItems ) {
							if ( 1 === maxSelections ) {
								onChange(
									0 < newSelectedItems.length ?
										newSelectedItems[0] :
										null
								);
							} else {
								onChange( newSelectedItems );
							}
						}
						break;
					default:
						break;
				}
			}
		});

		const {
			isOpen,
			getToggleButtonProps,
			getInputProps,
			getMenuProps,
			getItemProps,
			highlightedIndex,
			getComboboxProps
		} = useCombobox({
			items: availableItems,
			selectedItem: null,
			inputValue,
			stateReducer( state, actionAndChanges ) {
				const { changes, type } = actionAndChanges;
				switch ( type ) {
					case useCombobox.stateChangeTypes.InputKeyDownEnter:
					case useCombobox.stateChangeTypes.ItemClick:
						return {
							...changes,
							isOpen: 1 < maxSelections ? true : false, // Keep open for multiple, close for single
							highlightedIndex: 0,
							inputValue: '' // Clear input after selection
						};
					default:
						return changes;
				}
			},
			onStateChange({
				type,
				selectedItem: newSelectedItem,
				inputValue: newInputValue
			}) {
				switch ( type ) {
					case useCombobox.stateChangeTypes.InputKeyDownEnter:
					case useCombobox.stateChangeTypes.ItemClick:
						if ( newSelectedItem ) {

							// For single selection, automatically replace existing selection
							if ( 1 === maxSelections ) {
								onChange( newSelectedItem );
								setInputValue( '' );
							} else if ( // For multiple selections, only add if under limit
								currentSelectedItems.length < maxSelections
							) {
								const newSelectedItems = [
									...currentSelectedItems,
									newSelectedItem
								];
								onChange( newSelectedItems );
								setInputValue( '' );
							}
						}
						break;
					case useCombobox.stateChangeTypes.InputChange:
						setInputValue( newInputValue || '' );

						if ( loadOptions && isSearchable ) {
							debouncedLoadOptions( newInputValue || '' );
						}
						break;
					default:
						break;
				}
			},
			itemToString: ( item ) => ( item ? item.label : '' )
		});

		const handleRemoveItem = ( itemToRemove: SelectOption ) => {
			const newSelectedItems = currentSelectedItems.filter(
				( item ) => item.value !== itemToRemove.value
			);

			// Return single item or array based on maxSelections
			if ( 1 === maxSelections ) {
				onChange(
					0 < newSelectedItems.length ? newSelectedItems[0] : null
				);
			} else {
				onChange( newSelectedItems );
			}
		};

		// Handle backspace when input is empty to remove last selected item
		const handleKeyDown = ( event: React.KeyboardEvent ) => {
			if (
				'Backspace' === event.key &&
				'' === inputValue &&
				0 < currentSelectedItems.length
			) {
				event.preventDefault();
				const lastItem =
					currentSelectedItems[currentSelectedItems.length - 1];
				handleRemoveItem( lastItem );
			}
		};

		return (
			<>
				{/* Combobox container with integrated selected items */}
				<div
					{...getComboboxProps()}
					className="flex min-h-[2.5rem] w-full rounded-md border border-gray-400 bg-white focus-within:border-primary-dark focus-within:ring disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-200"
				>
					{/* Container for selected items and input */}
					<div className="flex flex-1 flex-wrap items-center gap-1 p-1">
						{/* Selected items (tags) */}
						{currentSelectedItems.map( ( selectedItem, index ) => (
							<span
								key={`selected-item-${index}`}
								{...getSelectedItemProps({
									selectedItem,
									index
								})}
								className="inline-flex items-center gap-1 rounded bg-primary-light px-2 py-1 text-xs text-primary-dark focus:bg-primary focus:text-white focus:outline-none"
							>
								{selectedItem.label}
								{showRemoveButton && (
									<button
										type="button"
										onClick={( e ) => {
											e.stopPropagation();
											handleRemoveItem( selectedItem );
										}}
										className="ml-1 rounded-full hover:bg-primary hover:text-white focus:bg-primary focus:text-white focus:outline-none"
										aria-label={`Remove ${selectedItem.label}`}
									>
										<svg
											className="h-3 w-3"
											fill="currentColor"
											viewBox="0 0 20 20"
										>
											<path
												fillRule="evenodd"
												d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
												clipRule="evenodd"
											/>
										</svg>
									</button>
								)}
							</span>
						) )}

						{/* Input field */}
						{( 1 === maxSelections ||
							currentSelectedItems.length < maxSelections ) && (
							<input
								{...getInputProps(
									getDropdownProps({
										preventKeyAction: isOpen,
										...( ref ? { ref } : {}),
										name,
										disabled,
										placeholder:
											0 === currentSelectedItems.length ?
												placeholder :
												1 < maxSelections ?
													`Add ${maxSelections - currentSelectedItems.length} more...` :
													'',
										readOnly: ! isSearchable,
										onKeyDown: handleKeyDown,
										...props
									})
								)}
								className="flex-1 min-w-[120px] border-none bg-transparent p-1 focus:outline-none disabled:cursor-not-allowed"
								style={{ outline: 'none' }}
							/>
						)}
					</div>

					{/* Selection counter and toggle button */}
					<div className="flex items-center border-l border-gray-300">
						{/* Max selections indicator */}
						{0 < maxSelections && (
							<span className="px-2 text-xs text-gray-500 border-r border-gray-200">
								{currentSelectedItems.length}/{maxSelections}
							</span>
						)}

						{/* Toggle button */}
						<button
							type="button"
							{...getToggleButtonProps({
								disabled
							})}
							className="flex items-center justify-center bg-transparent px-2 py-2 hover:bg-gray-100 focus:border-primary-dark focus:outline-none disabled:cursor-not-allowed disabled:bg-gray-200"
							aria-label="Toggle menu"
						>
							{loading ? (
								<div className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-primary"></div>
							) : (
								<svg
									className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
									fill="none"
									stroke="currentColor"
									viewBox="0 0 24 24"
								>
									<path
										strokeLinecap="round"
										strokeLinejoin="round"
										strokeWidth={2}
										d="M19 9l-7 7-7-7"
									/>
								</svg>
							)}
						</button>
					</div>
				</div>

				{/* Menu */}
				<div
					{...getMenuProps()}
					className={`w-full ${insideModal ? 'fixed' : ''} ${! isOpen ? 'hidden' : ''}`}
				>
					<ul
						className={`relative top-0 z-[9999] max-h-60 overflow-y-auto w-full rounded-md border border-gray-300 bg-white shadow-lg ${
							! ( isOpen && availableItems.length ) ? 'hidden' : ''
						}`}
					>
						{availableItems.map( ( item, index ) => (
							<li
								key={item.value}
								{...getItemProps({ item, index })}
								className={`cursor-pointer px-3 py-2 text-sm first:rounded-t-md last:rounded-b-md ${
									highlightedIndex === index ?
										'bg-primary-light text-primary-dark' :
										'hover:bg-gray-100'
								}`}
							>
								{item.isCustom ? (
									<span className="flex items-center gap-2">
										<svg
											className="h-4 w-4 text-primary"
											fill="none"
											stroke="currentColor"
											viewBox="0 0 24 24"
										>
											<path
												strokeLinecap="round"
												strokeLinejoin="round"
												strokeWidth={2}
												d="M12 6v6m0 0v6m0-6h6m-6 0H6"
											/>
										</svg>
										Create &quot;{item.label}&quot;

									</span>
								) : (
									item.label
								)}
							</li>
						) )}
						{0 === availableItems.length && ! loading && (
							<li className="px-3 py-2 text-sm text-gray-500">
								{currentSelectedItems.length >= maxSelections ?
									`Maximum ${maxSelections} selection${1 < maxSelections ? 's' : ''} reached` :
									'No options found'}
							</li>
						)}
					</ul>
				</div>
			</>
		);
	}
);

AsyncSelectInput.displayName = 'AsyncSelectInput';

export default AsyncSelectInput;
