// @vitest-environment happy-dom
import { describe, expect, it } from 'vitest'
import { act, render, screen, waitFor } from '@testing-library/react'

import { useSeoResource } from '../../views/seo/useSeoResource.js'

/**
 * Regression test for the `useSeoResource` stale-response race: when the
 * dependency (and therefore the fetcher) changes rapidly, a slow earlier
 * request that resolves AFTER a faster later one must not clobber the newer
 * result. We drive this with two deferred promises whose resolution order is
 * inverted relative to the request order.
 */

function Probe({ dep, fetcher }: { dep: number; fetcher: () => Promise<string> }) {
  const { data, loading, error } = useSeoResource<string>(fetcher, [dep])
  return (
    <div>
      <span data-testid="state">{loading ? 'loading' : (error ?? data ?? 'empty')}</span>
    </div>
  )
}

function deferred<T>() {
  let resolve!: (value: T) => void
  let reject!: (reason?: unknown) => void
  const promise = new Promise<T>((res, rej) => {
    resolve = res
    reject = rej
  })
  return { promise, resolve, reject }
}

describe('useSeoResource', () => {
  it('ignores a stale (earlier) response that resolves after the latest one', async () => {
    const first = deferred<string>()
    const second = deferred<string>()

    // First render uses the "first" fetcher (slow), then we switch deps so the
    // hook re-runs with the "second" fetcher (fast).
    const firstFetcher = () => first.promise
    const secondFetcher = () => second.promise

    const { rerender } = render(<Probe dep={1} fetcher={firstFetcher} />)
    rerender(<Probe dep={2} fetcher={secondFetcher} />)

    // The newer request resolves FIRST.
    await act(async () => {
      second.resolve('NEW')
    })
    await waitFor(() => expect(screen.getByTestId('state').textContent).toBe('NEW'))

    // The stale earlier request resolves LATER — it must be ignored.
    await act(async () => {
      first.resolve('OLD')
    })
    expect(screen.getByTestId('state').textContent).toBe('NEW')
  })

  it('surfaces fetcher errors', async () => {
    const d = deferred<string>()
    render(<Probe dep={1} fetcher={() => d.promise} />)
    await act(async () => {
      d.reject(new Error('boom'))
    })
    await waitFor(() => expect(screen.getByTestId('state').textContent).toBe('boom'))
  })
})
