import React, { useCallback, useMemo, useState, } from "react";
import { useClientLayoutEffect } from "../../../utils-external/index.js";
import { cl, createStrictContext } from "../../../utils/helpers/index.js";
import { usePrevious } from "../../../utils/hooks/index.js";
import { useInputContext } from "../Input/Input.context.js";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext.js";
import { toComboboxOption } from "../combobox-utils.js";
import { useComboboxCustomOptions } from "../customOptionsContext.js";
import filteredOptionsUtils from "./filtered-options-util.js";
import useVirtualFocus from "./useVirtualFocus.js";
const { Provider: FilteredOptionsContextProvider, useContext: useFilteredOptionsContext, } = createStrictContext({
    name: "FilteredOptionsContext",
    errorMessage: "useFilteredOptionsContext must be used within a FilteredOptionsProvider",
});
const FilteredOptionsProvider = ({ children, value: props, }) => {
    const { allowNewValues, filteredOptions: externalFilteredOptions, isListOpen: isExternalListOpen, isLoading, options, } = props;
    const [filteredOptionsRef, setFilteredOptionsRef] = useState(null);
    const virtualFocus = useVirtualFocus(filteredOptionsRef);
    const { inputProps: { "aria-describedby": partialAriaDescribedBy, id, disabled }, readOnly, value, searchTerm, setValue, setSearchTerm, shouldAutocomplete, setHideCaret, } = useInputContext();
    const { maxSelected } = useSelectedOptionsContext();
    const [isInternalListOpen, setInternalListOpen] = useState(false);
    const { customOptions } = useComboboxCustomOptions();
    const filteredOptions = useMemo(() => {
        if (externalFilteredOptions) {
            return externalFilteredOptions;
        }
        const opts = [...customOptions, ...options];
        return filteredOptionsUtils.getMatchingValuesFromList(searchTerm, opts);
    }, [customOptions, externalFilteredOptions, options, searchTerm]);
    const previousSearchTerm = usePrevious(searchTerm);
    const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] = useState(false);
    /**
     * Returns a map of all possible virtual options avaliable in the combobox, including:
     * - Filtered options
     * - Custom options
     * - Add new option
     */
    const allOptionsMap = useMemo(() => {
        const initialMap = Object.assign({ [filteredOptionsUtils.getAddNewOptionId(id)]: allowNewValues
                ? toComboboxOption(value)
                : undefined }, customOptions.reduce((acc, customOption) => {
            const _id = filteredOptionsUtils.getOptionId(id, customOption.value);
            acc[_id] = customOption;
            return acc;
        }, {}));
        /* Add the filtered options to the map */
        const mapWithFiltered = filteredOptions.reduce((map, _option) => {
            const _id = filteredOptionsUtils.getOptionId(id, _option.value);
            map[_id] = _option;
            return map;
        }, initialMap);
        /* Add all options, avoiding duplicates */
        const finalMap = options.reduce((map, _option) => {
            const _id = filteredOptionsUtils.getOptionId(id, _option.value);
            if (!map[_id]) {
                map[_id] = _option;
            }
            return map;
        }, mapWithFiltered);
        return finalMap;
    }, [allowNewValues, customOptions, id, filteredOptions, options, value]);
    useClientLayoutEffect(() => {
        var _a;
        const autoCompleteCandidate = (_a = filteredOptionsUtils.getFirstValueStartingWith(searchTerm, filteredOptions)) === null || _a === void 0 ? void 0 : _a.label;
        if (shouldAutocomplete &&
            autoCompleteCandidate &&
            ((previousSearchTerm === null || previousSearchTerm === void 0 ? void 0 : previousSearchTerm.length) || 0) < searchTerm.length) {
            setValue(`${searchTerm}${autoCompleteCandidate.substring(searchTerm.length)}`);
        }
    }, [
        filteredOptions,
        previousSearchTerm,
        searchTerm,
        setSearchTerm,
        setValue,
        shouldAutocomplete,
    ]);
    const isListOpen = useMemo(() => {
        return isExternalListOpen !== null && isExternalListOpen !== void 0 ? isExternalListOpen : isInternalListOpen;
    }, [isExternalListOpen, isInternalListOpen]);
    const toggleIsListOpen = useCallback((newState) => {
        if (disabled || readOnly) {
            return;
        }
        virtualFocus.resetFocus();
        if (newState !== null && newState !== void 0 ? newState : !isInternalListOpen) {
            setHideCaret(maxSelected.isLimitReached);
        }
        setInternalListOpen((oldState) => newState !== null && newState !== void 0 ? newState : !oldState);
    }, [
        virtualFocus,
        maxSelected.isLimitReached,
        isInternalListOpen,
        setHideCaret,
        disabled,
        readOnly,
    ]);
    const isValueNew = useMemo(() => Boolean(searchTerm) &&
        !allOptionsMap[filteredOptionsUtils.getOptionId(id, searchTerm)], [allOptionsMap, id, searchTerm]);
    const ariaDescribedBy = useMemo(() => {
        let activeOption = "";
        if (!isLoading && filteredOptions.length === 0 && !allowNewValues) {
            activeOption = filteredOptionsUtils.getNoHitsId(id);
        }
        else if (value || isLoading) {
            if (shouldAutocomplete && filteredOptions[0]) {
                activeOption = filteredOptionsUtils.getOptionId(id, filteredOptions[0].value);
            }
            else if (isListOpen && isLoading) {
                activeOption = filteredOptionsUtils.getIsLoadingId(id);
            }
        }
        const maybeMaxSelectedOptionsId = maxSelected.isLimitReached &&
            filteredOptionsUtils.getMaxSelectedOptionsId(id);
        return (cl(activeOption, maybeMaxSelectedOptionsId, partialAriaDescribedBy) ||
            undefined);
    }, [
        isListOpen,
        isLoading,
        maxSelected.isLimitReached,
        value,
        partialAriaDescribedBy,
        shouldAutocomplete,
        filteredOptions,
        id,
        allowNewValues,
    ]);
    const currentOption = useMemo(() => { var _a; return allOptionsMap[((_a = virtualFocus.activeElement) === null || _a === void 0 ? void 0 : _a.getAttribute("id")) || -1]; }, [allOptionsMap, virtualFocus]);
    const activeDescendantId = useMemo(() => { var _a; return ((_a = virtualFocus.activeElement) === null || _a === void 0 ? void 0 : _a.getAttribute("id")) || undefined; }, [virtualFocus.activeElement]);
    const filteredOptionsState = {
        activeDescendantId,
        allowNewValues,
        setFilteredOptionsRef,
        shouldAutocomplete,
        isListOpen,
        isLoading,
        filteredOptions,
        isMouseLastUsedInputDevice,
        setIsMouseLastUsedInputDevice,
        isValueNew,
        toggleIsListOpen,
        currentOption,
        virtualFocus,
        ariaDescribedBy,
    };
    return (React.createElement(FilteredOptionsContextProvider, Object.assign({}, filteredOptionsState), children));
};
export { FilteredOptionsProvider, useFilteredOptionsContext };
//# sourceMappingURL=filteredOptionsContext.js.map