// packages/commerce-catalog-react/src/useProductList.test.tsx
import { act, renderHook, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { describe, expect, it, vi } from 'vitest'
import type { CatalogFilter, Page, Product } from '@platform-modules/commerce-catalog'
import { CatalogProvider } from './CatalogProvider.js'
import { useProductList } from './useProductList.js'
import type { CatalogClient } from './client.js'

const page = (n: number): Page<Product> => ({
  items: Array.from({ length: n }, (_, i) => ({
    id: `p${i}`, kind: 'physical', vendorId: null, slug: `s${i}`, title: 'T', status: 'active',
    media: [], tags: [], createdAt: new Date(), updatedAt: new Date(), variants: [],
  })),
  total: n, page: 1, pageSize: 20,
})

function mk(client: Partial<CatalogClient>) {
  const full: CatalogClient = {
    getProductBySlug: async () => null,
    getProductById: async () => null,
    listProducts: async () => page(0),
    ...client,
  }
  const wrapper = ({ children }: { children: ReactNode }) => (
    <CatalogProvider client={full}>{children}</CatalogProvider>
  )
  return { wrapper }
}
const filter: CatalogFilter = { audience: 'public' }

describe('useProductList', () => {
  it('fetches the page on mount', async () => {
    const listProducts = vi.fn().mockResolvedValue(page(3))
    const { wrapper } = mk({ listProducts })
    const { result } = renderHook(() => useProductList(filter), { wrapper })
    await waitFor(() => expect(result.current.loading).toBe(false))
    expect(result.current.page?.items).toHaveLength(3)
    expect(listProducts).toHaveBeenCalledWith(filter)
  })

  it('empty page is not an error', async () => {
    const { wrapper } = mk({ listProducts: async () => page(0) })
    const { result } = renderHook(() => useProductList(filter), { wrapper })
    await waitFor(() => expect(result.current.loading).toBe(false))
    expect(result.current.page?.items).toEqual([])
    expect(result.current.error).toBeNull()
  })

  it('captures a thrown error into error state', async () => {
    const { wrapper } = mk({ listProducts: async () => { throw new Error('boom') } })
    const { result } = renderHook(() => useProductList(filter), { wrapper })
    await waitFor(() => expect(result.current.error?.message).toBe('boom'))
  })

  it('seeded: loading false, page from seed, NO mount fetch; reload() refetches', async () => {
    const listProducts = vi.fn().mockResolvedValue(page(5))
    const { wrapper } = mk({ listProducts })
    const { result } = renderHook(
      () => useProductList(filter, { initialData: page(2) }),
      { wrapper },
    )
    expect(result.current.loading).toBe(false)
    expect(result.current.page?.items).toHaveLength(2)
    act(() => result.current.reload())
    await waitFor(() => expect(result.current.page?.items).toHaveLength(5))
    expect(listProducts).toHaveBeenCalledTimes(1)
  })

  it('filter change refetches', async () => {
    const listProducts = vi.fn()
      .mockResolvedValueOnce(page(1))
      .mockResolvedValueOnce(page(4))
    const { wrapper } = mk({ listProducts })
    let f: CatalogFilter = { audience: 'public', tag: 'a' }
    const { result, rerender } = renderHook(() => useProductList(f), { wrapper })
    await waitFor(() => expect(result.current.page?.items).toHaveLength(1))
    f = { audience: 'public', tag: 'b' }
    rerender()
    await waitFor(() => expect(result.current.page?.items).toHaveLength(4))
    expect(listProducts).toHaveBeenCalledTimes(2)
  })

  it('throws CatalogProviderError outside a provider', () => {
    expect(() => renderHook(() => useProductList(filter))).toThrow()
  })
})
