"use client"; import useDebounce from "@/hooks/useDebounce"; import type { ILabelValue } from "@/types/shared"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; export type SelectSearchMode = "client" | "server"; export type UseSelectServerSearchArgs = { /** When `"server"`, options are loaded via `searchFn` + React Query. */ searchMode: SelectSearchMode; searchable: boolean; /** Whether the dropdown panel is open (queries run only while open). */ open: boolean; /** Live search input (debounced before the request). */ query: string; debounceMs?: number; /** Base React Query key; debounced search term is appended. */ searchQueryKey?: readonly unknown[]; searchFn?: (query: string) => Promise; /** Fallback / initial options (e.g. first page or selected value). */ items: readonly ILabelValue[]; /** Current selected value — kept visible if missing from server results. */ selectedValue?: string; }; export function useSelectServerSearch({ searchMode, searchable, open, query, debounceMs = 300, searchQueryKey, searchFn, items, selectedValue, }: UseSelectServerSearchArgs) { const isServerMode = searchable && searchMode === "server" && !!searchFn; const debouncedQuery = useDebounce(query, debounceMs); const queryKey = useMemo( () => [...(searchQueryKey ?? ["select-search"]), debouncedQuery], [searchQueryKey, debouncedQuery], ); const { data: serverItems, isFetching, isPending, isError, error, } = useQuery({ queryKey, queryFn: () => searchFn!(debouncedQuery), enabled: isServerMode && open, placeholderData: keepPreviousData, staleTime: 30_000, }); const displayItems = useMemo(() => { if (!isServerMode) return null; const list = serverItems ?? (debouncedQuery.trim() ? [] : [...items]); if (!selectedValue) return list; const hasSelected = list.some( (i) => String(i.value) === String(selectedValue), ); if (hasSelected) return list; const fromItems = items.find( (i) => String(i.value) === String(selectedValue), ); if (fromItems) return [fromItems, ...list]; return [{ value: selectedValue, label: String(selectedValue) }, ...list]; }, [ isServerMode, serverItems, debouncedQuery, items, selectedValue, ]); const isSearchDebouncing = isServerMode && open && query.trim() !== debouncedQuery.trim(); return { isServerMode, displayItems, isSearchLoading: isServerMode && open && (isSearchDebouncing || isFetching || isPending), isSearchDebouncing, isSearchError: isServerMode && open && isError, searchError: error, }; }