/*! ratio-ui · design system for knowledge sharing · https://github.com/losol/ratio-ui · (c) 2026 Losol AS · MPL-2.0 */
"use client";
import { X } from "../../icons/index.js";
import { ListBox as ListBox$1, ListBoxItem as ListBoxItem$1 } from "../../forms/ListBox/ListBox.js";
import { AutoComplete } from "../../forms/Autocomplete/AutoComplete.js";
import { SearchField as SearchField$1 } from "../../forms/Autocomplete/StyledComponents.js";
import { useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { Button, Input, Label } from "react-aria-components";
import { useAsyncList } from "react-stately";
//#region src/core/Lookup/Lookup.tsx
/**
* Inline typeahead picker: search asynchronously, navigate results with the
* keyboard, pick one to trigger an action. Used for "add user to event",
* "select event", and similar lookup flows where the user is *finding* an
* existing entity, not filling in a form field.
*
* Built on react-aria's `Autocomplete` + `ListBox` + `useAsyncList` and
* handles the common concerns: loading spinner, min-char threshold, deduping
* re-queries after selection, and rendering empty/placeholder states.
*
* For a modal / Cmd+K variant see `CommandPalette`.
*
* @example
* ```tsx
* <Lookup<UserDto>
*   label="Search User"
*   placeholder="Search by name or email (min 3 characters)"
*   minChars={3}
*   load={searchUsers}
*   getItemKey={u => u.id!}
*   getItemLabel={u => u.name ?? ''}
*   getItemTextValue={u => `${u.name} ${u.email}`}
*   renderItem={u => (
*     <>
*       <div className="font-medium">{u.name}</div>
*       <div className="text-sm text-gray-600">{u.email}</div>
*     </>
*   )}
*   onItemSelected={u => setSelected(u)}
*   emptyState="No users found"
* />
* ```
*/
function Lookup({ label, placeholder, minChars = 0, load, getItemKey, getItemLabel, getItemTextValue, renderItem, onItemSelected, emptyState = "No results", minCharsMessage, inputClassName, listClassName, inputId }) {
	const [selectedLabel, setSelectedLabel] = useState(null);
	const list = useAsyncList({
		getKey: getItemKey,
		async load({ filterText, signal }) {
			if (selectedLabel && filterText === selectedLabel) return { items: [] };
			if (!filterText || filterText.length < minChars) return { items: [] };
			try {
				return { items: await load(filterText, signal) };
			} catch {
				return { items: [] };
			}
		}
	});
	const handleSelectionChange = (keys) => {
		if (keys === "all") return;
		const key = keys.values().next().value;
		if (key === void 0) return;
		const item = list.items.find((i) => getItemKey(i) === key);
		if (!item) return;
		onItemSelected(item);
		const newLabel = getItemLabel(item).trim();
		if (newLabel) {
			setSelectedLabel(newLabel);
			list.setFilterText(newLabel);
		}
	};
	const textValueOf = getItemTextValue ?? getItemLabel;
	const resolvedMinCharsMessage = minCharsMessage ?? (minChars > 0 ? `Type at least ${minChars} character${minChars === 1 ? "" : "s"} to search` : void 0);
	const clearInput = () => {
		setSelectedLabel(null);
		list.setFilterText("");
	};
	const trimmedFilter = list.filterText.trim();
	const isJustSelected = selectedLabel !== null && trimmedFilter === selectedLabel;
	const shouldShowList = trimmedFilter.length > 0 && !isJustSelected;
	const belowMinChars = trimmedFilter.length < minChars;
	return /* @__PURE__ */ jsxs(AutoComplete, {
		inputValue: list.filterText,
		onInputChange: (value) => {
			if (selectedLabel && value !== selectedLabel) setSelectedLabel(null);
			list.setFilterText(value);
		},
		isLoading: list.isLoading,
		children: [/* @__PURE__ */ jsxs(SearchField$1, {
			className: "group flex flex-col gap-1",
			children: [/* @__PURE__ */ jsx(Label, {
				className: "text-sm font-medium text-(--text)",
				children: label
			}), /* @__PURE__ */ jsxs("div", {
				className: "relative",
				children: [
					/* @__PURE__ */ jsx(Input, {
						id: inputId,
						className: inputClassName ?? "w-full px-3 py-2 pr-16 border border-border-1 bg-card text-(--text) placeholder:text-(--text-subtle) rounded-lg focus:outline-none focus:ring-2 focus:ring-(--focus-ring)",
						placeholder
					}),
					list.isLoading && /* @__PURE__ */ jsx("div", {
						className: "absolute right-9 top-1/2 -translate-y-1/2 pointer-events-none",
						children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-4 w-4 border-2 border-(--primary) border-t-transparent rounded-full" })
					}),
					/* @__PURE__ */ jsx(Button, {
						"aria-label": "Clear search",
						onPress: clearInput,
						className: "absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded text-(--text-subtle) hover:text-(--text) focus:outline-none focus:ring-2 focus:ring-(--focus-ring) group-data-empty:hidden cursor-pointer",
						children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
					})
				]
			})]
		}), shouldShowList && /* @__PURE__ */ jsx(ListBox$1, {
			items: list.items,
			className: listClassName ?? "mt-1 bg-card border border-border-1 rounded-lg shadow-lg max-h-60 overflow-auto outline-none",
			selectionMode: "single",
			onSelectionChange: handleSelectionChange,
			renderEmptyState: () => /* @__PURE__ */ jsx("div", {
				className: "px-3 py-2 text-sm text-(--text-subtle)",
				children: belowMinChars && resolvedMinCharsMessage ? resolvedMinCharsMessage : emptyState
			}),
			children: (item) => {
				const typed = item;
				return /* @__PURE__ */ jsx(ListBoxItem$1, {
					id: getItemKey(typed),
					textValue: textValueOf(typed),
					className: "px-3 py-2 cursor-pointer outline-none rounded text-(--text) hover:bg-card-hover focus:bg-card-hover selected:bg-(--primary) selected:text-(--text-on-primary)",
					children: renderItem(typed)
				});
			}
		})]
	});
}
//#endregion
export { Lookup };

//# sourceMappingURL=Lookup.js.map