// Tapestry history walk-back tests. Verifies the `useInfiniteQuery` config
// shape so a future query-key rename or page-cap regression fails CI before
// it ships.

const captured: { options: any } = { options: null }

// `useTapestryPages` reads the active account id via React's
// `useSyncExternalStore` to re-render on sign-out. Outside a React renderer
// the hook throws; intercept it to call `getSnapshot` synchronously so the
// `useInfiniteQuery` capture below still runs.
jest.mock('react', () => {
  const actual = jest.requireActual('react')
  return {
    ...actual,
    useSyncExternalStore: (_sub: unknown, getSnapshot: () => unknown) =>
      getSnapshot(),
  }
})

jest.mock('@tanstack/react-query', () => ({
  useInfiniteQuery: (options: unknown) => {
    captured.options = options
    return {} as unknown
  },
}))

jest.mock('src/lib/api-context', () => ({
  ContextForActiveAccount: () => ({ request: jest.fn() }),
}))

// `range.ts` -> `queries/keys.ts` -> `accountStore.ts` -> `storage.ts` ->
// `react-native-mmkv`. The native-module load fails outside a built app, so
// short-circuit at the lowest stable layer with no-op MMKV/SecureStore
// shims. The test never reads or writes through them.
jest.mock('react-native-mmkv', () => ({
  createMMKV: () => ({
    getString: () => undefined,
    getNumber: () => undefined,
    getBoolean: () => undefined,
    set: () => {},
    remove: () => {},
    delete: () => {},
    clearAll: () => {},
    getAllKeys: () => [],
    addOnValueChangedListener: () => ({ remove: () => {} }),
  }),
}))

jest.mock('expo-secure-store', () => ({
  getItem: () => null,
  setItem: () => {},
  deleteItemAsync: async () => {},
}))

const { useTapestryPages, MAX_BACKFILL_PAGES, MIN_ROWS_FOR_VIEWPORT } =
  require('src/lib/tapestry/range') as typeof import('src/lib/tapestry/range')

describe('useTapestryPages', () => {
  beforeEach(() => {
    captured.options = null
  })

  it('uses the global per-instance pages query key', () => {
    useTapestryPages('pixel.example')
    expect(captured.options).not.toBeNull()
    expect(captured.options.queryKey).toEqual([
      'global',
      'tapestry',
      'pixel.example',
      'pages',
    ])
  })

  it('seeds from the current 30-minute page', () => {
    useTapestryPages('pixel.example')
    const param: string = captured.options.initialPageParam
    expect(param).toMatch(/^\d{12}$/)
    // Page-aligned: minutes are either 00 or 30.
    expect(['00', '30']).toContain(param.slice(10, 12))
  })

  it('walks pages backwards by 30 minutes', () => {
    useTapestryPages('pixel.example')
    // Fixed page on a clean boundary so `previousPage` math is unambiguous.
    const next = captured.options.getNextPageParam({
      page: '202604281200',
      rows: [],
    })
    expect(next).toBe('202604281130')
  })

  it('walks across hour and day boundaries', () => {
    useTapestryPages('pixel.example')
    expect(
      captured.options.getNextPageParam({ page: '202604281000', rows: [] })
    ).toBe('202604280930')
    // Hour boundary: 12:00 → 11:30
    expect(
      captured.options.getNextPageParam({ page: '202604281200', rows: [] })
    ).toBe('202604281130')
    // Day boundary: 00:00 (28 Apr) → 23:30 (27 Apr)
    expect(
      captured.options.getNextPageParam({ page: '202604280000', rows: [] })
    ).toBe('202604272330')
  })

  it('caps in-memory at the full 30-day retention window (1440 pages)', () => {
    useTapestryPages('pixel.example')
    expect(captured.options.maxPages).toBe(1440)
  })

  it('marks the query non-staling so tab re-entry does not re-fetch all pages', () => {
    // Past pages are immutable on the server; current-minute freshness is
    // handled by `useTapestryPoll`; cached-page lag is closed by
    // `useTapestryGapFill`. A `staleTime: 0` (RQ default) re-fetches every
    // retained page on remount — measured at 48 redundant GETs per tab
    // return on a 24 h backfill.
    useTapestryPages('pixel.example')
    expect(captured.options.staleTime).toBe(Number.POSITIVE_INFINITY)
  })

  it('exports backfill constants split between eager (24 h) and pull (30 d)', () => {
    // Eager loop caps at 24 h (48 × 30-minute pages) so a quiet instance
    // can't accumulate days of pages across tab re-entries — user-driven
    // scroll pagination is the path past the eager ceiling, capped only
    // by `maxPages` (1440 = 30 d retention). Viewport-row goal stays at
    // 30 for the legacy per-row layout.
    expect(MAX_BACKFILL_PAGES).toBe(48)
    expect(MIN_ROWS_FOR_VIEWPORT).toBe(30)
  })

  it('disables when host is empty', () => {
    useTapestryPages('')
    expect(captured.options.enabled).toBe(false)
  })

  it('rekeys on instance', () => {
    useTapestryPages('pixel.example')
    const a = captured.options.queryKey
    useTapestryPages('halflight.example')
    const b = captured.options.queryKey
    expect(a).not.toEqual(b)
    expect(b[2]).toBe('halflight.example')
  })
})
