// Tapestry history walk-back. `useInfiniteQuery` over canonical 30-minute
// pages — each page is a path-keyed cache hit on the server (immutable for
// past pages), so backfill across cold-starts is essentially free.

import { useInfiniteQuery } from '@tanstack/react-query'
import { useEffect, useSyncExternalStore } from 'react'
import { ContextForActiveAccount } from 'src/lib/api-context'
import { queryKeys } from 'src/lib/queries/keys'
import { AccountStore } from 'src/state/accountStore'
import { currentPage, previousPage } from './window'

type BucketRow = { bucket: string; letters: string }

type TapestryPage = {
  page: string
  rows: BucketRow[]
}

/**
 * Walks back through 30-minute pages for `instance`. Caps in-memory at the
 * full 30-day retention window (1440 pages = 30 d × 48 pages/d) so a sparse
 * instance can reach its oldest populated bucket without the eager loop
 * outrunning react-query's retained-page ceiling. Past pages are served
 * `Cache-Control: immutable, max-age=86400` from the nginx fastcgi cache,
 * so even a full walk-back is a sequence of CDN hits — no PHP-FPM per
 * request, ~5 KB worst-case wire across 1440 empty pages.
 *
 * v1 walks one direction only: the live tail (`useTapestryPoll`) handles
 * "newer than the seeded current page", so `getPreviousPageParam` stays
 * undefined.
 */
export function useTapestryPages(instance: string) {
  // Subscribe to AccountStore so a sign-out triggers a re-render that flips
  // `enabled` off — without this, the hook keeps the query active across the
  // active-account flip and React Query's `queryClient.clear()` observer-
  // rewire refires the queryFn before the Tapestry screen's no-user
  // `<Redirect />` unmounts the subtree. The queryFn's own guard is the
  // belt-and-suspenders second line for any race the `enabled` flip can't
  // close.
  const activeId = useSyncExternalStore(
    AccountStore.subscribe,
    AccountStore.activeId,
    AccountStore.activeId
  )
  return useInfiniteQuery({
    queryKey: queryKeys.tapestry.pages(instance),
    initialPageParam: currentPage(),
    queryFn: async ({ pageParam }: { pageParam: string }): Promise<TapestryPage> => {
      if (!AccountStore.activeId()) return { page: pageParam, rows: [] }
      const ctx = ContextForActiveAccount()
      const res = await ctx.request(`/api/v1/tapestry/page/${pageParam}`, {
        method: 'GET',
      })
      const rows = (await res.json()) as BucketRow[]
      return { page: pageParam, rows }
    },
    getNextPageParam: (last) => previousPage(last.page),
    maxPages: 1440,
    enabled: instance.length > 0 && !!activeId,
  })
}

// Eager backfill ceilings. Quiet instances return mostly-empty pages
// (`TapestryStore::readRange` only emits populated buckets), so the screen
// can mount with too few rendered rows to overflow the viewport — and
// without overflow, FlashList's `onEndReached` never fires and history
// stalls before the user has anything to scroll back through.
//
// `MIN_ROWS_FOR_VIEWPORT` is the rendered-row floor we walk towards; once
// the screen has at least this many `Row` items (live row + history rows
// after dedup) the eager loop stops and `onEndReached` takes over for
// further scroll-back. The condition `itemsLength < MIN_ROWS_FOR_VIEWPORT`
// also covers the zero-rows case — on a sparse instance we keep walking
// until we find at least one populated bucket, otherwise the screen is
// unscrollable and onEndReached never gets a chance to take over.
//
// `MAX_BACKFILL_PAGES = 1440` is a hard safety bound covering the full
// 30-day retention window. On `outlande.rs` today the oldest populated
// bucket is ~33 h back, so the loop terminates after ~67 pages once it
// finds rows; on a brand-new or fully idle instance it runs to the cap
// (all CDN hits) and then yields to the empty-state UI.
export const MIN_ROWS_FOR_VIEWPORT = 30
export const MAX_BACKFILL_PAGES = 1440

type TapestryPagesQuery = ReturnType<typeof useTapestryPages>

/**
 * Drive `fetchNextPage()` until the rendered list overflows the viewport
 * or a hard page ceiling is hit. Re-runs after each settle (the deps cover
 * `isFetchingNextPage` flipping false), so the loop walks one page at a
 * time and stops on its own when any guard fires.
 *
 * `itemsLength` is the consumer's rendered-item count (post-dedup, live
 * row included) — not raw server-row count. The eager fill is about
 * "enough rows to scroll", and that's what FlashList will see.
 */
export function useEagerBackfill(query: TapestryPagesQuery, itemsLength: number) {
  const { isSuccess, isFetchingNextPage, hasNextPage, fetchNextPage, data } = query
  const pagesFetched = data?.pages?.length ?? 0
  useEffect(() => {
    if (!isSuccess) return
    if (itemsLength >= MIN_ROWS_FOR_VIEWPORT) return
    if (pagesFetched >= MAX_BACKFILL_PAGES) return
    if (isFetchingNextPage) return
    if (!hasNextPage) return
    fetchNextPage()
  }, [
    itemsLength,
    isSuccess,
    isFetchingNextPage,
    pagesFetched,
    hasNextPage,
    fetchNextPage,
  ])
}
