'use client' import { useMemo } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { rankApiContacts } from '@/lib/contacts/contact-match-score' import { apiClient, OfflineError } from '../client' import type { ApiContact, ApiContactSyncResponse } from '../types' export const FALLBACK_CONTACT_BOOK_ID = 'contacts' type ApiContactBook = { id: string; name: string } type ApiContactsListResponse = { contacts: ApiContact[] pagination?: { page: number page_size: number total?: number } } export function normalizeContactBooksResponse(booksRaw: unknown): ApiContactBook[] { if (Array.isArray(booksRaw)) return booksRaw as ApiContactBook[] if (booksRaw && typeof booksRaw === 'object' && 'address_books' in booksRaw) { return (booksRaw as { address_books: ApiContactBook[] }).address_books ?? [] } return [] } export async function fetchContactByPath(path: string): Promise { const apiPath = path.replace(/^\/+/, '') return apiClient.get(`/contacts/${apiPath}`) } export async function fetchContactsForBook(bookId: string): Promise { const pageSize = 500 let page = 1 const all: ApiContact[] = [] while (page <= 100) { const res = await apiClient.get( `/contacts/books/${bookId}`, { page: String(page), page_size: String(pageSize) }, ) const batch = Array.isArray(res) ? res : (res.contacts ?? []) all.push(...batch) if (batch.length < pageSize) break const total = !Array.isArray(res) ? res.pagination?.total : undefined if (total != null && all.length >= total) break page += 1 } return all } export function useDefaultContactBookId() { const { data: booksRaw, isLoading, isError } = useContactBooks() return useMemo(() => { if (isLoading || isError) return undefined const books = normalizeContactBooksResponse(booksRaw) return books[0]?.id ?? FALLBACK_CONTACT_BOOK_ID }, [booksRaw, isLoading, isError]) } export function useContacts(bookId?: string) { const defaultBookId = useDefaultContactBookId() const resolvedBookId = bookId ?? defaultBookId return useQuery({ queryKey: ['contacts', resolvedBookId], queryFn: () => fetchContactsForBook(resolvedBookId!), enabled: !!resolvedBookId, staleTime: 5 * 60_000, }) } export function useContactBooks() { return useQuery({ queryKey: ['contact-books'], queryFn: async () => { const res = await apiClient.get( '/contacts/books', ) return normalizeContactBooksResponse(res) }, staleTime: 10 * 60_000, }) } export function useSyncContacts(bookId?: string, syncToken?: string) { return useQuery({ queryKey: ['contacts-sync', bookId, syncToken], queryFn: () => apiClient.get(`/contacts/books/${bookId}/sync`, { sync_token: syncToken, }), enabled: !!bookId && !!syncToken, }) } export function useSearchContacts(query: string) { const queryClient = useQueryClient() return useQuery({ queryKey: ['contacts-search', query], queryFn: async () => { try { const res = await apiClient.get( '/contacts/search', { q: query }, ) const list = Array.isArray(res) ? res : (res.contacts ?? []) return rankApiContacts(list, query) } catch (err) { if (err instanceof OfflineError) { const cached = queryClient.getQueriesData({ queryKey: ['contacts'], }) const allContacts: ApiContact[] = [] for (const [, data] of cached) { if (data) allContacts.push(...data) } return rankApiContacts(allContacts, query) } throw err } }, enabled: query.length >= 2, staleTime: 30_000, }) } export function useContactInteractions(email?: string) { return useQuery({ queryKey: ['contact-interactions', email], queryFn: () => apiClient.get('/contacts/interactions', { email }), enabled: !!email, }) }