// @vitest-environment happy-dom
// This file tests the chunked main-thread fallback path when Worker
// construction is blocked (e.g. by CSP). It runs in isolation from the
// worker-path tests so that module-level state (workersInitialized, workers)
// starts clean.
import '@testing-library/jest-dom/vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { vi, describe, it, expect, beforeAll, afterAll } from 'vitest'

const MOCK_TOKEN_A = { content: 'const', color: '#569cd6', offset: 0 }
const MOCK_TOKEN_B = { content: 'x', color: '#9cdcfe', offset: 0 }

// Mock shiki's createHighlighter for the fallback path
const mockCodeToTokens = vi.fn()

vi.mock('shiki', () => ({
  createHighlighter: vi.fn().mockResolvedValue({
    codeToTokens: mockCodeToTokens,
  }),
}))

// Stub Worker to throw (simulating CSP) BEFORE the hook module is loaded.
// initWorkers() catches the exception and leaves workers = [].
beforeAll(() => {
  // Use a class so Vitest doesn't warn about constructing vi.fn() without a class impl
  class BlockedWorker {
    constructor() {
      throw new Error('CSP blocks workers')
    }
  }
  vi.stubGlobal('Worker', BlockedWorker)

  mockCodeToTokens.mockReturnValue({
    tokens: [[MOCK_TOKEN_A], [MOCK_TOKEN_B]],
  })
})

afterAll(() => {
  vi.unstubAllGlobals()
})

// Dynamic import ensures this file's module instance is fresh (workersInitialized = false).
// We import inside tests below rather than at the top level.

describe('useHighlightedFile — fallback path (Worker unavailable)', () => {
  it('falls back to chunked main-thread highlighting when Worker construction throws', async () => {
    const { useHighlightedFile } = await import('./use-syntax-highlight')

    const lines = [
      { content: 'const x = 1', newLineNumber: 1, type: 'added' as const },
      { content: 'let y = 2', newLineNumber: 2, type: 'context' as const },
    ]

    const { result } = renderHook(() => useHighlightedFile('app.ts', lines))

    // Initially null while chunked highlighting runs
    expect(result.current).toBeNull()

    // Fallback createHighlighter path eventually resolves tokens
    await waitFor(
      () => {
        expect(result.current).not.toBeNull()
      },
      { timeout: 5000 },
    )

    expect(result.current?.get(1)).toEqual([MOCK_TOKEN_A])
    expect(result.current?.get(2)).toEqual([MOCK_TOKEN_B])
  })

  it('returns a complete token map with no lines missing for ≤200-line input (single-chunk equivalence)', async () => {
    const { useHighlightedFile } = await import('./use-syntax-highlight')

    // 5 lines — well within the 200-line chunk size, so a single codeToTokens call handles all
    const MOCK_TOKENS = [
      [{ content: 'line1', color: '#fff', offset: 0 }],
      [{ content: 'line2', color: '#fff', offset: 0 }],
      [{ content: 'line3', color: '#fff', offset: 0 }],
      [{ content: 'line4', color: '#fff', offset: 0 }],
      [{ content: 'line5', color: '#fff', offset: 0 }],
    ]
    mockCodeToTokens.mockReturnValueOnce({ tokens: MOCK_TOKENS })

    const lines = [1, 2, 3, 4, 5].map((n) => ({
      content: `line${n}`,
      newLineNumber: n,
      type: 'context' as const,
    }))

    const { result } = renderHook(() => useHighlightedFile('src/bar.ts', lines))

    await waitFor(
      () => {
        expect(result.current).not.toBeNull()
      },
      { timeout: 5000 },
    )

    // All 5 line numbers must be present — no lines missing
    expect(result.current!.size).toBe(5)
    for (let n = 1; n <= 5; n++) {
      expect(result.current!.get(n)).toEqual(MOCK_TOKENS[n - 1])
    }
  })

  it('calls AbortController.abort() when component unmounts during chunked fallback', async () => {
    const { useHighlightedFile } = await import('./use-syntax-highlight')

    const abortSpy = vi.spyOn(AbortController.prototype, 'abort')

    // Delay the mock so the hook is still in-flight when we unmount
    mockCodeToTokens.mockImplementationOnce(
      () =>
        new Promise((resolve) =>
          setTimeout(() => resolve({ tokens: [[MOCK_TOKEN_A]] }), 500),
        ),
    )

    const lines = [{ content: 'const x = 1', newLineNumber: 1, type: 'added' as const }]

    const { unmount } = renderHook(() => useHighlightedFile('unmount.ts', lines))

    // Unmount while the async chunked highlight is still pending
    unmount()

    // The cleanup function calls abortController.abort()
    expect(abortSpy).toHaveBeenCalled()

    abortSpy.mockRestore()
  })
})
