import { useState, useRef, useEffect, KeyboardEvent } from 'react' import { Textarea } from '@/components/ui/textarea' import { makeRequest } from '@/lib/api' interface FileSearchResult { path: string name: string } interface ApiResponse { success: boolean data: T | null message: string | null } interface FileSearchTextareaProps { value: string onChange: (value: string) => void placeholder?: string rows?: number disabled?: boolean className?: string projectId?: string } export function FileSearchTextarea({ value, onChange, placeholder, rows = 3, disabled = false, className, projectId }: FileSearchTextareaProps) { const [searchQuery, setSearchQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [showDropdown, setShowDropdown] = useState(false) const [selectedIndex, setSelectedIndex] = useState(-1) const [atSymbolPosition, setAtSymbolPosition] = useState(-1) const [isLoading, setIsLoading] = useState(false) const textareaRef = useRef(null) const dropdownRef = useRef(null) // Search for files when query changes useEffect(() => { if (!searchQuery || !projectId || searchQuery.length < 1) { setSearchResults([]) setShowDropdown(false) return } const searchFiles = async () => { setIsLoading(true) try { const response = await makeRequest( `/api/projects/${projectId}/search?q=${encodeURIComponent(searchQuery)}` ) if (response.ok) { const result: ApiResponse = await response.json() if (result.success && result.data) { setSearchResults(result.data) setShowDropdown(true) setSelectedIndex(-1) } } } catch (error) { console.error('Failed to search files:', error) } finally { setIsLoading(false) } } const debounceTimer = setTimeout(searchFiles, 300) return () => clearTimeout(debounceTimer) }, [searchQuery, projectId]) // Handle text changes and detect @ symbol const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value const newCursorPosition = e.target.selectionStart || 0 onChange(newValue) // Check if @ was just typed const textBeforeCursor = newValue.slice(0, newCursorPosition) const lastAtIndex = textBeforeCursor.lastIndexOf('@') if (lastAtIndex !== -1) { // Check if there's no space after the @ (still typing the search query) const textAfterAt = textBeforeCursor.slice(lastAtIndex + 1) const hasSpace = textAfterAt.includes(' ') || textAfterAt.includes('\n') if (!hasSpace) { setAtSymbolPosition(lastAtIndex) setSearchQuery(textAfterAt) return } } // If no valid @ context, hide dropdown setShowDropdown(false) setSearchQuery('') setAtSymbolPosition(-1) } // Handle keyboard navigation const handleKeyDown = (e: KeyboardEvent) => { if (!showDropdown || searchResults.length === 0) return switch (e.key) { case 'ArrowDown': e.preventDefault() setSelectedIndex(prev => prev < searchResults.length - 1 ? prev + 1 : 0 ) break case 'ArrowUp': e.preventDefault() setSelectedIndex(prev => prev > 0 ? prev - 1 : searchResults.length - 1 ) break case 'Enter': if (selectedIndex >= 0) { e.preventDefault() selectFile(searchResults[selectedIndex]) } break case 'Escape': e.preventDefault() setShowDropdown(false) setSearchQuery('') setAtSymbolPosition(-1) break } } // Select a file and insert it into the text const selectFile = (file: FileSearchResult) => { if (atSymbolPosition === -1) return const beforeAt = value.slice(0, atSymbolPosition) const afterQuery = value.slice(atSymbolPosition + 1 + searchQuery.length) const newValue = beforeAt + file.path + afterQuery onChange(newValue) setShowDropdown(false) setSearchQuery('') setAtSymbolPosition(-1) // Focus back to textarea setTimeout(() => { if (textareaRef.current) { const newCursorPos = atSymbolPosition + file.path.length textareaRef.current.focus() textareaRef.current.setSelectionRange(newCursorPos, newCursorPos) } }, 0) } // Calculate dropdown position const getDropdownPosition = () => { if (!textareaRef.current || atSymbolPosition === -1) return { top: 0, left: 0 } const textBeforeAt = value.slice(0, atSymbolPosition) const lines = textBeforeAt.split('\n') const currentLine = lines.length - 1 const charInLine = lines[lines.length - 1].length // Rough calculation - this is an approximation const lineHeight = 20 const charWidth = 8 const top = (currentLine + 1) * lineHeight + 10 const left = charWidth * charInLine return { top, left } } const dropdownPosition = getDropdownPosition() return (