import { useMemo } from 'react'
import { useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query'
import { useFilterStore, filtersToParams } from '../store/filterStore'
import api from '../services/api'
import type { Photo } from '../types/photo'

/**
 * Optimistically strip the supplied photo ids from every cached
 * timeline list. Used by discard / delete flows so the photos vanish
 * from the grid the instant the user acts, without waiting for the
 * mutation round-trip + invalidation refetch. The subsequent invalidate
 * still runs and reconciles the cache with server truth.
 */
export function stripPhotosFromCache(queryClient: QueryClient, ids: string[]) {
  if (ids.length === 0) return
  const removed = new Set(ids)
  queryClient.setQueriesData<Photo[]>({ queryKey: ['photos'] }, (prev) => {
    if (!prev) return prev
    return prev.filter((p) => !removed.has(p.id))
  })
}

interface CursorPage {
  photos: Photo[]
  next_cursor: string | null
}

async function fetchCursorPage(
  params: Record<string, unknown>,
  signal?: AbortSignal,
): Promise<CursorPage> {
  const resp = await api.get<CursorPage>('/photos', { params, signal })
  return resp.data
}

/**
 * Single source of truth for the timeline photos query. Both Timeline and
 * PreviewView call this so they share one cache entry — previously
 * PreviewView looked the cache up by key directly, which broke the moment
 * Timeline's key gained the filter params.
 */
export function usePhotosQuery() {
  const q = useFilterStore((s) => s.q)
  const dateFrom = useFilterStore((s) => s.dateFrom)
  const dateTo = useFilterStore((s) => s.dateTo)
  const mediaTypes = useFilterStore((s) => s.mediaTypes)
  const ratingMin = useFilterStore((s) => s.ratingMin)
  const ratingMax = useFilterStore((s) => s.ratingMax)
  const colorLabel = useFilterStore((s) => s.colorLabel)
  const flag = useFilterStore((s) => s.flag)
  const heapId = useFilterStore((s) => s.heapId)
  const folderId = useFilterStore((s) => s.folderId)
  const tagIds = useFilterStore((s) => s.tagIds)
  const duplicates = useFilterStore((s) => s.duplicates)
  const needsReview = useFilterStore((s) => s.needsReview)
  const groupBy = useFilterStore((s) => s.groupBy)
  const sortBy = useFilterStore((s) => s.sortBy)
  const sortOrder = useFilterStore((s) => s.sortOrder)

  const filterParams = useMemo(
    () =>
      filtersToParams({
        q,
        dateFrom,
        dateTo,
        mediaTypes,
        ratingMin,
        ratingMax,
        colorLabel,
        flag,
        heapId,
        folderId,
        tagIds,
        duplicates,
        needsReview,
        groupBy,
        sortBy,
        sortOrder,
      }),
    [q, dateFrom, dateTo, mediaTypes, ratingMin, ratingMax, colorLabel, flag, heapId, folderId, tagIds, duplicates, needsReview, groupBy, sortBy, sortOrder]
  )

  const queryClient = useQueryClient()

  return useQuery({
    queryKey: ['photos', filterParams],
    queryFn: async ({ signal }) => {
      // Two-phase fetch using cursor-based (keyset) pagination.
      // Phase 1 returns the first page (resolves the useQuery promise
      // so consumers exit loading state). Phase 2 chains a bounded
      // background loop — each response includes a `next_cursor` that
      // seeks directly to the next slice via an indexed range scan,
      // O(1) regardless of depth (no OFFSET skipping).
      //
      // MAX_PAGES is intentionally modest: 20 × 500 = 10 000 photos
      // covers almost every browsing session up-front without burning
      // through a 100k library on cold load. If a user scrolls past
      // that horizon we'll add an infinite-query trigger; for now the
      // cap keeps cold-load memory / network pressure sane.
      const PER_PAGE = 500
      const MAX_PAGES = 20
      const INTER_PAGE_DELAY_MS = 50

      const first = await fetchCursorPage(
        { per_page: PER_PAGE, ...filterParams },
        signal,
      )
      const firstBatch = first.photos || []
      let nextCursor: string | null = first.next_cursor

      if (nextCursor) {
        // Fire-and-forget background loop using cursor chaining.
        void (async () => {
          for (let i = 0; i < MAX_PAGES && nextCursor; i++) {
            if (signal?.aborted) return
            try {
              const page = await fetchCursorPage(
                { per_page: PER_PAGE, cursor: nextCursor, ...filterParams },
                signal,
              )
              if (signal?.aborted) return
              const more = page.photos || []
              nextCursor = page.next_cursor
              queryClient.setQueryData<Photo[]>(
                ['photos', filterParams],
                (prev) => (prev ? [...prev, ...more] : more)
              )
              if (!nextCursor || more.length < PER_PAGE) return
              // Yield a beat between pages so the main thread stays
              // responsive (thumbnail decode, scroll handling) while
              // we're back-filling in the background.
              await new Promise((r) => setTimeout(r, INTER_PAGE_DELAY_MS))
            } catch {
              return
            }
          }
        })()
      }

      return firstBatch
    },
    staleTime: 30_000,
  })
}
