import { useMemo, type ReactNode, } from 'react' import type { VaultEntry } from '../types' import type { NoteReference } from '../utils/ai-context' import { buildTypeEntryMap } from '../utils/typeColors' import { deleteInlineSelection, replaceInlineSelection, } from './inlineWikilinkEdits' import { buildInlineWikilinkSegments, extractInlineWikilinkReferences, findActiveWikilinkQuery, } from './inlineWikilinkText' import { InlineWikilinkEditorField, InlineWikilinkPaletteLayout, InlineWikilinkSuggestionList, } from './InlineWikilinkParts' import { handleInlineWikilinkKeyDown } from './inlineWikilinkKeydown' import { useInlineWikilinkSelection } from './useInlineWikilinkSelection' import { useInlineWikilinkSuggestionsState } from './useInlineWikilinkSuggestionsState' import { normalizeInlineWikilinkValue } from './inlineWikilinkTokens' interface InlineWikilinkInputProps { entries: VaultEntry[] value: string onChange: (value: string) => void onSubmit?: (text: string, references: NoteReference[]) => void submitOnEmpty?: boolean disabled?: boolean placeholder?: string inputRef?: React.RefObject dataTestId?: string editorClassName?: string suggestionListVariant?: 'floating' | 'palette' suggestionEmptyLabel?: string paletteHeader?: ReactNode paletteEmptyState?: ReactNode paletteFooter?: ReactNode } function collapseSelectionRange(nextSelectionIndex: number) { return { start: nextSelectionIndex, end: nextSelectionIndex, } } function submitInlineValue({ onSubmit, submitOnEmpty, value, references, }: { onSubmit?: (text: string, references: NoteReference[]) => void submitOnEmpty: boolean value: string references: NoteReference[] }) { if (!onSubmit) return const normalizedValue = normalizeInlineWikilinkValue(value) if (!submitOnEmpty && !normalizedValue.trim()) return onSubmit(normalizedValue, references) } function renderInlineEditorField({ value, placeholder, disabled, inputRef, dataTestId, editorClassName, onInput, onKeyDown, onSelectionChange, segments, typeEntryMap, }: { value: string placeholder?: string disabled: boolean inputRef: React.Ref dataTestId: string editorClassName?: string onInput: () => void onKeyDown: (event: React.KeyboardEvent) => void onSelectionChange: () => void segments: ReturnType typeEntryMap: Record }) { return ( ) } function renderInlineSuggestionList({ suggestions, selectedSuggestionIndex, setSuggestionIndex, selectSuggestion, typeEntryMap, suggestionListVariant, suggestionEmptyLabel, }: { suggestions: ReturnType['suggestions'] selectedSuggestionIndex: number setSuggestionIndex: (index: number) => void selectSuggestion: (index: number) => void typeEntryMap: Record suggestionListVariant: 'floating' | 'palette' suggestionEmptyLabel: string }) { if (suggestions.length === 0) return null return ( ) } export function InlineWikilinkInput({ entries, value, onChange, onSubmit, submitOnEmpty = false, disabled = false, placeholder, inputRef, dataTestId = 'agent-input', editorClassName, suggestionListVariant = 'floating', suggestionEmptyLabel = 'No matching notes', paletteHeader, paletteEmptyState, paletteFooter, }: InlineWikilinkInputProps) { const segments = useMemo( () => buildInlineWikilinkSegments(value, entries), [entries, value], ) const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries]) const { selectionRange, selectionIndex, setSelectionRange, setCombinedRef, syncSelectionRange, commitValueFromEditor, focusSelectionRange, } = useInlineWikilinkSelection({ value, onChange, inputRef, }) const activeQuery = useMemo( () => selectionRange.start === selectionRange.end ? findActiveWikilinkQuery(value, selectionIndex) : null, [selectionIndex, selectionRange.end, selectionRange.start, value], ) const references = useMemo(() => extractInlineWikilinkReferences(value, entries), [entries, value]) const { suggestions, selectedSuggestionIndex, setSuggestionIndex, selectSuggestion, cycleSuggestions, } = useInlineWikilinkSuggestionsState({ activeQueryKey: activeQuery ? `${activeQuery.start}:${activeQuery.query}` : '', entries, query: activeQuery?.query ?? null, value, selectionIndex, onChange, onSelectionIndexChange: (nextSelectionIndex) => setSelectionRange(collapseSelectionRange(nextSelectionIndex)), focusSelectionAt: (nextSelectionIndex) => focusSelectionRange(collapseSelectionRange(nextSelectionIndex)), }) const insertText = (text: string) => { const nextState = replaceInlineSelection(value, selectionRange, text) onChange(nextState.value) setSelectionRange(nextState.selection) } const deleteContent = (direction: 'backward' | 'forward') => { const nextState = deleteInlineSelection(value, selectionRange, segments, direction) if (!nextState) return onChange(nextState.value) setSelectionRange(nextState.selection) } const submitValue = () => submitInlineValue({ onSubmit, submitOnEmpty, value, references }) const handleKeyDown = (event: React.KeyboardEvent) => handleInlineWikilinkKeyDown({ event, disabled, suggestionsOpen: suggestions.length > 0, onCycleSuggestions: cycleSuggestions, onSelectSuggestion: () => selectSuggestion(selectedSuggestionIndex), onDeleteContent: deleteContent, onInsertText: insertText, canSubmit: onSubmit !== undefined, onSubmit: submitValue, }) const editor = renderInlineEditorField({ value, placeholder, disabled, inputRef: setCombinedRef, dataTestId, editorClassName, onInput: commitValueFromEditor, onKeyDown: handleKeyDown, onSelectionChange: syncSelectionRange, segments, typeEntryMap, }) const suggestionList = renderInlineSuggestionList({ suggestions, selectedSuggestionIndex, setSuggestionIndex, selectSuggestion, typeEntryMap, suggestionListVariant, suggestionEmptyLabel, }) if (suggestionListVariant === 'palette') { return ( ) } return
{editor}{suggestionList}
}