import { CaretUpDown } from '@phosphor-icons/react' import { useEffect, useId, useMemo, useRef, useState, type ChangeEvent, type FocusEvent, type KeyboardEvent, type RefObject } from 'react' import { Input } from '@/components/ui/input' import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' import { FilterFieldOptionsList } from './filter-builder/FilterFieldOptionsList' const CONTENT_FIELDS = new Set(['body']) interface FilterFieldComboboxProps { value: string fields: string[] onChange: (value: string) => void } interface FieldGroup { key: 'property' | 'content' label: string options: string[] } function normalizeFieldQuery(query: string): string { return query.trim().toLowerCase() } function buildFieldGroups(fields: string[], currentValue: string, query: string): FieldGroup[] { const allFields = currentValue !== '' && !fields.includes(currentValue) ? [currentValue, ...fields] : fields const normalized = normalizeFieldQuery(query) const matches = (field: string) => normalized === '' || field.toLowerCase().includes(normalized) const propertyOptions = allFields.filter((field) => !CONTENT_FIELDS.has(field) && matches(field)) const contentOptions = allFields.filter((field) => CONTENT_FIELDS.has(field) && matches(field)) const groups: FieldGroup[] = [] if (propertyOptions.length > 0) groups.push({ key: 'property', label: 'Properties', options: propertyOptions }) if (contentOptions.length > 0) groups.push({ key: 'content', label: 'Content', options: contentOptions }) return groups } function flattenGroups(groups: FieldGroup[]): string[] { return groups.flatMap((group) => group.options) } function initialHighlightIndex(options: string[], currentValue: string): number { if (options.length === 0) return -1 const currentIndex = options.indexOf(currentValue) return currentIndex >= 0 ? currentIndex : 0 } function stepHighlightedIndex(current: number, optionCount: number, direction: 'next' | 'previous'): number { if (current < 0) return direction === 'next' ? 0 : optionCount - 1 if (direction === 'next') return (current + 1) % optionCount return (current - 1 + optionCount) % optionCount } function moveHighlightedOption({ event, open, options, direction, openCombobox, setHighlightedIndex, }: { event: KeyboardEvent open: boolean options: string[] direction: 'next' | 'previous' openCombobox: () => void setHighlightedIndex: (updater: number | ((current: number) => number)) => void }) { event.preventDefault() if (!open) { openCombobox() return } if (options.length === 0) return setHighlightedIndex((current) => stepHighlightedIndex(current, options.length, direction)) } function selectHighlightedOption({ event, open, options, highlightedIndex, selectOption, }: { event: KeyboardEvent open: boolean options: string[] highlightedIndex: number selectOption: (value: string) => void }) { if (!open || highlightedIndex < 0 || options[highlightedIndex] === undefined) return event.preventDefault() selectOption(options[highlightedIndex]) } function closeOpenCombobox({ event, open, closeCombobox, }: { event: KeyboardEvent open: boolean closeCombobox: () => void }) { if (!open) return event.preventDefault() closeCombobox() } function handleFilterFieldKeyDown({ event, open, options, highlightedIndex, openCombobox, setHighlightedIndex, selectOption, closeCombobox, }: { event: KeyboardEvent open: boolean options: string[] highlightedIndex: number openCombobox: () => void setHighlightedIndex: (updater: number | ((current: number) => number)) => void selectOption: (value: string) => void closeCombobox: () => void }) { switch (event.key) { case 'ArrowDown': moveHighlightedOption({ event, open, options, direction: 'next', openCombobox, setHighlightedIndex }) return case 'ArrowUp': moveHighlightedOption({ event, open, options, direction: 'previous', openCombobox, setHighlightedIndex }) return case 'Enter': selectHighlightedOption({ event, open, options, highlightedIndex, selectOption }) return case 'Escape': closeOpenCombobox({ event, open, closeCombobox }) return default: return } } function FilterFieldInput({ inputRef, open, query, value, listboxId, highlightedIndex, onFocus, onChange, onKeyDown, }: { inputRef: RefObject open: boolean query: string value: string listboxId: string highlightedIndex: number onFocus: () => void onChange: (event: ChangeEvent) => void onKeyDown: (event: KeyboardEvent) => void }) { return ( <> = 0 ? `${listboxId}-option-${highlightedIndex}` : undefined} className="h-8 pr-7 text-sm" data-testid="filter-field-combobox-input" />