/** @license MIT | @cube-dev/ui-kit v0.147.0 | Cube Dev Team */
import { extractStyles } from "../../../utils/styles.js";
import { useCombinedRefs } from "../../../utils/react/useCombinedRefs.js";
import { usePopoverSync } from "../../../utils/react/usePopoverSync.js";
import { useEvent } from "../../../_internal/hooks/use-event.js";
import { useFocus as useFocus$1 } from "../../../utils/react/interactions.js";
import { LoadingIcon } from "../../../icons/LoadingIcon.js";
import { useProviderProps } from "../../../provider.js";
import { _CollectionItem } from "../../CollectionItem.js";
import { useI18n } from "../../../i18n/useI18n.js";
import { CloseIcon } from "../../../icons/CloseIcon.js";
import { DirectionIcon } from "../../../icons/DirectionIcon.js";
import { SearchIcon } from "../../../icons/SearchIcon.js";
import { generateRandomId } from "../../../utils/random.js";
import { wrapWithField } from "../../form/wrapper.js";
import { InvalidIcon } from "../../shared/InvalidIcon.js";
import { ValidIcon } from "../../shared/ValidIcon.js";
import { DEFAULT_INPUT_STYLES, INPUT_WRAPPER_STYLES } from "../TextInput/TextInputBase.js";
import { ListBoxPopover } from "../ListBoxPopover/ListBoxPopover.js";
import { useCompositeFocus } from "../ListBoxPopover/useCompositeFocus.js";
import { filterCollectionNodes, getEdgeVisibleKey, getNextVisibleKey, getVisibleKeys, markKeyboardFocus } from "../ListBoxPopover/listNavigation.js";
import { BASE_STYLES, COLOR_STYLES, OUTER_STYLES, tasty } from "@tenphi/tasty";
import React, { cloneElement, forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { useFilter, useKeyboard } from "react-aria";
import { Section, useListState } from "react-stately";

//#region src/components/fields/SearchComboBox/SearchComboBox.tsx
const SearchComboBoxWrapperElement = tasty({
	qa: "SearchComboBoxWrapper",
	styles: INPUT_WRAPPER_STYLES
});
const InputElement = tasty({
	as: "input",
	styles: DEFAULT_INPUT_STYLES
});
const PROP_STYLES = [
	...BASE_STYLES,
	...OUTER_STYLES,
	...COLOR_STYLES
];
function useDelayedFlag(active, delay) {
	const [flag, setFlag] = useState(false);
	useEffect(() => {
		if (!active) {
			setFlag(false);
			return;
		}
		if (delay <= 0) {
			setFlag(true);
			return;
		}
		const id = setTimeout(() => setFlag(true), delay);
		return () => clearTimeout(id);
	}, [active, delay]);
	return flag;
}
const SearchComboBoxInput = forwardRef(function SearchComboBoxInput({ qa, inputRef, id, value, placeholder, isDisabled, isReadOnly, autoFocus, autoComplete, size, mods, inputStyles, keyboardProps, focusProps, onChange, onFocus, isExpanded, searchComboBoxId, listStateRef }, ref) {
	return /* @__PURE__ */ jsx(InputElement, {
		ref: useCombinedRefs(ref, inputRef),
		qa,
		id,
		type: "search",
		value,
		placeholder,
		isDisabled,
		readOnly: isReadOnly,
		autoFocus,
		autoComplete,
		"data-autofocus": autoFocus ? "" : void 0,
		onChange,
		onFocus,
		onBlur: focusProps.onBlur,
		...keyboardProps,
		styles: inputStyles,
		mods,
		"data-size": size,
		"data-input-type": "searchcombobox",
		role: "combobox",
		"aria-expanded": isExpanded,
		"aria-haspopup": "listbox",
		"aria-controls": isExpanded ? `SearchComboBoxListBox-${searchComboBoxId}` : void 0,
		"aria-activedescendant": isExpanded && listStateRef.current?.selectionManager.focusedKey != null ? `ListBoxItem-${listStateRef.current?.selectionManager.focusedKey}` : void 0
	});
});
const SearchComboBox = forwardRef(function SearchComboBox(props, ref) {
	props = useProviderProps(props);
	const { t } = useI18n();
	let { qa, label, labelStyles, isRequired, necessityIndicator, validationState, id, icon, prefix, isDisabled, autoFocus, autoComplete = "off", wrapperRef, inputRef, triggerRef, popoverRef, listBoxRef, isLoading, loadingDelay = 1e3, inputStyles, optionStyles, triggerStyles, listBoxStyles, overlayStyles, suffix, hideTrigger = true, message, description, size = "medium", direction = "bottom", shouldFlip = true, popoverTrigger = "input", suffixPosition = "before", filter, styles, labelSuffix, inputValue, defaultInputValue, onInputChange, isClearable = true, onClear, placeholder, emptyLabel, onSelect, onSubmit, items, children: renderChildren, sectionStyles, headingStyles, isReadOnly, overlayOffset = 8, containerPadding = 8, onOpenChange, onFocus, onBlur, onKeyDown, disabledKeys, ...otherProps } = props;
	const searchComboBoxId = useMemo(() => generateRandomId(), []);
	wrapperRef = useCombinedRefs(wrapperRef);
	popoverRef = useCombinedRefs(popoverRef);
	const isControlledInput = inputValue !== void 0;
	const [internalInputValue, setInternalInputValue] = useState(defaultInputValue ?? "");
	const effectiveInputValue = isControlledInput ? inputValue : internalInputValue;
	const [isPopoverOpen, setIsPopoverOpen] = useState(false);
	styles = extractStyles(otherProps, PROP_STYLES, styles);
	ref = useCombinedRefs(ref);
	inputRef = useCombinedRefs(inputRef);
	triggerRef = useCombinedRefs(triggerRef);
	listBoxRef = useCombinedRefs(listBoxRef);
	const children = renderChildren;
	const [isFilterActive, setIsFilterActive] = useState(false);
	const { contains } = useFilter({ sensitivity: "base" });
	const textFilterFn = useMemo(() => filter === false ? () => true : filter || contains, [filter, contains]);
	const filterFn = useCallback((nodes) => {
		const term = effectiveInputValue.trim();
		if (!isFilterActive || !term) return nodes;
		return filterCollectionNodes(nodes, term, textFilterFn);
	}, [
		isFilterActive,
		effectiveInputValue,
		textFilterFn
	]);
	const localCollectionState = useListState({
		children,
		items,
		selectionMode: "none"
	});
	const { isFocused, focusProps } = useFocus$1({ isDisabled });
	const isInvalid = validationState === "invalid";
	const validation = cloneElement(isInvalid ? InvalidIcon : ValidIcon);
	const listStateRef = useRef(null);
	const [, setFocusTick] = useState(0);
	const bumpFocus = useEvent(() => setFocusTick((t) => t + 1));
	const getItemLabel = useCallback((key) => {
		return (localCollectionState?.collection?.getItem(key))?.textValue || String(key);
	}, [localCollectionState?.collection]);
	const showLoading = useDelayedFlag(!!isLoading, loadingDelay);
	const hasResults = useMemo(() => {
		const collection = localCollectionState?.collection;
		if (!collection) return false;
		for (const node of filterFn(collection)) if (node.type === "section") {
			if (node.childNodes && [...node.childNodes].length > 0) return true;
		} else return true;
		return false;
	}, [localCollectionState?.collection, filterFn]);
	const disabledKeySet = useMemo(() => new Set(disabledKeys ? Array.from(disabledKeys) : []), [disabledKeys]);
	const visibleItemKeys = useMemo(() => {
		const collection = localCollectionState?.collection;
		const keys = [];
		if (!collection) return keys;
		const walk = (nodes) => {
			for (const node of nodes) if (node.type === "item") {
				if (!disabledKeySet.has(node.key)) keys.push(node.key);
			} else if (node.childNodes) walk(node.childNodes);
		};
		walk(filterFn(collection));
		return keys;
	}, [
		localCollectionState?.collection,
		filterFn,
		disabledKeySet
	]);
	const trimmedInput = effectiveInputValue.trim();
	const shouldShowPopover = Boolean(isPopoverOpen && (hasResults || showLoading || trimmedInput.length > 0 && !isLoading));
	usePopoverSync({
		menuId: searchComboBoxId,
		isOpen: shouldShowPopover,
		onClose: () => setIsPopoverOpen(false),
		triggerRef: wrapperRef,
		containerRef: popoverRef
	});
	useEffect(() => {
		onOpenChange?.(shouldShowPopover);
	}, [shouldShowPopover]);
	const suppressFocusOpenRef = useRef(false);
	const clearAndClose = useEvent(() => {
		setIsFilterActive(false);
		if (!isControlledInput) setInternalInputValue("");
		onInputChange?.("");
		setIsPopoverOpen(false);
		suppressFocusOpenRef.current = true;
		setTimeout(() => {
			inputRef.current?.focus();
			suppressFocusOpenRef.current = false;
		}, 0);
	});
	const handleSelectionChange = useEvent((selection) => {
		const key = Array.isArray(selection) ? selection[0] : selection;
		if (key != null) {
			const textValue = getItemLabel(key);
			onSelect?.(String(key), textValue);
		}
		clearAndClose();
	});
	const handleSubmit = useEvent((value) => {
		onSubmit?.(value);
		clearAndClose();
	});
	const clearValue = useEvent(() => {
		if (!isControlledInput) setInternalInputValue("");
		onInputChange?.("");
		setIsFilterActive(false);
		if (isPopoverOpen) setIsPopoverOpen(false);
		suppressFocusOpenRef.current = true;
		setTimeout(() => {
			inputRef.current?.focus();
			suppressFocusOpenRef.current = false;
		}, 0);
		onClear?.();
	});
	const handleInputChange = useEvent((e) => {
		const value = e.target.value;
		if (!isControlledInput) setInternalInputValue(value);
		onInputChange?.(value);
		const trimmed = value.trim();
		setIsFilterActive(trimmed.length > 0);
		if (popoverTrigger !== "manual") {
			if (trimmed && !isPopoverOpen) setIsPopoverOpen(true);
			else if (!trimmed && isPopoverOpen && popoverTrigger === "input") setIsPopoverOpen(false);
		}
	});
	const handleInputFocus = useEvent((e) => {
		focusProps.onFocus?.(e);
		if (suppressFocusOpenRef.current) return;
		if (popoverTrigger === "focus" && !isPopoverOpen) setIsPopoverOpen(true);
	});
	const handleCompositeBlur = useEvent(() => {
		setIsFilterActive(false);
		setIsPopoverOpen(false);
		onBlur?.();
	});
	const { compositeFocusProps } = useCompositeFocus({
		wrapperRef,
		popoverRef,
		onFocus,
		onBlur: handleCompositeBlur,
		isDisabled
	});
	const { keyboardProps } = useKeyboard({ onKeyDown: (e) => {
		onKeyDown?.(e);
		if (e.key === "ArrowDown" || e.key === "ArrowUp") {
			e.preventDefault();
			if (!shouldShowPopover) {
				if (!isPopoverOpen) {
					if (!hasResults) setIsFilterActive(false);
					setIsPopoverOpen(true);
				}
				return;
			}
			const listState = listStateRef.current;
			if (!listState) return;
			const nextKey = getNextVisibleKey(listState, e.key === "ArrowDown" ? 1 : -1);
			if (nextKey != null) {
				markKeyboardFocus(listState);
				listState.selectionManager.setFocusedKey(nextKey);
				bumpFocus();
			}
		} else if (e.key === "Enter") {
			if (shouldShowPopover) {
				const listState = listStateRef.current;
				const visibleKeys = listState ? getVisibleKeys(listState) : visibleItemKeys;
				const focusedKey = listState?.selectionManager.focusedKey;
				const keyToSelect = focusedKey != null && visibleKeys.includes(focusedKey) ? focusedKey : visibleKeys[0] ?? null;
				if (keyToSelect != null) {
					e.preventDefault();
					if (listState) listState.selectionManager.select(keyToSelect, e);
					else handleSelectionChange(keyToSelect);
					return;
				}
			}
			const value = trimmedInput;
			if (onSubmit && value) {
				e.preventDefault();
				handleSubmit(value);
				return;
			}
			e.preventDefault();
		} else if (e.key === "Escape") {
			if (isPopoverOpen) {
				e.preventDefault();
				setIsPopoverOpen(false);
				inputRef.current?.focus();
			} else if (effectiveInputValue) {
				e.preventDefault();
				if (isClearable) clearValue();
			}
		} else if (e.key === "Home" || e.key === "End") {
			if (shouldShowPopover) {
				e.preventDefault();
				const listState = listStateRef.current;
				if (!listState) return;
				const targetKey = getEdgeVisibleKey(listState, e.key === "Home" ? "first" : "last");
				if (targetKey != null) {
					markKeyboardFocus(listState);
					listState.selectionManager.setFocusedKey(targetKey);
					bumpFocus();
				}
			}
		}
	} });
	if (icon === void 0) icon = /* @__PURE__ */ jsx(SearchIcon, {});
	if (icon) {
		icon = /* @__PURE__ */ jsx("div", {
			"data-element": "InputIcon",
			children: icon
		});
		if (prefix) prefix = /* @__PURE__ */ jsxs(Fragment$1, { children: [icon, prefix] });
		else prefix = icon;
	}
	const showClearButton = isClearable && effectiveInputValue !== "" && !isDisabled && !isReadOnly;
	const mods = useMemo(() => ({
		invalid: isInvalid,
		valid: validationState === "valid",
		disabled: isDisabled,
		hovered: false,
		focused: isFocused,
		loading: showLoading,
		prefix: !!prefix,
		suffix: true,
		clearable: showClearButton
	}), [
		isInvalid,
		validationState,
		isDisabled,
		isFocused,
		showLoading,
		prefix,
		showClearButton
	]);
	const searchComboBoxWidth = wrapperRef?.current?.offsetWidth;
	useLayoutEffect(() => {
		if (!shouldShowPopover) return;
		let attempts = 0;
		let timer;
		const applyFocus = () => {
			if (!shouldShowPopover) return;
			const listState = listStateRef.current;
			if (listState) {
				const keys = getVisibleKeys(listState);
				if (keys.length > 0) {
					const focusedKey = listState.selectionManager.focusedKey;
					if (!(focusedKey != null && keys.includes(focusedKey))) {
						markKeyboardFocus(listState);
						listState.selectionManager.setFocusedKey(keys[0]);
						bumpFocus();
					}
					return;
				}
			}
			attempts += 1;
			if (attempts < 8) timer = setTimeout(applyFocus, 16);
		};
		applyFocus();
		return () => {
			if (timer != null) clearTimeout(timer);
		};
	}, [shouldShowPopover, visibleItemKeys]);
	const resolvedEmptyLabel = emptyLabel !== void 0 ? emptyLabel : showLoading ? t("searchComboBox.loading", "Loading…") : t("searchComboBox.noResults", "No results found");
	const searchComboBoxField = /* @__PURE__ */ jsxs(SearchComboBoxWrapperElement, {
		ref: wrapperRef,
		mods,
		styles,
		style: { zIndex: isFocused ? 1 : "initial" },
		"data-size": size,
		...compositeFocusProps,
		children: [
			prefix ? /* @__PURE__ */ jsx("div", {
				"data-element": "Prefix",
				children: prefix
			}) : null,
			/* @__PURE__ */ jsx(SearchComboBoxInput, {
				qa: qa || "SearchComboBox",
				inputRef,
				id,
				value: effectiveInputValue,
				placeholder,
				isDisabled,
				isReadOnly,
				autoFocus,
				autoComplete,
				size,
				mods,
				inputStyles,
				keyboardProps,
				focusProps,
				isExpanded: shouldShowPopover,
				searchComboBoxId,
				listStateRef,
				onChange: handleInputChange,
				onFocus: handleInputFocus
			}),
			/* @__PURE__ */ jsxs("div", {
				"data-element": "Suffix",
				children: [
					suffixPosition === "before" ? suffix : null,
					validationState || showLoading ? /* @__PURE__ */ jsxs(Fragment$1, { children: [validationState && !showLoading ? validation : null, showLoading ? /* @__PURE__ */ jsx(LoadingIcon, { "data-element": "InputIcon" }) : null] }) : null,
					suffixPosition === "after" ? suffix : null,
					showClearButton && /* @__PURE__ */ jsx(_CollectionItem.Action, {
						icon: /* @__PURE__ */ jsx(CloseIcon, {}),
						size,
						theme: validationState === "invalid" ? "danger" : void 0,
						qa: "SearchComboBoxClearButton",
						"data-no-trigger": hideTrigger ? "" : void 0,
						"data-popover-dismiss": "",
						"aria-label": t("searchComboBox.clearValue", "Clear value"),
						onPress: clearValue
					}),
					!hideTrigger ? /* @__PURE__ */ jsx(_CollectionItem.Action, {
						ref: triggerRef,
						"data-popover-trigger": true,
						icon: /* @__PURE__ */ jsx(DirectionIcon, { to: shouldShowPopover ? "up" : "down" }),
						qa: "SearchComboBoxTrigger",
						mods: {
							pressed: shouldShowPopover,
							disabled: isDisabled,
							loading: showLoading
						},
						"data-size": size,
						isDisabled,
						styles: triggerStyles,
						"aria-expanded": shouldShowPopover,
						"aria-haspopup": "listbox",
						"aria-label": t("searchComboBox.showOptions", "Show options"),
						onPress: () => {
							if (!isDisabled) {
								const willOpen = !isPopoverOpen;
								setIsPopoverOpen(willOpen);
								if (willOpen) {
									inputRef.current?.focus();
									if (!hasResults) setIsFilterActive(false);
								}
							}
						}
					}) : null
				]
			}),
			/* @__PURE__ */ jsx(ListBoxPopover, {
				isOpen: shouldShowPopover,
				triggerRef: wrapperRef,
				popoverRef,
				listBoxRef,
				direction,
				shouldFlip,
				overlayOffset,
				containerPadding,
				comboBoxWidth: searchComboBoxWidth,
				listBoxId: `SearchComboBoxListBox-${searchComboBoxId}`,
				overlayStyles,
				listBoxStyles,
				optionStyles,
				sectionStyles,
				headingStyles,
				selectedKey: null,
				isDisabled,
				disabledKeys,
				items,
				listStateRef,
				label,
				ariaLabel: props["aria-label"],
				compositeFocusProps,
				filter: filterFn,
				size,
				emptyLabel: resolvedEmptyLabel,
				onSelectionChange: handleSelectionChange,
				onClose: () => setIsPopoverOpen(false),
				children
			})
		]
	});
	const { children: _, ...propsWithoutChildren } = props;
	return wrapWithField(searchComboBoxField, ref, propsWithoutChildren);
});
SearchComboBox.Item = _CollectionItem;
SearchComboBox.Section = Section;

//#endregion
export { SearchComboBox };
//# sourceMappingURL=SearchComboBox.js.map