'use client'; import { useState, useCallback, useRef, useEffect } from 'react'; export interface SearchResult { id: string; type: 'image' | 'video' | 'link' | 'note'; title: string; description: string; url: string; tags: string[]; source: string; dateAdded: string; collection: string; thumbnail: string; } export interface SearchResponse { results: SearchResult[]; total: number; query: string; searchTime?: string; message?: string; } export interface SearchState { query: string; results: SearchResult[]; isLoading: boolean; error: string | null; hasSearched: boolean; total: number; searchTime: string | null; } export interface SearchOptions { type?: 'image' | 'video' | 'link' | 'note' | 'all'; limit?: number; } export function useSearch() { const [state, setState] = useState({ query: '', results: [], isLoading: false, error: null, hasSearched: false, total: 0, searchTime: null, }); const debounceTimeoutRef = useRef(); const abortControllerRef = useRef(); const search = useCallback(async ( query: string, options: SearchOptions = {} ) => { // Clear previous timeout if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } // Cancel previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } // Create new abort controller abortControllerRef.current = new AbortController(); // Debounce search to avoid too many API calls return new Promise((resolve, reject) => { debounceTimeoutRef.current = setTimeout(async () => { if (!query.trim()) { setState(prev => ({ ...prev, query: '', results: [], isLoading: false, error: null, hasSearched: false, total: 0, searchTime: null, })); resolve({ results: [], total: 0, query: '' }); return; } setState(prev => ({ ...prev, query: query.trim(), isLoading: true, error: null, })); try { const searchParams = new URLSearchParams({ q: query.trim(), type: options.type || 'all', limit: (options.limit || 20).toString(), }); const response = await fetch(`/api/search?${searchParams}`, { signal: abortControllerRef.current?.signal, }); if (!response.ok) { throw new Error(`Search failed: ${response.statusText}`); } const data: SearchResponse = await response.json(); setState(prev => ({ ...prev, results: data.results, isLoading: false, hasSearched: true, total: data.total, searchTime: data.searchTime || null, error: null, })); resolve(data); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { // Request was cancelled, don't update state return; } const errorMessage = error instanceof Error ? error.message : 'Search failed'; setState(prev => ({ ...prev, isLoading: false, error: errorMessage, })); reject(error); } }, 300); // 300ms debounce }); }, []); // Clear search results const clearSearch = useCallback(() => { setState({ query: '', results: [], isLoading: false, error: null, hasSearched: false, total: 0, searchTime: null, }); }, []); // Cleanup on unmount useEffect(() => { return () => { if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); return { ...state, search, clearSearch, }; }