/**
 * Tests for DeckGlOverlay — establishes a vitest mocking pattern for
 * `@vis.gl/react-google-maps` (useMap) and `@deck.gl/google-maps`
 * (GoogleMapsOverlay) so the 0%-coverage map renderer files in src/maps/
 * become testable.
 *
 * The same mock approach can be lifted into a shared tests/mocks/ module
 * and reused for deckgl.tsx, google-maps.tsx, and map-libre.tsx in future
 * ratchet PRs.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createRef } from 'react'
import { render } from '@testing-library/react'

// ─── Mocks ────────────────────────────────────────────────────────────────
// `useMap` returns whatever we set via setMockMap below.
let mockMap: unknown = null
const setMockMap = (m: unknown) => {
  mockMap = m
}

vi.mock('@vis.gl/react-google-maps', () => ({
  useMap: () => mockMap,
}))

// Track GoogleMapsOverlay instances so we can assert lifecycle behavior.
interface OverlayMock {
  setMap: ReturnType<typeof vi.fn>
  setProps: ReturnType<typeof vi.fn>
  finalize: ReturnType<typeof vi.fn>
  ctorArgs: unknown
}
const overlayInstances: OverlayMock[] = []

vi.mock('@deck.gl/google-maps', () => ({
  GoogleMapsOverlay: class GoogleMapsOverlayMock {
    setMap = vi.fn()
    setProps = vi.fn()
    finalize = vi.fn()
    ctorArgs: unknown
    constructor(args: unknown) {
      this.ctorArgs = args
      overlayInstances.push(this)
    }
  },
}))

// Import AFTER vi.mock so the mock is registered first.
import { DeckGlOverlay } from './deck-overlay'

beforeEach(() => {
  overlayInstances.length = 0
  mockMap = null
})

describe('DeckGlOverlay', () => {
  it('renders nothing (returns null)', () => {
    const { container } = render(<DeckGlOverlay layers={[]} />)
    expect(container.firstChild).toBeNull()
  })

  it('constructs exactly one GoogleMapsOverlay with `interleaved: true`', () => {
    render(<DeckGlOverlay layers={[]} />)
    expect(overlayInstances).toHaveLength(1)
    expect(
      (overlayInstances[0]!.ctorArgs as { interleaved: boolean }).interleaved,
    ).toBe(true)
  })

  it('calls setMap with null when no map is mounted yet', () => {
    render(<DeckGlOverlay layers={[]} />)
    expect(overlayInstances[0]!.setMap).toHaveBeenCalledWith(null)
  })

  it('calls setMap with the map instance when useMap returns one', () => {
    const fakeMap = { __id: 'map-1' }
    setMockMap(fakeMap)
    render(<DeckGlOverlay layers={[]} />)
    expect(overlayInstances[0]!.setMap).toHaveBeenCalledWith(fakeMap)
  })

  it('threads props into setProps on mount', () => {
    const layers = [{ id: 'layer-1' }] as unknown as never[]
    render(<DeckGlOverlay layers={layers} />)
    expect(overlayInstances[0]!.setProps).toHaveBeenCalled()
    const lastCall = overlayInstances[0]!.setProps.mock.calls.at(-1)![0] as {
      layers: unknown[]
    }
    expect(lastCall.layers).toBe(layers)
  })

  it('updates setProps on every render when props change', () => {
    const { rerender } = render(<DeckGlOverlay layers={[]} />)
    const overlay = overlayInstances[0]!
    const callsAfterMount = overlay.setProps.mock.calls.length
    rerender(
      <DeckGlOverlay layers={[{ id: 'changed' }] as unknown as never[]} />,
    )
    expect(overlay.setProps.mock.calls.length).toBeGreaterThan(callsAfterMount)
  })

  it('exposes the deck instance through `instanceRef.current`', () => {
    const instanceRef = createRef<unknown>()
    render(
      <DeckGlOverlay
        layers={[]}
        instanceRef={
          instanceRef as unknown as Parameters<
            typeof DeckGlOverlay
          >[0]['instanceRef']
        }
      />,
    )
    expect(instanceRef.current).toBe(overlayInstances[0])
  })

  it('finalizes the overlay on unmount', () => {
    const { unmount } = render(<DeckGlOverlay layers={[]} />)
    const overlay = overlayInstances[0]!
    expect(overlay.finalize).not.toHaveBeenCalled()
    unmount()
    expect(overlay.finalize).toHaveBeenCalled()
  })

  it('persists the same overlay across re-renders (does not reconstruct)', () => {
    const { rerender } = render(<DeckGlOverlay layers={[]} />)
    rerender(<DeckGlOverlay layers={[{ id: 'a' }] as unknown as never[]} />)
    rerender(<DeckGlOverlay layers={[{ id: 'b' }] as unknown as never[]} />)
    expect(overlayInstances).toHaveLength(1)
  })

  it('works without an instanceRef prop', () => {
    expect(() => render(<DeckGlOverlay layers={[]} />)).not.toThrow()
  })
})
