/**
 * WordPress dependencies
 */
import { __ } from "@wordpress/i18n";
import { useState, useEffect, useMemo, useRef } from "@wordpress/element";
import { FlexBlock, Flex, SearchControl, MenuItem, Popover, Button, Disabled, __experimentalScrollable as Scrollable } from "@wordpress/components";

/**
 * Custom PWP Search Component that allows to search an item from an array.
 *
 * @param {object} props component property
 * props: {
 * 			label: string, - label of the search input
 * 			placeholder: string, - placeholder of the search input
 * 			help: string, - help text of the search input
 * 			options: array<{ value: string|number, label: string }>  - list of options for the search input
 * 			totalPages: number - total possible number of pages from the response
 * 			currentPage: number - the current page of the api request if used
 * 			selected: array - list of the selected options
 * 			onChange: function - function to trigger when there's a change on value
 * 			onInputChange: function - function to trigger when there's a change on the input value
 * 			onLoadMore: function - event trigger when load more option is clicked from the display option list
 * 			disabled: boolean - Whether disable the search control or not
 * 		}
 *
 */
export const PWPSearchOption = ({
	label,
	placeholder,
	help,
	options,
	totalPages = 1,
	currentPage,
	selected,
	onChange,
	onInputChange,
	onLoadMore,
	disabled,
}) => {
	/** States **/
	const LOAD_MORE = 'LOAD_MORE',
			KEY_TAB = 'Tab',
			KEY_ESC = 'Escape';
	const [ searchInput, setSearchInput ] = useState( '' );
	const [ popoverAnchor, setPopoverAnchor ] = useState();
	const [ forceClosePopover, setForceClosePopover ] = useState( false );
	const popoverRef = useRef();

	/** Computed **/
	const displayOptions = useMemo(() => {
		if (currentPage < totalPages) {
			return [
				...options,
				{
					value: LOAD_MORE,
					label: __( 'Load more', 'personalizewp' ),
				},
			];
		}
		return options;
	}, [ options, currentPage, searchInput ] );

	const hasOptionToDisplay = useMemo(() => {
		return displayOptions.length > 0;
	}, [ displayOptions ] );

	const showSearchResults = useMemo(() => {
		// Refuse to show any results if popover forced to stay closed.
		if (forceClosePopover) {
			return false;
		}
		return hasOptionToDisplay;
	}, [ hasOptionToDisplay, forceClosePopover ] );

	/**
	 * Function to add a selected option from the array list,
	 * it will check the list if the option has already been selected,
	 * if not it will push to the selectedOptions array
	 *
	 * @param {any} optionVal selected option value
	 */
	const onAddOption = (optionVal) => {
		if (optionVal !== null) {
			const alreadySelected = selected.find((s) => s.value === optionVal);
			if (!alreadySelected) {
				const option = options?.find((u) => u.value === optionVal);
				onChange([...selected, option]);
			}
		}
		// Immediately close the popover if an item is added.
		setForceClosePopover( true );
	};

	/**
	 * Function to remove a selected option from the display list,
	 *
	 * @param {any} option selected option value
	 * option: { value: string, label: string }
	 */
	const onRemoveOption = ({ value }) => {
		onChange(selected.filter((o) => o.value !== value));
	};

	/**
	 * Applies focus to the selected items list
	 *
	 * @param {any} index
	 */
	const applyFocusToList = (index) => {
		const elements = document.getElementsByClassName(
			"search-options__menu-item",
		);
		if (elements && elements.length > index) {
			elements[index].focus();
		}
	};

	/** Effects **/
	// Close the popover on mount whether it has a display option or not
	useEffect( () => {
		setForceClosePopover( true );
	}, [] );

	const searchControlInput = (
		<SearchControl
			ref={setPopoverAnchor}
			label={label}
			placeholder={placeholder}
			help={help}
			size="compact"
			value={searchInput}
			onFocus={() => {
				// Once the input has content, and there are items to show...
				if ( '' !== searchInput && hasOptionToDisplay ) {
					// ...allow the popover to show.
					setForceClosePopover( false );
				}
			}}
			onChange={(input) => {
				// Allow the popover to show.
				setForceClosePopover(false);
				setSearchInput(input);
				onInputChange(input);
			}}
			onKeyDown={({ key }) => {
				if ( KEY_ESC === key ) {
					setForceClosePopover(true);
					return;
				}
				if (
					KEY_TAB === key &&
					showSearchResults
				) {
					popoverRef?.current?.focus();
				}
			}}
		/>
	);

	return (
		<>
			{disabled ? <Disabled>{searchControlInput}</Disabled> : searchControlInput}

			{showSearchResults && (
				<Popover
					ref={popoverRef}
					anchor={popoverAnchor}
					focusOnMount={false}
					tabIndex={0}
					placement={"bottom-start"}
					flip={false}
					onFocusOutside={() => {
						setForceClosePopover(true);
					}}
				>
					<Scrollable style={{ maxHeight: 200 }}>
						{displayOptions.map((option, index) => {
							return (
								<MenuItem
									className="search-options__menu-item"
									data={index}
									key={index}
									onClick={() => {
										if (LOAD_MORE === option.value) {
											onLoadMore();
											applyFocusToList(index - 1);
										} else {
											onAddOption(option.value);
										}
									}}
									onKeyDown={({ shiftKey, key }) => {
										if ( KEY_ESC === key ) {
											setForceClosePopover(true);
											popoverAnchor?.focus();
											return;
										}
										if (
											(shiftKey &&
												KEY_TAB === key &&
												index === 0) ||
											(KEY_TAB === key &&
												index ===
													displayOptions.length - 1)
										) {
											popoverAnchor?.focus();
										}
									}}
								>
									{option.label}
								</MenuItem>
							);
						})}
					</Scrollable>
				</Popover>
			)}

			<FlexBlock className={"search-option-selected"}>
				{selected.map((option, index) => {
					return (
						<Flex
							key={index}
							justify="space-between"
							className={"search-option-selected-item"}
						>
							<span>{option.label}</span>
							<Button
								style={{
									padding: "0",
									minWidth: "min-content",
								}}
								icon="no-alt"
								onClick={() => {
									onRemoveOption(option);
								}}
							/>
						</Flex>
					);
				})}
			</FlexBlock>
		</>
	);
};
