/** @license MIT | @cube-dev/ui-kit v0.0.0-canary-1fc5ad1 | Cube Dev Team */
import { extractStyles } from "../../../tasty/utils/styles.js";
import { CONTAINER_STYLES } from "../../../tasty/styles/list.js";
import { filterBaseProps } from "../../../tasty/utils/filter-base-props.js";
import { mergeProps as mergeProps$1 } from "../../../utils/react/mergeProps.js";
import { TooltipProvider } from "../../overlays/Tooltip/TooltipProvider.js";
import { LoadingIcon } from "../../../icons/LoadingIcon.js";
import { useMenuContext } from "../Menu/context.js";
import { MenuItem } from "../Menu/MenuItem.js";
import { StyledDivider, StyledFooter, StyledHeader, StyledMenu } from "../Menu/styled.js";
import { MenuSection } from "../Menu/MenuSection.js";
import { _MenuTrigger } from "../Menu/MenuTrigger.js";
import { StyledCommandMenu, StyledEmptyState, StyledLoadingWrapper, StyledSearchInput } from "./styled.js";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { useFilter, useMenu } from "react-aria";
import { useDOMRef } from "@react-spectrum/utils";
import { Item, Section, useTreeState } from "react-stately";
import { useSyncRef } from "@react-aria/utils";

//#region src/components/actions/CommandMenu/CommandMenu.tsx
function CommandMenu(props, ref) {
	const { searchPlaceholder = "Search commands...", searchValue: controlledSearchValue, onSearchChange, filter: customFilter, emptyLabel = "No commands found", searchInputStyles, headerStyles, footerStyles, isLoading = false, shouldFilter = true, autoFocus = true, size = "medium", qa, styles, selectedKeys, defaultSelectedKeys, onSelectionChange, header, footer, ...restMenuProps } = props;
	const domRef = useDOMRef(ref);
	const searchInputRef = useRef(null);
	const contextProps = useMenuContext();
	const completeProps = mergeProps$1(contextProps, restMenuProps, {
		selectedKeys: selectedKeys ? new Set(selectedKeys) : void 0,
		defaultSelectedKeys: defaultSelectedKeys ? new Set(defaultSelectedKeys) : void 0,
		onSelectionChange: onSelectionChange ? (keys) => {
			if (keys === "all") onSelectionChange(Array.from(treeState.collection.getKeys()).map((key) => String(key)));
			else if (keys instanceof Set) onSelectionChange(Array.from(keys).map((key) => String(key)));
			else onSelectionChange([]);
		} : void 0
	});
	const [internalSearchValue, setInternalSearchValue] = useState("");
	const searchValue = controlledSearchValue ?? internalSearchValue;
	const handleSearchChange = useCallback((value) => {
		if (controlledSearchValue === void 0) setInternalSearchValue(value);
		onSearchChange?.(value);
	}, [controlledSearchValue, onSearchChange]);
	const { contains } = useFilter({ sensitivity: "base" });
	useMemo(() => customFilter || contains, [customFilter, contains]);
	const enhancedFilter = useCallback((textValue, inputValue, item) => {
		if (item?.forceMount) return true;
		if (!shouldFilter) return true;
		const searchWords = inputValue.trim().toLowerCase().split(/\s+/).filter((word) => word.length > 0);
		if (searchWords.length === 0) return true;
		const searchableTexts = [];
		searchableTexts.push(textValue.toLowerCase());
		if (item?.keywords && Array.isArray(item.keywords)) searchableTexts.push(...item.keywords.map((keyword) => keyword.toLowerCase()));
		return searchWords.every((searchWord) => searchableTexts.some((text) => text.includes(searchWord)));
	}, [shouldFilter]);
	const collectionFilter = useCallback((nodes) => {
		const term = searchValue.trim();
		if (!term) return nodes;
		if (term.toLowerCase().split(/\s+/).filter((word) => word.length > 0).length === 0) return nodes;
		const filterNodes = (iter) => {
			const result = [];
			for (const node of iter) if (node.type === "section") {
				const filteredChildren = filterNodes(node.childNodes);
				if (filteredChildren.length) result.push({
					...node,
					childNodes: filteredChildren
				});
			} else if (enhancedFilter(node.textValue ?? String(node.rendered ?? ""), term, node.props)) result.push(node);
			return result;
		};
		return filterNodes(nodes);
	}, [searchValue, enhancedFilter]);
	const treeState = useTreeState({
		...completeProps,
		filter: collectionFilter,
		shouldUseVirtualFocus: true
	});
	const collectionItems = [...treeState.collection];
	collectionItems.some((item) => item.type === "section");
	const [focusedKey, setFocusedKey] = React.useState(null);
	const focusedKeyRef = useRef(null);
	const filteredCollectionItems = useMemo(() => {
		const term = searchValue.trim();
		if (!term) return collectionItems;
		if (term.toLowerCase().split(/\s+/).filter((word) => word.length > 0).length === 0) return collectionItems;
		const filterNodes = (items) => {
			const result = [];
			[...items].forEach((item) => {
				if (item.type === "section") {
					const filteredChildren = filterNodes(item.childNodes);
					if (filteredChildren.length) result.push({
						...item,
						childNodes: filteredChildren
					});
				} else if (enhancedFilter(item.textValue ?? String(item.rendered ?? ""), term, item.props)) result.push(item);
			});
			return result;
		};
		return filterNodes(collectionItems);
	}, [
		collectionItems,
		searchValue,
		enhancedFilter
	]);
	const hasFilteredItems = filteredCollectionItems.length > 0;
	const viewHasSections = filteredCollectionItems.some((item) => item.type === "section");
	const findFirstSelectableItem = useCallback(() => {
		const { selectionManager } = treeState;
		const visit = (items) => {
			for (const node of items) if (node?.type === "section") {
				const childResult = visit(Array.from(node.childNodes ?? []));
				if (childResult != null) return childResult;
			} else if (node?.type === "item" && !selectionManager.isDisabled(node.key)) return node.key;
			return null;
		};
		return visit(filteredCollectionItems);
	}, [filteredCollectionItems, treeState.selectionManager]);
	const menuRef = useRef(null);
	const { menuProps } = useMenu({
		...completeProps,
		"aria-label": "Command palette menu",
		filter: collectionFilter,
		shouldUseVirtualFocus: true
	}, treeState, menuRef);
	const renderedItems = useMemo(() => {
		const items = [];
		let isFirstSection = true;
		filteredCollectionItems.forEach((item) => {
			if (item.type === "section") {
				if (!isFirstSection) items.push(/* @__PURE__ */ jsx(StyledDivider, {
					role: "separator",
					"aria-orientation": "horizontal"
				}, `divider-${String(item.key)}`));
				items.push(/* @__PURE__ */ jsx(MenuSection, {
					item,
					state: treeState,
					styles: completeProps.sectionStyles,
					itemStyles: completeProps.itemStyles,
					headingStyles: completeProps.sectionHeadingStyles,
					size
				}, item.key));
				isFirstSection = false;
				return;
			}
			let menuItem = /* @__PURE__ */ jsx(MenuItem, {
				item,
				state: treeState,
				styles: completeProps.itemStyles,
				size,
				onAction: item.onAction
			}, item.key);
			if (item.props.tooltip) menuItem = /* @__PURE__ */ jsx(TooltipProvider, {
				activeWrap: true,
				placement: "right",
				...typeof item.props.tooltip === "string" ? { title: item.props.tooltip } : item.props.tooltip,
				children: menuItem
			}, item.key);
			if (item.props.wrapper) menuItem = item.props.wrapper(menuItem);
			items.push(React.cloneElement(menuItem, { key: item.key }));
		});
		return items;
	}, [
		filteredCollectionItems,
		treeState,
		completeProps.sectionStyles,
		completeProps.itemStyles,
		completeProps.sectionHeadingStyles
	]);
	React.useEffect(() => {
		if (autoFocus && searchInputRef.current) {
			const timeoutId = setTimeout(() => {
				if (searchInputRef.current) searchInputRef.current.focus();
			}, 0);
			return () => clearTimeout(timeoutId);
		}
	}, [autoFocus]);
	React.useEffect(() => {
		if (autoFocus && contextProps.autoFocus && searchInputRef.current) {
			const timeoutId = setTimeout(() => {
				if (searchInputRef.current) searchInputRef.current.focus();
			}, 50);
			return () => clearTimeout(timeoutId);
		}
	}, [autoFocus, contextProps.autoFocus]);
	const prevSearchValueRef = useRef("");
	React.useEffect(() => {
		const currentSearchValue = searchValue.trim();
		if (currentSearchValue !== prevSearchValueRef.current && currentSearchValue !== "") {
			const firstSelectableKey = findFirstSelectableItem();
			if (firstSelectableKey && hasFilteredItems) {
				treeState.selectionManager.setFocusedKey(firstSelectableKey);
				setFocusedKey(firstSelectableKey);
				focusedKeyRef.current = firstSelectableKey;
			} else {
				treeState.selectionManager.setFocusedKey(null);
				setFocusedKey(null);
				focusedKeyRef.current = null;
			}
		}
		prevSearchValueRef.current = currentSearchValue;
	}, [
		searchValue,
		findFirstSelectableItem,
		hasFilteredItems
	]);
	const extractedStyles = useMemo(() => extractStyles(props, CONTAINER_STYLES), [props]);
	const showEmptyState = searchValue.trim().length > 0 && !hasFilteredItems && !isLoading;
	useSyncRef(contextProps, menuRef);
	const mods = useMemo(() => {
		let popoverMod = completeProps.mods?.popover;
		let trayMod = completeProps.mods?.tray;
		return {
			sections: viewHasSections,
			footer: !!footer,
			header: !!header,
			popover: popoverMod,
			tray: trayMod
		};
	}, [
		viewHasSections,
		footer,
		header,
		completeProps.mods
	]);
	return /* @__PURE__ */ jsxs(StyledCommandMenu, {
		...filterBaseProps(props),
		ref: domRef,
		qa: qa || "CommandMenu",
		"data-size": size,
		mods,
		styles: mergeProps$1(extractedStyles, styles),
		children: [
			header && /* @__PURE__ */ jsx(StyledHeader, {
				role: "presentation",
				"data-size": size,
				styles: {
					border: "none",
					...headerStyles
				},
				children: header
			}),
			/* @__PURE__ */ jsx(StyledSearchInput, {
				ref: searchInputRef,
				type: "search",
				placeholder: searchPlaceholder,
				value: searchValue,
				styles: searchInputStyles,
				"data-size": size,
				"aria-controls": `${qa || "CommandMenu"}-menu`,
				role: "combobox",
				"aria-expanded": "true",
				"aria-haspopup": "listbox",
				"aria-activedescendant": focusedKey != null ? `${qa || "CommandMenu"}-menu-option-${focusedKey}` : void 0,
				onChange: (e) => handleSearchChange(e.target.value),
				onKeyDown: (e) => {
					if (e.key === "ArrowDown" || e.key === "ArrowUp") {
						e.preventDefault();
						const isArrowDown = e.key === "ArrowDown";
						const { selectionManager } = treeState;
						const currentKey = focusedKeyRef.current || selectionManager.focusedKey;
						const getVisibleItemKeys = () => {
							const keys = [];
							const visit = (items) => {
								for (const node of items) if (node?.type === "section") visit(Array.from(node.childNodes ?? []));
								else if (node?.type === "item") keys.push(node.key);
							};
							visit(filteredCollectionItems);
							return keys;
						};
						const findNextSelectableKey = (currentIndex, direction, visibleKeys) => {
							const increment = direction === "forward" ? 1 : -1;
							for (let i = currentIndex + increment; i >= 0 && i < visibleKeys.length; i += increment) {
								const key = visibleKeys[i];
								if (!selectionManager.isDisabled(key)) return key;
							}
							return null;
						};
						const findFirstLastSelectableKey = (direction, visibleKeys) => {
							const keysToCheck = direction === "forward" ? visibleKeys : [...visibleKeys].reverse();
							for (const key of keysToCheck) if (!selectionManager.isDisabled(key)) return key;
							return null;
						};
						const visibleKeys = getVisibleItemKeys();
						if (visibleKeys.length === 0) return;
						let nextKey;
						const direction = isArrowDown ? "forward" : "backward";
						if (currentKey == null) nextKey = findFirstLastSelectableKey(direction, visibleKeys);
						else {
							const currentIndex = visibleKeys.indexOf(currentKey);
							if (currentIndex === -1) nextKey = findFirstLastSelectableKey(direction, visibleKeys);
							else {
								nextKey = findNextSelectableKey(currentIndex, direction, visibleKeys);
								if (nextKey == null) nextKey = findFirstLastSelectableKey(direction, visibleKeys);
							}
						}
						if (nextKey != null) {
							selectionManager.setFocusedKey(nextKey);
							setFocusedKey(nextKey);
							focusedKeyRef.current = nextKey;
						}
					} else if (e.key === "Enter" || e.key === " " && !searchValue.trim()) {
						const currentFocusedKey = focusedKey || treeState.selectionManager.focusedKey;
						if (currentFocusedKey != null) {
							e.preventDefault();
							if (treeState.selectionManager.selectionMode !== "none") treeState.selectionManager.select(currentFocusedKey, e);
							else {
								const node = treeState.collection.getItem(currentFocusedKey);
								if (node) {
									const onAction = completeProps.onAction;
									if (onAction) onAction(currentFocusedKey);
									if (node.props?.onAction) node.props.onAction(currentFocusedKey);
								}
							}
							const { onClose, closeOnSelect } = contextProps;
							if (onClose && closeOnSelect !== false) onClose();
						}
					} else if (e.key === "Escape") {
						if (searchValue) {
							e.preventDefault();
							handleSearchChange("");
						}
					}
				}
			}),
			isLoading && /* @__PURE__ */ jsx(StyledLoadingWrapper, { children: /* @__PURE__ */ jsx(LoadingIcon, {
				role: "progressbar",
				"aria-label": "Loading commands",
				"aria-hidden": false
			}) }),
			!isLoading && !showEmptyState && /* @__PURE__ */ jsx(StyledMenu, {
				...menuProps,
				ref: menuRef,
				id: `${qa || "CommandMenu"}-menu`,
				"aria-label": "Command menu",
				qa: "Menu",
				"data-size": size,
				mods,
				styles: {
					border: "none",
					boxShadow: "none",
					radius: 0,
					padding: "0.5x"
				},
				children: renderedItems
			}),
			!isLoading && showEmptyState && /* @__PURE__ */ jsx(StyledEmptyState, { children: emptyLabel }),
			footer && /* @__PURE__ */ jsx(StyledFooter, {
				role: "presentation",
				"data-size": size,
				styles: footerStyles,
				children: footer
			})
		]
	});
}
const _CommandMenu = React.forwardRef(CommandMenu);
const __CommandMenu = Object.assign(_CommandMenu, {
	Trigger: _MenuTrigger,
	Item,
	Section,
	displayName: "CommandMenu"
});

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