/** @license MIT | @cube-dev/ui-kit v0.147.0 | Cube Dev Team */
import { chain } from "../../../utils/react/chain.js";
import { mergeProps as mergeProps$1 } from "../../../utils/react/mergeProps.js";
import { useCombinedRefs } from "../../../utils/react/useCombinedRefs.js";
import { useEvent } from "../../../_internal/hooks/use-event.js";
import { useProviderProps } from "../../../provider.js";
import { _CollectionItem } from "../../CollectionItem.js";
import { generateRandomId } from "../../../utils/random.js";
import { useFormProps } from "../../form/Form/Form.js";
import { useFieldProps } from "../../form/Form/use-field/use-field-props.js";
import { castNullableStringValue } from "../../../utils/react/nullableValue.js";
import { TextInputBase } from "../TextInput/TextInputBase.js";
import { ListBoxPopover } from "../ListBoxPopover/ListBoxPopover.js";
import { useCompositeFocus } from "../ListBoxPopover/useCompositeFocus.js";
import { collectVisibleKeys, filterCollectionNodes, getEdgeVisibleKey, getNextVisibleKey, markKeyboardFocus } from "../ListBoxPopover/listNavigation.js";
import { useCaretAnchor } from "./useCaretAnchor.js";
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { useFilter, useTextField } from "react-aria";
import { Section, useListState } from "react-stately";

//#region src/components/fields/CommandTextArea/CommandTextArea.tsx
const DEFAULT_TRIGGERS = [{
	char: "/",
	atLineStart: true
}];
/**
* Inspect the textarea contents up to the caret and find the active trigger
* token, if any. The token includes the trigger character and ends at the
* caret (so it grows as the user types). A token ends at whitespace or the end
* of the input.
*/
function parseActiveToken(value, caret, triggers) {
	if (!triggers.length) return null;
	const before = value.slice(0, caret);
	value.slice(caret);
	for (const trigger of triggers) {
		const ch = trigger.char;
		if (trigger.atLineStart) {
			const lineStart = before.lastIndexOf("\n") + 1;
			const linePrefix = before.slice(lineStart);
			if (linePrefix[0] !== ch) continue;
			if (linePrefix.slice(1).search(/\s/) !== -1) continue;
			if (!linePrefix) continue;
			return {
				trigger,
				token: linePrefix,
				start: lineStart,
				end: caret
			};
		}
		const match = /(?:^|\s)([\S]+)$/.exec(before);
		if (!match) continue;
		const candidate = match[1];
		if (candidate[0] !== ch) continue;
		return {
			trigger,
			token: candidate,
			start: caret - candidate.length,
			end: caret
		};
	}
	return null;
}
function CommandTextArea(props, ref) {
	props = castNullableStringValue(props);
	props = useProviderProps(props);
	props = useFormProps(props);
	props = useFieldProps(props, {
		defaultValidationTrigger: "onBlur",
		valuePropsMapper: ({ value, onChange }) => ({
			onChange,
			value: value?.toString() ?? ""
		})
	});
	let { qa, autoSize = false, isDisabled = false, isReadOnly = false, isRequired = false, onChange, maxRows = 10, rows = 3, labelProps: userLabelProps, inputRef: propsInputRef, value, defaultValue, placeholder, items, children: renderChildren, triggers = DEFAULT_TRIGGERS, filter, onCommand, insertSpaceAfter = true, disabledKeys, direction = "top", shouldFlip = true, overlayOffset = 8, containerPadding = 8, overlayStyles, listBoxStyles, optionStyles, sectionStyles, headingStyles, wrapperRef: propsWrapperRef, popoverRef: propsPopoverRef, listBoxRef: propsListBoxRef, listStateRef: propsListStateRef, onKeyDown, onFocus, onBlur, ...otherProps } = props;
	rows = Math.max(rows, 1);
	maxRows = Math.max(maxRows, rows);
	const localInputRef = useRef(null);
	const inputRef = propsInputRef ?? localInputRef;
	const wrapperRef = useCombinedRefs(propsWrapperRef);
	const popoverRef = useCombinedRefs(propsPopoverRef);
	const listBoxRef = useCombinedRefs(propsListBoxRef);
	const localListStateRef = useRef(null);
	const listStateRef = propsListStateRef ?? localListStateRef;
	const commandTextAreaId = useMemo(() => generateRandomId(), []);
	const isControlled = value !== void 0;
	const [internalValue, setInternalValue] = useState(value ?? defaultValue ?? "");
	const effectiveValue = isControlled ? value : internalValue;
	const handleChange = useEvent((next) => {
		if (!isControlled) setInternalValue(next);
		onChange?.(next);
	});
	let children = renderChildren;
	const renderFn = renderChildren;
	if (items && typeof renderFn === "function") children = Array.from(items).map((item, idx) => {
		const rendered = renderFn(item);
		if (React.isValidElement(rendered) && rendered.key == null) return React.cloneElement(rendered, { key: rendered?.key ?? item?.key ?? idx });
		return rendered;
	});
	const [caret, setCaret] = useState(() => effectiveValue.length);
	const syncCaret = useEvent(() => {
		const el = inputRef.current;
		if (!el) return;
		setCaret(el.selectionStart ?? el.value.length);
	});
	const activeToken = useMemo(() => parseActiveToken(effectiveValue, Math.min(caret, effectiveValue.length), triggers), [
		effectiveValue,
		caret,
		triggers
	]);
	const term = useMemo(() => {
		if (!activeToken) return "";
		const { token, trigger } = activeToken;
		return token.startsWith(trigger.char) ? token.slice(trigger.char.length) : token;
	}, [activeToken]);
	const { contains } = useFilter({ sensitivity: "base" });
	const textFilterFn = useMemo(() => filter === false ? () => true : filter || contains, [filter, contains]);
	const localCollectionState = useListState({
		children,
		items,
		selectionMode: "none"
	});
	const filterFn = useCallback((nodes) => filterCollectionNodes(nodes, term, textFilterFn, { matchExtraFields: true }), [term, textFilterFn]);
	const visibleFilteredKeys = useMemo(() => {
		if (!activeToken) return [];
		const keys = [];
		const disabled = disabledKeys ? new Set(disabledKeys) : void 0;
		collectVisibleKeys(filterFn(localCollectionState.collection), keys, disabled);
		return keys;
	}, [
		activeToken,
		filterFn,
		localCollectionState.collection,
		disabledKeys
	]);
	const filteredCount = visibleFilteredKeys.length;
	const [dismissedToken, setDismissedToken] = useState(null);
	const [, setFocusTick] = useState(0);
	const bumpFocus = useEvent(() => setFocusTick((t) => t + 1));
	useEffect(() => {
		setDismissedToken((prev) => prev != null && activeToken?.token !== prev ? null : prev);
	}, [activeToken?.token]);
	const shouldShowPopover = !!activeToken && activeToken.token !== dismissedToken && filteredCount > 0;
	const { anchorRef: caretAnchorRef, positionApiRef } = useCaretAnchor({
		inputRef,
		wrapperRef,
		caret,
		value: effectiveValue,
		isActive: shouldShowPopover
	});
	const adjustHeight = useEvent(() => {
		const textarea = inputRef.current;
		if (!textarea || !autoSize) return;
		textarea.style.height = "auto";
		const computedStyle = getComputedStyle(textarea);
		const paddingTop = parseFloat(computedStyle.paddingTop) || 0;
		const paddingBottom = parseFloat(computedStyle.paddingBottom) || 0;
		const borderTop = parseFloat(computedStyle.borderTopWidth) || 0;
		const borderBottom = parseFloat(computedStyle.borderBottomWidth) || 0;
		const lineHeight = parseInt(computedStyle.lineHeight) || 20;
		const contentHeight = textarea.scrollHeight - paddingTop - paddingBottom;
		const computedRows = Math.ceil(contentHeight / lineHeight);
		const totalHeight = Math.max(Math.min(computedRows, maxRows), rows) * lineHeight + paddingTop + paddingBottom + borderTop + borderBottom;
		textarea.style.height = `${totalHeight}px`;
	});
	let { labelProps, inputProps } = useTextField({
		...otherProps,
		value: effectiveValue,
		placeholder,
		isDisabled,
		isReadOnly,
		isRequired,
		onChange: chain(handleChange, adjustHeight),
		inputElementType: "textarea"
	}, inputRef);
	const mergedLabelProps = mergeProps$1(labelProps, userLabelProps);
	const useEnvironmentalEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
	useEnvironmentalEffect(() => {
		if (!autoSize || !inputRef.current) return;
		adjustHeight();
		const resizeObserver = new ResizeObserver(adjustHeight);
		resizeObserver.observe(inputRef.current);
		return () => resizeObserver.disconnect();
	}, [autoSize, inputRef.current]);
	useEnvironmentalEffect(() => {
		if (autoSize && inputRef.current) adjustHeight();
	}, [effectiveValue]);
	const pendingCaretRef = useRef(null);
	useEnvironmentalEffect(() => {
		const el = inputRef.current;
		if (!el) return;
		if (pendingCaretRef.current != null) {
			const pos = pendingCaretRef.current;
			pendingCaretRef.current = null;
			el.focus();
			el.setSelectionRange(pos, pos);
			setCaret(pos);
			return;
		}
		const domCaret = el.selectionStart ?? el.value.length;
		setCaret((prev) => prev === domCaret ? prev : domCaret);
	}, [effectiveValue]);
	const getItemTextValue = useCallback((key) => {
		return (localCollectionState?.collection?.getItem(key))?.textValue || String(key);
	}, [localCollectionState?.collection]);
	const commit = useEvent((key) => {
		const listState = listStateRef.current;
		if (visibleFilteredKeys.length > 0 && !visibleFilteredKeys.includes(key)) key = visibleFilteredKeys[0];
		const textValue = getItemTextValue(key);
		const token = activeToken;
		const el = inputRef.current;
		if (token) {
			const next = effectiveValue.slice(0, token.start) + textValue + (insertSpaceAfter ? " " : "") + effectiveValue.slice(token.end);
			const caretPos = token.start + textValue.length + (insertSpaceAfter ? 1 : 0);
			pendingCaretRef.current = caretPos;
			handleChange(next);
			if (el) {
				el.focus();
				try {
					el.setSelectionRange(caretPos, caretPos);
				} catch {}
			}
		} else if (el) {
			const start = el.selectionStart ?? effectiveValue.length;
			const end = el.selectionEnd ?? effectiveValue.length;
			const next = effectiveValue.slice(0, start) + textValue + (insertSpaceAfter ? " " : "") + effectiveValue.slice(end);
			pendingCaretRef.current = start + textValue.length + (insertSpaceAfter ? 1 : 0);
			handleChange(next);
		}
		setDismissedToken(null);
		onCommand?.(key, {
			textValue,
			...listState?.collection?.getItem(key)?.props ?? {}
		});
	});
	const onKeyDownHandler = useCallback((e) => {
		onKeyDown?.(e);
		if (e.defaultPrevented) return;
		if (!shouldShowPopover) return;
		const listState = listStateRef.current;
		if (e.key === "ArrowDown" || e.key === "ArrowUp") {
			if (!listState) return;
			e.preventDefault();
			const nextKey = getNextVisibleKey(listState, e.key === "ArrowDown" ? 1 : -1);
			if (nextKey != null) {
				markKeyboardFocus(listState);
				listState.selectionManager.setFocusedKey(nextKey);
				bumpFocus();
			}
		} else if (e.key === "Home" || e.key === "End") {
			if (!listState) return;
			e.preventDefault();
			const edgeKey = getEdgeVisibleKey(listState, e.key === "Home" ? "first" : "last");
			if (edgeKey != null) {
				markKeyboardFocus(listState);
				listState.selectionManager.setFocusedKey(edgeKey);
				bumpFocus();
			}
		} else if (e.key === "Enter" || e.key === "Tab") {
			if (!listState) return;
			const focused = listState.selectionManager.focusedKey;
			if (focused != null) {
				e.preventDefault();
				commit(focused);
			}
		} else if (e.key === "Escape") {
			e.preventDefault();
			if (activeToken) setDismissedToken(activeToken.token);
		}
	}, [
		shouldShowPopover,
		activeToken,
		commit,
		onKeyDown,
		listStateRef
	]);
	const focusInitAttemptsRef = useRef(0);
	useLayoutEffect(() => {
		if (!shouldShowPopover) return;
		focusInitAttemptsRef.current = 0;
		const tick = () => {
			if (!shouldShowPopover) return;
			const listState = listStateRef.current;
			if (!listState) {
				focusInitAttemptsRef.current += 1;
				if (focusInitAttemptsRef.current < 8) requestAnimationFrame(tick);
				return;
			}
			if (listState.selectionManager.focusedKey == null) {
				const first = getEdgeVisibleKey(listState, "first");
				if (first != null) {
					markKeyboardFocus(listState);
					listState.selectionManager.setFocusedKey(first);
					bumpFocus();
				}
			}
		};
		requestAnimationFrame(() => requestAnimationFrame(tick));
	}, [shouldShowPopover]);
	useLayoutEffect(() => {
		if (!shouldShowPopover) return;
		const listState = listStateRef.current;
		if (!listState) return;
		if (visibleFilteredKeys.length === 0) return;
		const focused = listState.selectionManager.focusedKey;
		if (focused == null || !visibleFilteredKeys.includes(focused)) {
			markKeyboardFocus(listState);
			listState.selectionManager.setFocusedKey(visibleFilteredKeys[0]);
			bumpFocus();
		}
	}, [visibleFilteredKeys, shouldShowPopover]);
	const { compositeFocusProps } = useCompositeFocus({
		wrapperRef,
		popoverRef,
		onFocus,
		onBlur,
		isDisabled
	});
	const focusedKey = listStateRef.current?.selectionManager.focusedKey ?? null;
	const listBoxId = `CommandTextAreaListBox-${commandTextAreaId}`;
	const popoverOverlayStyles = {
		width: "30x max-content 50vw",
		...overlayStyles
	};
	const commandInputProps = mergeProps$1(inputProps, {
		role: "combobox",
		"aria-autocomplete": "list",
		"aria-expanded": shouldShowPopover,
		"aria-haspopup": "listbox",
		"aria-controls": shouldShowPopover ? listBoxId : void 0,
		"aria-activedescendant": shouldShowPopover && focusedKey != null ? `ListBoxItem-${focusedKey}` : void 0,
		onKeyDown: onKeyDownHandler,
		onSelect: syncCaret,
		onKeyUp: syncCaret,
		onClick: syncCaret,
		"data-input-type": "command-textarea"
	});
	return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TextInputBase, {
		ref,
		...otherProps,
		multiLine: true,
		qa,
		inputRef,
		wrapperRef,
		wrapperProps: compositeFocusProps,
		labelProps: mergedLabelProps,
		inputProps: commandInputProps,
		isDisabled,
		isReadOnly,
		isRequired,
		rows
	}), /* @__PURE__ */ jsx(ListBoxPopover, {
		isOpen: shouldShowPopover,
		triggerRef: wrapperRef,
		positionTargetRef: caretAnchorRef,
		positionApiRef,
		popoverRef,
		listBoxRef,
		direction,
		shouldFlip,
		overlayOffset,
		containerPadding,
		listBoxId,
		overlayStyles: popoverOverlayStyles,
		listBoxStyles,
		optionStyles,
		optionHighlight: term,
		sectionStyles,
		headingStyles,
		isDisabled,
		disabledKeys,
		items,
		listStateRef,
		ariaLabel: props["aria-label"] || (typeof otherProps.label === "string" ? otherProps.label : "Commands"),
		compositeFocusProps,
		filter: filterFn,
		onClose: () => {
			if (activeToken) setDismissedToken(activeToken.token);
		},
		onSelectionChange: (selection) => {
			const key = Array.isArray(selection) ? selection[0] : selection;
			if (key != null) commit(key);
		},
		children
	})] });
}
const _CommandTextArea = forwardRef(CommandTextArea);
Object.assign(_CommandTextArea, {
	Item: _CollectionItem,
	Section,
	displayName: "CommandTextArea"
});
Object.defineProperty(_CommandTextArea, "cubeInputType", {
	value: "CommandTextArea",
	enumerable: false,
	configurable: false
});

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