'use client'

import { useMemo } from 'react'
import {
  useInfiniteQuery,
  useMutation,
  useQuery,
  useQueryClient,
  type InfiniteData,
  type QueryClient,
} from '@tanstack/react-query'
import { appendContactToBookCache } from '../contact-book-cache'
import { apiClient, ApiRequestError, OfflineError } from '../client'
import { enqueue } from '../offline-queue'
import type { ApiContact } from '../types'
import type {
  ApiDiscoveryCounts,
  ApiDiscoveryScan,
  ApiDiscoveredProfile,
  ApiDiscoveredProfileGroup,
  ApiDispositionEmails,
  ApiEnrichmentSuggestion,
  ApiLLMModelsResponse,
  ApiLLMSettings,
  ApiSearchSettings,
} from '@/lib/contacts/discovery-types'
import {
  coerceDiscoveryGroups,
  dedupeDiscoveryGroups,
  discoveryGroupReactKey,
  parseDiscoveryOtherPage,
  parseDiscoveryOtherResponse,
  type DiscoveryOtherPageResult,
} from '@/lib/contacts/discovery-grouping'
import { filterVisibleEnrichmentSuggestions } from '@/lib/contacts/discovery-utils'
import { useContactsList } from '@/lib/contacts/use-contacts-list'

const ACTIVE_SCAN_STORAGE_KEY = 'ultimail-contact-discovery-scan-id'

export function getPersistedScanId(): string | null {
  if (typeof window === 'undefined') return null
  return localStorage.getItem(ACTIVE_SCAN_STORAGE_KEY)
}

export function persistScanId(scanId: string | null) {
  if (typeof window === 'undefined') return
  if (scanId) {
    localStorage.setItem(ACTIVE_SCAN_STORAGE_KEY, scanId)
  } else {
    localStorage.removeItem(ACTIVE_SCAN_STORAGE_KEY)
  }
}

export function useDiscoveryCounts() {
  return useQuery({
    queryKey: ['contact-discovery-counts'],
    queryFn: () => apiClient.get<ApiDiscoveryCounts>('/contacts/discovery/counts'),
    staleTime: 30_000,
  })
}

export const OTHER_CONTACTS_PAGE_SIZE = 12

const OTHER_CONTACTS_QUERY_KEY = ['contact-discovery-other', 'groups', 'infinite'] as const

function otherContactsQueryKey(searchQuery: string) {
  const q = searchQuery.trim()
  return q ? ([...OTHER_CONTACTS_QUERY_KEY, q] as const) : OTHER_CONTACTS_QUERY_KEY
}

/** Cache session si l'API renvoie encore toute la liste d'un coup (legacy). */
let legacyOtherContactsFullList: ApiDiscoveredProfileGroup[] | null = null

function sliceLegacyOtherContactsPage(offset: number): DiscoveryOtherPageResult | null {
  if (!legacyOtherContactsFullList?.length) return null
  const groups = legacyOtherContactsFullList.slice(offset, offset + OTHER_CONTACTS_PAGE_SIZE)
  return {
    groups,
    total: legacyOtherContactsFullList.length,
    hasMore: offset + OTHER_CONTACTS_PAGE_SIZE < legacyOtherContactsFullList.length,
  }
}

export function flattenOtherContactPages(
  data: InfiniteData<DiscoveryOtherPageResult> | undefined,
): ApiDiscoveredProfileGroup[] {
  if (!data?.pages?.length) return []
  return dedupeDiscoveryGroups(data.pages.flatMap((page) => page.groups ?? []))
}

function dedupeOtherContactsInfiniteCache(
  data: InfiniteData<DiscoveryOtherPageResult> | undefined,
): InfiniteData<DiscoveryOtherPageResult> | undefined {
  if (!data?.pages?.length) return data
  const seen = new Set<string>()
  const pages = data.pages.map((page) => ({
    ...page,
    groups: dedupeDiscoveryGroups(page.groups ?? []).filter((group) => {
      const key = discoveryGroupReactKey(group)
      if (seen.has(key)) return false
      seen.add(key)
      return true
    }),
  }))
  return { ...data, pages }
}

export function useOtherDiscoveredContacts(enabled = true, searchQuery = '') {
  const trimmedSearch = searchQuery.trim()

  return useInfiniteQuery({
    queryKey: otherContactsQueryKey(trimmedSearch),
    queryFn: async ({ pageParam }) => {
      const offset = typeof pageParam === 'number' ? pageParam : 0
      const cachedLegacy = offset > 0 && !trimmedSearch ? sliceLegacyOtherContactsPage(offset) : null
      if (cachedLegacy) {
        return {
          groups: coerceDiscoveryGroups(cachedLegacy.groups),
          total: cachedLegacy.total,
          hasMore: cachedLegacy.hasMore,
        }
      }

      const res = await apiClient.get<{
        groups?: ApiDiscoveredProfileGroup[]
        profiles?: ApiDiscoveredProfile[]
        total?: number
        has_more?: boolean
        limit?: number
        offset?: number
      }>('/contacts/discovery/other', {
        limit: String(OTHER_CONTACTS_PAGE_SIZE),
        offset: String(offset),
        ...(trimmedSearch ? { q: trimmedSearch } : {}),
      })
      const allGroups = coerceDiscoveryGroups(parseDiscoveryOtherResponse(res))
      const serverPaginated =
        ('has_more' in res && res.has_more != null) || typeof res.total === 'number'

      if (!serverPaginated && allGroups.length > OTHER_CONTACTS_PAGE_SIZE) {
        legacyOtherContactsFullList = allGroups
        const groups = allGroups.slice(offset, offset + OTHER_CONTACTS_PAGE_SIZE)
        return {
          groups,
          total: allGroups.length,
          hasMore: offset + OTHER_CONTACTS_PAGE_SIZE < allGroups.length,
        }
      }

      legacyOtherContactsFullList = null
      const page = parseDiscoveryOtherPage(res, {
        offset,
        pageSize: OTHER_CONTACTS_PAGE_SIZE,
      })
      return {
        groups: coerceDiscoveryGroups(page.groups ?? []),
        total: page.total,
        hasMore: page.hasMore,
      }
    },
    initialPageParam: 0,
    getNextPageParam: (lastPage, allPages) => {
      if (!lastPage?.hasMore) return undefined
      return allPages.reduce((sum, p) => sum + (p.groups?.length ?? 0), 0)
    },
    staleTime: 30_000,
    enabled,
    refetchInterval: (query) => {
      const groups = flattenOtherContactPages(query.state.data)
      return groups.some((g) => g.profile?.enrichment_status === 'enriching') ? 2000 : false
    },
  })
}

export function useIgnoredDiscoveredContacts() {
  return useQuery({
    queryKey: ['contact-discovery-ignored'],
    queryFn: async () => {
      const res = await apiClient.get<{ profiles: ApiDiscoveredProfile[] }>(
        '/contacts/discovery/ignored',
      )
      return res.profiles ?? []
    },
    staleTime: 30_000,
  })
}

export function useBlockedDiscoveredContacts() {
  return useQuery({
    queryKey: ['contact-discovery-blocked'],
    queryFn: async () => {
      const res = await apiClient.get<{ profiles: ApiDiscoveredProfile[] }>(
        '/contacts/discovery/blocked',
      )
      return res.profiles ?? []
    },
    staleTime: 30_000,
  })
}

export function useEnrichmentSuggestions(type?: 'enrich' | 'all') {
  return useQuery({
    queryKey: ['contact-discovery-suggestions', type],
    queryFn: async () => {
      const res = await apiClient.get<{ suggestions: ApiEnrichmentSuggestion[] }>(
        '/contacts/discovery/suggestions',
        type === 'enrich' ? { type: 'enrich' } : undefined,
      )
      return res.suggestions ?? []
    },
    staleTime: 30_000,
  })
}

/** Enrichment suggestions for existing contacts, excluding values already on the contact. */
export function useVisibleEnrichmentSuggestions() {
  const { contacts } = useContactsList()
  const query = useEnrichmentSuggestions('enrich')
  const suggestions = useMemo(
    () => filterVisibleEnrichmentSuggestions(query.data ?? [], contacts),
    [query.data, contacts],
  )
  return { ...query, suggestions }
}

export function useActiveDiscoveryScan() {
  return useQuery({
    queryKey: ['contact-discovery-scan-active'],
    queryFn: async () => {
      const res = await apiClient.get<{ scan: ApiDiscoveryScan | null }>(
        '/contacts/discovery/scan/active',
      )
      const scan = res.scan ?? null
      if (scan && (scan.status === 'running' || scan.status === 'pending')) {
        persistScanId(scan.id)
      } else if (!scan) {
        persistScanId(null)
      }
      return scan
    },
    refetchInterval: (query) => {
      const scan = query.state.data
      if (scan && (scan.status === 'running' || scan.status === 'pending')) {
        return 2000
      }
      return false
    },
    staleTime: 0,
  })
}

export function useDiscoveryScan(scanId?: string) {
  return useQuery({
    queryKey: ['contact-discovery-scan', scanId],
    queryFn: () => apiClient.get<ApiDiscoveryScan>(`/contacts/discovery/scan/${scanId}`),
    enabled: !!scanId,
    refetchInterval: (query) => {
      const status = query.state.data?.status
      return status === 'running' || status === 'pending' ? 2000 : false
    },
  })
}

export function useStartDiscoveryScan() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: async (bookId?: string) => {
      const path = bookId
        ? `/contacts/discovery/scan?book_id=${encodeURIComponent(bookId)}`
        : '/contacts/discovery/scan'
      return apiClient.post<ApiDiscoveryScan>(path)
    },
    onSuccess: (scan) => {
      persistScanId(scan.id)
      queryClient.setQueryData(['contact-discovery-scan-active'], scan)
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-scan', scan.id] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-counts'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-other'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-ignored'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-blocked'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-suggestions'] })
    },
  })
}

export function useCancelDiscoveryScan() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: () => apiClient.post<void>('/contacts/discovery/scan/cancel'),
    onSuccess: () => {
      persistScanId(null)
      queryClient.setQueryData(['contact-discovery-scan-active'], null)
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-scan-active'] })
    },
  })
}

function groupMatchesProfileId(group: ApiDiscoveredProfileGroup, profileId: string): boolean {
  if (group.profile?.id === profileId) return true
  if (group.profile_ids?.includes(profileId)) return true
  return group.profiles?.some((p) => p.id === profileId) ?? false
}

function findOtherContactGroupInCache(
  queryClient: QueryClient,
  profileId: string,
): ApiDiscoveredProfileGroup | undefined {
  const data = queryClient.getQueryData<InfiniteData<DiscoveryOtherPageResult>>(
    OTHER_CONTACTS_QUERY_KEY,
  )
  if (!data?.pages?.length) return undefined
  for (const page of data.pages) {
    for (const group of page.groups ?? []) {
      if (groupMatchesProfileId(group, profileId)) return group
    }
  }
  return undefined
}

/** Emails liés à un groupe découvert (pour blocage optimiste côté client). */
export function extractDiscoveryGroupEmails(group: ApiDiscoveredProfileGroup): string[] {
  const emails = new Set<string>()
  if (group.primary_email) emails.add(group.primary_email.toLowerCase())
  for (const profile of group.profiles ?? (group.profile ? [group.profile] : [])) {
    if (profile.primary_email) emails.add(profile.primary_email.toLowerCase())
    for (const entry of profile.all_emails ?? []) {
      if (entry.email) emails.add(entry.email.toLowerCase())
    }
  }
  return [...emails]
}

export function removeOtherContactGroupFromCache(
  queryClient: QueryClient,
  profileId: string,
): boolean {
  let removed = false
  queryClient.setQueryData<InfiniteData<DiscoveryOtherPageResult>>(
    OTHER_CONTACTS_QUERY_KEY,
    (old) => {
      if (!old) return old
      let didRemove = false
      const pages = old.pages.map((page) => {
        const groups = (page.groups ?? []).filter((group) => {
          if (groupMatchesProfileId(group, profileId)) {
            didRemove = true
            return false
          }
          return true
        })
        return { ...page, groups }
      })
      if (didRemove && pages[0]) {
        removed = true
        pages[0] = {
          ...pages[0],
          total: Math.max(0, pages[0].total - 1),
        }
      }
      return { ...old, pages }
    },
  )
  if (removed) {
    if (legacyOtherContactsFullList) {
      legacyOtherContactsFullList = legacyOtherContactsFullList.filter(
        (group) => !groupMatchesProfileId(group, profileId),
      )
    }
    queryClient.setQueryData<ApiDiscoveryCounts>(['contact-discovery-counts'], (old) => {
      if (!old) return old
      return {
        ...old,
        other_contacts: Math.max(0, old.other_contacts - 1),
      }
    })
  }
  return removed
}

interface OptimisticDispositionContext {
  previous: InfiniteData<DiscoveryOtherPageResult> | undefined
  previousCounts: ApiDiscoveryCounts | undefined
  previousIgnored: ApiDiscoveredProfile[] | undefined
  previousBlocked: ApiDiscoveredProfile[] | undefined
  removedGroup: ApiDiscoveredProfileGroup | undefined
}

function rollbackDispositionContext(
  queryClient: QueryClient,
  context: OptimisticDispositionContext | undefined,
) {
  if (!context) return
  if (context.previous) {
    queryClient.setQueryData(OTHER_CONTACTS_QUERY_KEY, context.previous)
  }
  if (context.previousCounts) {
    queryClient.setQueryData(['contact-discovery-counts'], context.previousCounts)
  }
  if (context.previousIgnored) {
    queryClient.setQueryData(['contact-discovery-ignored'], context.previousIgnored)
  }
  if (context.previousBlocked) {
    queryClient.setQueryData(['contact-discovery-blocked'], context.previousBlocked)
  }
}

function optimisticRemoveFromDispositionLists(
  queryClient: QueryClient,
  profileId: string,
): Pick<
  OptimisticDispositionContext,
  'previousIgnored' | 'previousBlocked' | 'previousCounts'
> {
  const previousIgnored = queryClient.getQueryData<ApiDiscoveredProfile[]>([
    'contact-discovery-ignored',
  ])
  const previousBlocked = queryClient.getQueryData<ApiDiscoveredProfile[]>([
    'contact-discovery-blocked',
  ])
  const previousCounts = queryClient.getQueryData<ApiDiscoveryCounts>([
    'contact-discovery-counts',
  ])

  let removedIgnored = false
  let removedBlocked = false

  queryClient.setQueryData<ApiDiscoveredProfile[]>(['contact-discovery-ignored'], (old) => {
    if (!old) return old
    const next = old.filter((p) => p.id !== profileId)
    removedIgnored = next.length !== old.length
    return next
  })
  queryClient.setQueryData<ApiDiscoveredProfile[]>(['contact-discovery-blocked'], (old) => {
    if (!old) return old
    const next = old.filter((p) => p.id !== profileId)
    removedBlocked = next.length !== old.length
    return next
  })

  if (removedIgnored || removedBlocked) {
    queryClient.setQueryData<ApiDiscoveryCounts>(['contact-discovery-counts'], (old) => {
      if (!old) return old
      return {
        ...old,
        ignored: removedIgnored ? Math.max(0, old.ignored - 1) : old.ignored,
        blocked: removedBlocked ? Math.max(0, old.blocked - 1) : old.blocked,
      }
    })
  }

  return { previousIgnored, previousBlocked, previousCounts }
}

function optimisticRemoveOtherContact(
  queryClient: QueryClient,
  profileId: string,
): OptimisticDispositionContext {
  const previous = queryClient.getQueryData<InfiniteData<DiscoveryOtherPageResult>>(
    OTHER_CONTACTS_QUERY_KEY,
  )
  const previousCounts = queryClient.getQueryData<ApiDiscoveryCounts>([
    'contact-discovery-counts',
  ])
  const previousIgnored = queryClient.getQueryData<ApiDiscoveredProfile[]>([
    'contact-discovery-ignored',
  ])
  const previousBlocked = queryClient.getQueryData<ApiDiscoveredProfile[]>([
    'contact-discovery-blocked',
  ])
  const removedGroup = findOtherContactGroupInCache(queryClient, profileId)
  removeOtherContactGroupFromCache(queryClient, profileId)
  void queryClient.cancelQueries({ queryKey: OTHER_CONTACTS_QUERY_KEY })
  return {
    previous,
    previousCounts,
    previousIgnored,
    previousBlocked,
    removedGroup,
  }
}

function prependIgnoredProfile(
  queryClient: QueryClient,
  group: ApiDiscoveredProfileGroup | undefined,
) {
  const profile = group?.profile ?? group?.profiles?.[0]
  if (!profile) return
  queryClient.setQueryData<ApiDiscoveredProfile[]>(['contact-discovery-ignored'], (old) => {
    const list = old ?? []
    if (list.some((p) => p.id === profile.id)) return list
    return [{ ...profile, status: 'ignored' as const }, ...list]
  })
  queryClient.setQueryData<ApiDiscoveryCounts>(['contact-discovery-counts'], (old) => {
    if (!old) return old
    return { ...old, ignored: old.ignored + 1 }
  })
}

function prependBlockedProfile(
  queryClient: QueryClient,
  group: ApiDiscoveredProfileGroup | undefined,
) {
  const profile = group?.profile ?? group?.profiles?.[0]
  if (!profile) return
  queryClient.setQueryData<ApiDiscoveredProfile[]>(['contact-discovery-blocked'], (old) => {
    const list = old ?? []
    if (list.some((p) => p.id === profile.id)) return list
    return [{ ...profile, status: 'blocked' as const }, ...list]
  })
  queryClient.setQueryData<ApiDiscoveryCounts>(['contact-discovery-counts'], (old) => {
    if (!old) return old
    return { ...old, blocked: old.blocked + 1 }
  })
}

export function useAcceptDiscoveredProfile() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (vars: { profileId: string; contactUid?: string }) =>
      apiClient.post<void>(`/contacts/discovery/profiles/${vars.profileId}/accept`, {
        contact_uid: vars.contactUid ?? '',
      }),
    onMutate: (vars) => {
      return optimisticRemoveOtherContact(queryClient, vars.profileId)
    },
    onError: (_err, _vars, context) => {
      rollbackDispositionContext(queryClient, context)
    },
  })
}

/** Crée le contact NC puis marque le profil découvert comme accepté (retrait optimiste immédiat). */
export function useAddDiscoveredContact() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: async (vars: {
      bookId: string
      profileId: string
      contact: Partial<ApiContact>
    }) => {
      try {
        const created = await apiClient.post<ApiContact>(
          `/contacts/discovery/profiles/${vars.profileId}/add-to-book`,
          { book_id: vars.bookId, contact: vars.contact },
        )
        return { created, contactUid: created.uid }
      } catch (err) {
        if (!(err instanceof ApiRequestError) || (err.status !== 404 && err.status !== 405)) {
          throw err
        }
        const created = await apiClient.post<ApiContact | undefined>(
          `/contacts/books/${vars.bookId}`,
          vars.contact,
        )
        const contactUid = created?.uid ?? vars.contact.uid ?? ''
        await apiClient.post<void>(`/contacts/discovery/profiles/${vars.profileId}/accept`, {
          contact_uid: contactUid,
        })
        const merged: ApiContact = {
          ...(vars.contact as ApiContact),
          ...(created ?? {}),
          uid: contactUid,
        }
        return { created: merged, contactUid }
      }
    },
    onMutate: (vars) => {
      return optimisticRemoveOtherContact(queryClient, vars.profileId)
    },
    onSuccess: (data, vars) => {
      if (data.created.uid) {
        appendContactToBookCache(queryClient, vars.bookId, data.created)
      }
    },
    onError: (err, _vars, context) => {
      rollbackDispositionContext(queryClient, context)
      if (err instanceof OfflineError) {
        enqueue({
          id: crypto.randomUUID(),
          timestamp: Date.now(),
          type: 'create_contact',
          payload: { bookId: _vars.bookId, ..._vars.contact },
          retries: 0,
        })
      }
    },
  })
}

export function useRejectDiscoveredProfile() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (profileId: string) =>
      apiClient.post<void>(`/contacts/discovery/profiles/${profileId}/reject`),
    onMutate: (profileId) => {
      const dispositionContext = optimisticRemoveFromDispositionLists(queryClient, profileId)
      void queryClient.cancelQueries({ queryKey: ['contact-discovery-ignored'] })
      void queryClient.cancelQueries({ queryKey: ['contact-discovery-blocked'] })
      return dispositionContext
    },
    onError: (_err, _profileId, context) => {
      if (!context) return
      if (context.previousIgnored) {
        queryClient.setQueryData(['contact-discovery-ignored'], context.previousIgnored)
      }
      if (context.previousBlocked) {
        queryClient.setQueryData(['contact-discovery-blocked'], context.previousBlocked)
      }
      if (context.previousCounts) {
        queryClient.setQueryData(['contact-discovery-counts'], context.previousCounts)
      }
    },
  })
}

export function useIgnoreDiscoveredProfile() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (profileId: string) =>
      apiClient.post<ApiDispositionEmails>(
        `/contacts/discovery/profiles/${profileId}/ignore`,
      ),
    onMutate: (profileId) => {
      const context = optimisticRemoveOtherContact(queryClient, profileId)
      prependIgnoredProfile(queryClient, context.removedGroup)
      return context
    },
    onError: (_err, _profileId, context) => {
      rollbackDispositionContext(queryClient, context)
    },
  })
}

export function useBlockDiscoveredProfile() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (profileId: string) =>
      apiClient.post<ApiDispositionEmails>(
        `/contacts/discovery/profiles/${profileId}/block`,
      ),
    onMutate: (profileId) => {
      const context = optimisticRemoveOtherContact(queryClient, profileId)
      prependBlockedProfile(queryClient, context.removedGroup)
      return context
    },
    onError: (_err, _profileId, context) => {
      rollbackDispositionContext(queryClient, context)
    },
  })
}

export interface ProfileEnrichResponse {
  profile_id: string
  enrichment_status: ApiDiscoveredProfile['enrichment_status']
}

function patchGroupEnrichmentStatus(
  groups: ApiDiscoveredProfileGroup[] | undefined,
  profileId: string,
  status: ApiDiscoveredProfile['enrichment_status'],
): ApiDiscoveredProfileGroup[] | undefined {
  if (!groups) return groups
  return groups.map((g) => {
    const id = g.profile?.id ?? g.profile_ids[0]
    if (id !== profileId) return g
    const patchProfile = (p: ApiDiscoveredProfile): ApiDiscoveredProfile => ({
      ...p,
      enrichment_status: status,
    })
    return {
      ...g,
      profile: g.profile ? patchProfile(g.profile) : g.profile,
      profiles: g.profiles?.map(patchProfile),
    }
  })
}

export function useEnrichDiscoveredProfile() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (profileId: string) =>
      apiClient.post<ProfileEnrichResponse>(
        `/contacts/discovery/profiles/${profileId}/enrich`,
      ),
    onMutate: async (profileId) => {
      await queryClient.cancelQueries({ queryKey: OTHER_CONTACTS_QUERY_KEY })
      queryClient.setQueryData<InfiniteData<DiscoveryOtherPageResult>>(
        OTHER_CONTACTS_QUERY_KEY,
        (old) => {
          if (!old) return old
          return {
            ...old,
            pages: old.pages.map((page) => ({
              ...page,
              groups:
                patchGroupEnrichmentStatus(page.groups ?? [], profileId, 'enriching') ??
                page.groups ??
                [],
            })),
          }
        },
      )
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-other'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-suggestions'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-counts'] })
    },
  })
}

export function useAcceptEnrichmentSuggestion() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (suggestionId: string) =>
      apiClient.post<ApiEnrichmentSuggestion>(
        `/contacts/discovery/suggestions/${suggestionId}/accept`,
      ),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-counts'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-suggestions'] })
      queryClient.invalidateQueries({ queryKey: ['contacts'] })
    },
  })
}

export function useRejectEnrichmentSuggestion() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (suggestionId: string) =>
      apiClient.post<void>(`/contacts/discovery/suggestions/${suggestionId}/reject`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-counts'] })
      queryClient.invalidateQueries({ queryKey: ['contact-discovery-suggestions'] })
    },
  })
}

function normalizeLLMSettings(raw: ApiLLMSettings): ApiLLMSettings {
  return {
    ...raw,
    default_provider_id: raw.default_provider_id ?? '',
    providers: raw.providers ?? [],
  }
}

export function useLLMSettings() {
  return useQuery({
    queryKey: ['llm-settings'],
    queryFn: async () => {
      const data = await apiClient.get<ApiLLMSettings>('/contacts/discovery/llm-settings')
      return normalizeLLMSettings(data)
    },
    staleTime: 60_000,
  })
}

export function useUpdateLLMSettings() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (settings: ApiLLMSettings) =>
      apiClient.put<ApiLLMSettings>('/contacts/discovery/llm-settings', settings),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['llm-settings'] })
    },
  })
}

function normalizeSearchSettings(raw: ApiSearchSettings): ApiSearchSettings {
  return {
    ...raw,
    default_provider_id: raw.default_provider_id ?? '',
    providers: raw.providers ?? [],
  }
}

export function useSearchSettings() {
  return useQuery({
    queryKey: ['search-settings'],
    queryFn: async () => {
      const data = await apiClient.get<ApiSearchSettings>('/contacts/discovery/search-settings')
      return normalizeSearchSettings(data)
    },
    staleTime: 60_000,
  })
}

export function useUpdateSearchSettings() {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: (settings: ApiSearchSettings) =>
      apiClient.put<ApiSearchSettings>('/contacts/discovery/search-settings', settings),
    onSuccess: (data) => {
      queryClient.setQueryData(['search-settings'], normalizeSearchSettings(data))
    },
  })
}

export function useDiscoverLLMModels(baseUrl: string, apiKey: string) {
  const trimmedBaseUrl = baseUrl.trim()
  const trimmedApiKey = apiKey.trim()
  return useQuery({
    queryKey: ['llm-models', trimmedBaseUrl, trimmedApiKey],
    queryFn: () =>
      apiClient.post<ApiLLMModelsResponse>('/contacts/discovery/llm-models/discover', {
        base_url: trimmedBaseUrl,
        api_key: trimmedApiKey || undefined,
      }),
    enabled: trimmedBaseUrl.length > 0,
    staleTime: 5 * 60_000,
    retry: false,
  })
}

export function scanProgressLabel(scan: ApiDiscoveryScan): string {
  const phase = scanPhaseLabel(scan.phase)
  if (scan.phase === 'enriching') {
    const done = scan.profiles_found
    const total = scan.profiles_total
    if (total > 0) {
      return `${phase} ${done.toLocaleString('fr-FR')} / ${total.toLocaleString('fr-FR')} contacts`
    }
    return phase
  }
  if (scan.phase === 'building_profiles') {
    const done = scan.profiles_found
    const total = scan.profiles_total
    if (total > 0) {
      return `${phase} ${done.toLocaleString('fr-FR')} / ${total.toLocaleString('fr-FR')} profils`
    }
    return phase
  }
  if (scan.phase === 'scanning_messages') {
    if (scan.total_messages > 0) {
      return `${phase} ${scan.messages_scanned.toLocaleString('fr-FR')} / ${scan.total_messages.toLocaleString('fr-FR')} messages`
    }
    return `${phase} ${scan.messages_scanned.toLocaleString('fr-FR')} messages`
  }
  return phase
}

export function scanPhaseLabel(phase: ApiDiscoveryScan['phase']): string {
  switch (phase) {
    case 'scanning_messages':
      return 'Analyse des messages…'
    case 'building_profiles':
      return 'Construction des profils…'
    case 'enriching':
      return 'Enrichissement IA…'
    case 'done':
      return 'Terminé'
    default:
      return 'En attente…'
  }
}
