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<SearchResponseDTO, Error, SearchResult>({
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<SearchResponseDTO>(`/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,
}
}