/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-af8d194 | Cube Dev Team */
import { extractStyles } from "../../../tasty/utils/styles.js";
import { BASE_STYLES, COLOR_STYLES, OUTER_STYLES } from "../../../tasty/styles/list.js";
import { _modAttrs } from "../../../tasty/utils/mod-attrs.js";
import { tasty } from "../../../tasty/tasty.js";
import { mergeProps as mergeProps$1 } from "../../../utils/react/mergeProps.js";
import { useCombinedRefs } from "../../../utils/react/useCombinedRefs.js";
import { useFocus as useFocus$1 } from "../../../utils/react/interactions.js";
import { LoadingIcon } from "../../../icons/LoadingIcon.js";
import { useProviderProps } from "../../../provider.js";
import { StyledHeader } from "../../actions/Menu/styled.js";
import { useFormProps } from "../../form/Form/Form.js";
import { useFieldProps } from "../../form/Form/use-field/use-field-props.js";
import { wrapWithField } from "../../form/wrapper.js";
import { ListBox } from "../ListBox/ListBox.js";
import { DEFAULT_INPUT_STYLES, INPUT_WRAPPER_STYLES } from "../TextInput/TextInputBase.js";
import { cloneElement, forwardRef, isValidElement, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { useFilter, useKeyboard } from "react-aria";
import { Item, Section, useListState } from "react-stately";

//#region src/components/fields/FilterListBox/FilterListBox.tsx
const FilterListBoxWrapperElement = tasty({ styles: {
	display: "grid",
	flow: "column",
	gridColumns: "1sf",
	gridRows: "max-content max-content 1sf",
	gap: 0,
	position: "relative",
	radius: true,
	color: "#dark-02",
	transition: "theme",
	outline: {
		"": "#purple-03.0",
		"invalid & focused": "#danger.50",
		focused: "#purple-03"
	},
	border: {
		"": true,
		focused: "#primary-text",
		valid: "#success-text.50",
		invalid: "#danger-text.50",
		disabled: true,
		popover: false
	}
} });
const SearchWrapperElement = tasty({
	qa: "FilterListBoxSearchWrapper",
	styles: {
		...INPUT_WRAPPER_STYLES,
		border: "bottom",
		radius: "1r top",
		fill: "#clear",
		height: "($size + 1x)",
		$size: {
			"": "$size-md",
			"size=small": "$size-sm",
			"size=medium": "$size-md",
			"size=large": "$size-lg"
		}
	}
});
const SearchInputElement = tasty({
	as: "input",
	styles: {
		...DEFAULT_INPUT_STYLES,
		fill: "#clear",
		padding: {
			"": ".5x 1.5x",
			prefix: "0 1.5x 0 .5x"
		}
	}
});
const StyledHeaderWithoutBorder = tasty(StyledHeader, { styles: { border: false } });
const PROP_STYLES = [
	...BASE_STYLES,
	...OUTER_STYLES,
	...COLOR_STYLES
];
const FilterListBox = forwardRef(function FilterListBox(props, ref) {
	props = useProviderProps(props);
	props = useFormProps(props);
	props = useFieldProps(props, { valuePropsMapper: ({ value, onChange }) => {
		const fieldProps = {};
		if (props.selectionMode === "multiple") fieldProps.selectedKeys = value || [];
		else fieldProps.selectedKey = value ?? null;
		fieldProps.onSelectionChange = (key) => {
			if (props.selectionMode === "multiple") if (key === "all") onChange("all");
			else onChange(key ? Array.isArray(key) ? key : [key] : []);
			else onChange(Array.isArray(key) ? key[0] : key);
		};
		return fieldProps;
	} });
	let { qa, label, extra, id, labelStyles, isRequired, necessityIndicator, validationState, isDisabled, isLoading, searchPlaceholder = "Search...", autoFocus, filter, mods: externalMods, emptyLabel, searchInputStyles, listStyles, optionStyles, sectionStyles, headingStyles, searchInputRef, listRef, message, description, styles, focusOnHover, shouldFocusWrap, labelSuffix, selectedKey, defaultSelectedKey, selectedKeys, defaultSelectedKeys, onSelectionChange: externalOnSelectionChange, allowsCustomValue = false, showSelectAll, selectAllLabel, header, footer, size = "medium", headerStyles, footerStyles, listBoxStyles, items, children: renderChildren, onEscape, isCheckable, onOptionClick, selectionMode = "single", allValueProps, customValueProps, newCustomValueProps, searchValue: controlledSearchValue, onSearchChange, _internalCollection, form, ...otherProps } = props;
	let children = renderChildren;
	const renderFn = renderChildren;
	if (items && typeof renderFn === "function") try {
		children = Array.from(items).map((item, idx) => {
			const rendered = renderFn(item);
			if (isValidElement(rendered) && rendered.key == null) return cloneElement(rendered, { key: rendered?.key ?? item?.key ?? idx });
			return rendered;
		});
	} catch {}
	const localCollectionState = _internalCollection ? { collection: _internalCollection } : useListState({
		children,
		items,
		selectionMode: "none"
	});
	const originalKeys = useMemo(() => {
		const keys = /* @__PURE__ */ new Set();
		for (const item of localCollectionState.collection) if (item.type === "item") keys.add(String(item.key));
		return keys;
	}, [localCollectionState.collection]);
	const [customKeys, setCustomKeys] = useState(/* @__PURE__ */ new Set());
	useEffect(() => {
		if (!allowsCustomValue) return;
		const currentSelectedKeys = selectedKeys ? selectedKeys === "all" ? [] : Array.from(selectedKeys).map(String) : selectedKey != null ? [String(selectedKey)] : [];
		if (currentSelectedKeys.length === 0) return;
		const keysToAdd = currentSelectedKeys.filter((k) => !originalKeys.has(k));
		if (keysToAdd.length) setCustomKeys((prev) => new Set([...Array.from(prev), ...keysToAdd]));
	}, [
		allowsCustomValue,
		selectedKeys,
		selectedKey,
		originalKeys
	]);
	const mergedChildren = useMemo(() => {
		if (!children && customKeys.size === 0) return children;
		const customArray = Array.from(customKeys).map((key) => /* @__PURE__ */ jsx(Item, {
			textValue: key,
			...customValueProps,
			children: key
		}, key));
		const selectedKeysSet = /* @__PURE__ */ new Set();
		if (selectionMode === "multiple") if (selectedKeys === "all") {} else Array.from(selectedKeys ?? []).forEach((k) => selectedKeysSet.add(String(k)));
		else if (selectedKey != null) selectedKeysSet.add(String(selectedKey));
		const selectedCustom = [];
		const unselectedCustom = [];
		customArray.forEach((item) => {
			if (selectedKeysSet.has(String(item.key))) selectedCustom.push(item);
			else unselectedCustom.push(item);
		});
		if (!children) return [...selectedCustom, ...unselectedCustom];
		const originalArray = Array.isArray(children) ? children : [children];
		return [
			...selectedCustom,
			...originalArray,
			...unselectedCustom
		];
	}, [
		children,
		customKeys,
		selectionMode,
		selectedKey,
		selectedKeys
	]);
	const innerAriaLabel = props["aria-label"] || (typeof label === "string" ? label : void 0);
	const [internalSearchValue, setInternalSearchValue] = useState("");
	const isSearchControlled = controlledSearchValue !== void 0;
	const searchValue = isSearchControlled ? controlledSearchValue : internalSearchValue;
	const handleSearchChange = useCallback((value) => {
		if (!isSearchControlled) setInternalSearchValue(value);
		onSearchChange?.(value);
	}, [isSearchControlled, onSearchChange]);
	const { contains } = useFilter({ sensitivity: "base" });
	const textFilterFn = useMemo(() => filter === false ? () => true : filter || contains, [filter, contains]);
	const filterFn = useCallback((nodes) => {
		const term = searchValue.trim();
		if (!term) return nodes;
		return [...nodes].map((node) => {
			if (node.type === "section" && node.childNodes) {
				const filteredNodes = [...node.childNodes].filter((child) => textFilterFn(child.textValue || "", term));
				if (filteredNodes.length === 0) return null;
				return {
					...node,
					childNodes: filteredNodes,
					hasChildNodes: true
				};
			}
			return textFilterFn(node.textValue || "", term) ? node : null;
		}).filter(Boolean);
	}, [searchValue, textFilterFn]);
	const enhancedChildren = useMemo(() => {
		let childrenToProcess = mergedChildren;
		if (!allowsCustomValue) return childrenToProcess;
		const term = searchValue.trim();
		if (!term) return childrenToProcess;
		const doesTermExist = (term) => {
			if (customKeys.has(term)) return true;
			for (const item of localCollectionState.collection) if (item.type === "item") {
				if (term === (item.textValue || String(item.rendered || "")) || String(item.key) === term) return true;
			}
			return false;
		};
		if (doesTermExist(term)) return childrenToProcess;
		const hasVisibleFilteredItems = (() => {
			for (const item of localCollectionState.collection) if (item.type === "item") {
				if (textFilterFn(item.textValue || String(item.rendered || ""), term)) return true;
			} else if (item.type === "section" && item.childNodes) {
				for (const child of item.childNodes) if (textFilterFn(child.textValue || String(child.rendered || ""), term)) return true;
			}
			for (const customKey of customKeys) if (textFilterFn(customKey, term)) return true;
			return false;
		})();
		const customOption = /* @__PURE__ */ jsx(Item, {
			textValue: term,
			...mergeProps$1(customValueProps, newCustomValueProps),
			children: term
		}, term);
		if (hasVisibleFilteredItems && childrenToProcess) {
			const hasSections = [...localCollectionState.collection].some((item) => item.type === "section");
			const customValueSection = /* @__PURE__ */ jsx(Section, {
				"aria-label": "Custom value",
				children: customOption
			}, "__custom_value__");
			if (hasSections) {
				if (Array.isArray(childrenToProcess)) return [...childrenToProcess, customValueSection];
				return [childrenToProcess, customValueSection];
			}
			return [/* @__PURE__ */ jsx(Section, {
				"aria-label": "Filtered items",
				children: childrenToProcess
			}, "__filtered_items__"), customValueSection];
		}
		if (Array.isArray(childrenToProcess)) return [...childrenToProcess, customOption];
		if (childrenToProcess) return [childrenToProcess, customOption];
		return customOption;
	}, [
		allowsCustomValue,
		mergedChildren,
		searchValue,
		customKeys,
		localCollectionState.collection,
		customValueProps,
		newCustomValueProps,
		textFilterFn
	]);
	styles = extractStyles(otherProps, PROP_STYLES, styles);
	ref = useCombinedRefs(ref);
	searchInputRef = useCombinedRefs(searchInputRef);
	listRef = useCombinedRefs(listRef);
	const { isFocused, focusProps } = useFocus$1({ isDisabled });
	const isInvalid = validationState === "invalid";
	const listBoxRef = useRef(null);
	const listStateRef = useRef(null);
	useLayoutEffect(() => {
		const listState = listStateRef.current;
		if (!listState) return;
		const { selectionManager, collection } = listState;
		const collectVisibleKeys = (nodes, out) => {
			for (const node of nodes) if (node.type === "item") out.push(node.key);
			else if (node.childNodes) collectVisibleKeys(node.childNodes, out);
		};
		const visibleKeys = [];
		collectVisibleKeys(collection, visibleKeys);
		if (visibleKeys.length === 0) {
			selectionManager.setFocusedKey(null);
			return;
		}
		const currentFocused = selectionManager.focusedKey;
		if (currentFocused != null && visibleKeys.includes(currentFocused)) return;
		const findFirstVisibleSelectedKey = () => {
			if (selectionMode === "single") {
				if (selectedKey != null) {
					const selectedKeyStr = String(selectedKey);
					if (visibleKeys.some((k) => String(k) === selectedKeyStr)) return selectedKey;
				}
			} else if (selectionMode === "multiple") {
				if (selectedKeys && selectedKeys !== "all") for (const key of selectedKeys) {
					const keyStr = String(key);
					if (visibleKeys.some((k) => String(k) === keyStr)) return key;
				}
			}
			return null;
		};
		let keyToFocus = null;
		if (currentFocused == null) keyToFocus = findFirstVisibleSelectedKey();
		else keyToFocus = findFirstVisibleSelectedKey();
		if (keyToFocus == null) keyToFocus = visibleKeys[0];
		if (listState.lastFocusSourceRef) listState.lastFocusSourceRef.current = "keyboard";
		selectionManager.setFocusedKey(keyToFocus);
	}, [
		searchValue,
		enhancedChildren,
		selectionMode,
		selectedKey,
		selectedKeys
	]);
	const { keyboardProps } = useKeyboard({ onKeyDown: (e) => {
		if (e.key === "ArrowDown" || e.key === "ArrowUp") {
			e.preventDefault();
			const listState = listStateRef.current;
			if (!listState) return;
			const { selectionManager, collection } = listState;
			const collectVisibleKeys = (nodes, out) => {
				for (const node of nodes) if (node.type === "item") out.push(node.key);
				else if (node.childNodes) collectVisibleKeys(node.childNodes, out);
			};
			const visibleKeys = [];
			collectVisibleKeys(collection, visibleKeys);
			if (visibleKeys.length === 0) return;
			const isArrowDown = e.key === "ArrowDown";
			const direction = isArrowDown ? 1 : -1;
			const currentKey = selectionManager.focusedKey;
			let nextKey = null;
			if (currentKey == null) nextKey = isArrowDown ? visibleKeys[0] : visibleKeys[visibleKeys.length - 1];
			else {
				const currentIndex = visibleKeys.indexOf(currentKey);
				if (currentIndex !== -1) {
					const newIndex = currentIndex + direction;
					if (newIndex >= 0 && newIndex < visibleKeys.length) nextKey = visibleKeys[newIndex];
					else if (shouldFocusWrap) nextKey = isArrowDown ? visibleKeys[0] : visibleKeys[visibleKeys.length - 1];
				} else nextKey = isArrowDown ? visibleKeys[0] : visibleKeys[visibleKeys.length - 1];
			}
			if (nextKey != null) {
				if (listState.lastFocusSourceRef) listState.lastFocusSourceRef.current = "keyboard";
				selectionManager.setFocusedKey(nextKey);
			}
		} else if (e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown") {
			e.preventDefault();
			const listState = listStateRef.current;
			if (!listState) return;
			const { selectionManager, collection } = listState;
			const collectVisibleKeys = (nodes, out) => {
				for (const node of nodes) if (node.type === "item") out.push(node.key);
				else if (node.childNodes) collectVisibleKeys(node.childNodes, out);
			};
			const visibleKeys = [];
			collectVisibleKeys(collection, visibleKeys);
			if (visibleKeys.length === 0) return;
			const targetKey = e.key === "Home" || e.key === "PageUp" ? visibleKeys[0] : visibleKeys[visibleKeys.length - 1];
			if (listState.lastFocusSourceRef) listState.lastFocusSourceRef.current = "keyboard";
			selectionManager.setFocusedKey(targetKey);
		} else if (e.key === "Enter" || e.key === " " && !searchValue) {
			const listState = listStateRef.current;
			if (!listState) return;
			const keyToSelect = listState.selectionManager.focusedKey;
			if (keyToSelect != null) {
				e.preventDefault();
				listState.selectionManager.select(keyToSelect, e);
				if (e.key === "Enter" && isCheckable && onEscape && selectionMode === "multiple") onEscape();
			}
		} else if (e.key === "Escape") {
			if (searchValue) {
				e.preventDefault();
				handleSearchChange("");
			} else if (onEscape) {
				e.preventDefault();
				onEscape();
			}
		}
	} });
	const mods = useMemo(() => ({
		invalid: isInvalid,
		valid: validationState === "valid",
		disabled: !!isDisabled,
		focused: isFocused,
		loading: !!isLoading,
		searchable: true,
		prefix: !!isLoading,
		...externalMods
	}), [
		isInvalid,
		validationState,
		isDisabled,
		isFocused,
		isLoading,
		externalMods
	]);
	const handleSelectionChange = (selection) => {
		if (allowsCustomValue) {
			let selectedValues = [];
			if (selection != null) if (Array.isArray(selection)) selectedValues = selection.map(String);
			else selectedValues = [String(selection)];
			const nextSet = /* @__PURE__ */ new Set();
			selectedValues.forEach((val) => {
				if (!originalKeys.has(val)) nextSet.add(val);
			});
			setCustomKeys(nextSet);
		}
		if (externalOnSelectionChange) externalOnSelectionChange(selection);
	};
	const handleOptionClick = (key) => {
		setTimeout(() => {
			if (searchInputRef.current) searchInputRef.current.focus();
		}, 0);
		if (onOptionClick) onOptionClick(key);
	};
	const searchInput = /* @__PURE__ */ jsxs(SearchWrapperElement, {
		mods,
		"data-size": size,
		children: [isLoading && /* @__PURE__ */ jsx("div", {
			"data-element": "Prefix",
			children: /* @__PURE__ */ jsx("div", {
				"data-element": "InputIcon",
				children: isLoading ? /* @__PURE__ */ jsx(LoadingIcon, {}) : null
			})
		}), /* @__PURE__ */ jsx(SearchInputElement, {
			ref: searchInputRef,
			qa: qa || "FilterListBox",
			id,
			"data-prefix": isLoading ? "" : void 0,
			type: "search",
			placeholder: searchPlaceholder,
			value: searchValue,
			disabled: isDisabled,
			autoFocus,
			"data-autofocus": autoFocus ? "" : void 0,
			styles: searchInputStyles,
			"data-size": size,
			"data-input-type": "filterlistbox",
			role: "combobox",
			"aria-expanded": "true",
			"aria-haspopup": "listbox",
			"aria-activedescendant": listStateRef.current?.selectionManager.focusedKey != null ? `ListBoxItem-${listStateRef.current?.selectionManager.focusedKey}` : void 0,
			onChange: (e) => {
				handleSearchChange(e.target.value);
			},
			...keyboardProps,
			..._modAttrs(mods)
		})]
	});
	return wrapWithField(/* @__PURE__ */ jsxs(FilterListBoxWrapperElement, {
		ref,
		qa: "FilterListBoxWrapper",
		..._modAttrs(mods),
		styles,
		...focusProps,
		children: [
			header ? /* @__PURE__ */ jsx(StyledHeaderWithoutBorder, {
				"data-size": size,
				styles: headerStyles,
				children: header
			}) : /* @__PURE__ */ jsx("div", { role: "presentation" }),
			searchInput,
			/* @__PURE__ */ jsx(ListBox, {
				ref: listBoxRef,
				"aria-label": innerAriaLabel,
				selectedKey,
				defaultSelectedKey,
				selectedKeys,
				defaultSelectedKeys,
				selectionMode,
				isDisabled,
				listRef,
				stateRef: listStateRef,
				listStyles,
				shouldFocusWrap,
				optionStyles,
				sectionStyles,
				headingStyles,
				validationState,
				disallowEmptySelection: props.disallowEmptySelection,
				disabledKeys: props.disabledKeys,
				focusOnHover,
				shouldUseVirtualFocus: true,
				showSelectAll,
				selectAllLabel,
				footer,
				footerStyles,
				mods,
				size: "medium",
				styles: listBoxStyles,
				isCheckable,
				items,
				allValueProps,
				filter: filterFn,
				emptyLabel: searchValue.trim() ? emptyLabel !== void 0 ? emptyLabel : "No results found" : "No items",
				onSelectionChange: handleSelectionChange,
				onEscape,
				onOptionClick: handleOptionClick,
				children: enhancedChildren
			})
		]
	}), ref, props);
});
FilterListBox.Item = ListBox.Item;
FilterListBox.Section = Section;
Object.defineProperty(FilterListBox, "cubeInputType", {
	value: "FilterListBox",
	enumerable: false,
	configurable: false
});

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