import { useInfiniteQuery } from '@tanstack/react-query' import { apiFetch } from '../../api/client' import { adaptSearchRow } from '../../api/adapters' import type { SearchRow, SearchRowDTO } from '../../api/adapters' interface SearchResponseDTO { results: SearchRowDTO[] limit: number next_cursor?: string } export interface SearchResult { results: SearchRow[] limit: number nextCursor?: string } export interface SearchFilters { q: string tool?: string host?: string } interface UseSearchReturn { results: SearchRow[] fetchNextPage: () => void hasNextPage: boolean isFetchingNextPage: boolean isLoading: boolean error: Error | null } /** * Hook for cursor-paginated full-text search against the search API. * * - Query key includes filters so changing any filter triggers a fresh fetch. * - Empty `q` short-circuits to an empty result set without issuing a request. * - Cursor pagination appends results (never replaces) when fetchNextPage is called. * * Respects: IV3 (cursor pagination), AS1 (cursor pagination is stable). */ export function useSearch(filters: SearchFilters, enabled: boolean): UseSearchReturn { const query = useInfiniteQuery({ queryKey: ['search', filters], queryFn: async ({ pageParam }) => { const params = new URLSearchParams() params.set('q', filters.q) if (filters.tool) params.set('tool', filters.tool) if (filters.host) params.set('host', filters.host) if (pageParam) params.set('cursor', pageParam as string) return apiFetch(`/api/v1/search?${params.toString()}`) }, initialPageParam: undefined as string | undefined, getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined, enabled: enabled && filters.q.length > 0, staleTime: 0, select: (data) => ({ pages: data.pages, pageParams: data.pageParams, results: data.pages.flatMap(p => p.results.map(adaptSearchRow)), limit: data.pages[data.pages.length - 1]?.limit ?? 0, nextCursor: data.pages[data.pages.length - 1]?.next_cursor, }), }) return { results: query.data?.results ?? [], fetchNextPage: query.fetchNextPage, hasNextPage: query.hasNextPage ?? false, isFetchingNextPage: query.isFetchingNextPage, isLoading: query.isLoading, error: query.error, } }