/**
 * Tests for DeckGLComponent — the basemap-routing wrapper that picks
 * GoogleMaps or MapLibre based on the basemap type.
 *
 * The two children (GoogleMaps, MapLibre) are heavy SDK-bound components;
 * we mock them as prop-capturing passthroughs so DeckGLComponent's own
 * routing logic, isLoaded tracking, and onLoad coordination can be
 * exercised in isolation.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, act } from '@testing-library/react'

// Capture the props each child renderer receives so we can verify the
// routing branches.
const googleMapsProps: unknown[] = []
const mapLibreProps: unknown[] = []

vi.mock('./google-maps', () => ({
  GoogleMaps: (props: Record<string, unknown>) => {
    googleMapsProps.push(props)
    return <div data-testid='gmaps-stub' />
  },
}))

vi.mock('./map-libre', () => ({
  MapLibre: (props: Record<string, unknown>) => {
    mapLibreProps.push(props)
    return <div data-testid='maplibre-stub' />
  },
}))

import { DeckGLComponent } from './deckgl'
import { CartoBasemaps, GmapsBasemap } from '../const'

beforeEach(() => {
  googleMapsProps.length = 0
  mapLibreProps.length = 0
})

describe('DeckGLComponent basemap routing', () => {
  it('renders GoogleMaps for a Google Maps basemap', () => {
    const { getByTestId, queryByTestId } = render(
      <DeckGLComponent
        id='m1'
        basemap={GmapsBasemap.roadmap}
        initialViewState={{ latitude: 0, longitude: 0, zoom: 5 }}
        layers={[]}
      />,
    )
    expect(getByTestId('gmaps-stub')).toBeDefined()
    expect(queryByTestId('maplibre-stub')).toBeNull()
  })

  it('renders MapLibre for a CARTO (positron) basemap', () => {
    const { getByTestId, queryByTestId } = render(
      <DeckGLComponent
        id='m2'
        basemap={CartoBasemaps.positron}
        initialViewState={{ latitude: 0, longitude: 0, zoom: 5 }}
        layers={[]}
      />,
    )
    expect(getByTestId('maplibre-stub')).toBeDefined()
    expect(queryByTestId('gmaps-stub')).toBeNull()
  })

  it('renders MapLibre for darkMatter and voyager basemaps', () => {
    const { rerender, getByTestId } = render(
      <DeckGLComponent
        id='m3'
        basemap={CartoBasemaps.darkMatter}
        initialViewState={{ latitude: 0, longitude: 0, zoom: 5 }}
        layers={[]}
      />,
    )
    expect(getByTestId('maplibre-stub')).toBeDefined()
    rerender(
      <DeckGLComponent
        id='m3'
        basemap={CartoBasemaps.voyager}
        initialViewState={{ latitude: 0, longitude: 0, zoom: 5 }}
        layers={[]}
      />,
    )
    expect(getByTestId('maplibre-stub')).toBeDefined()
  })

  it('uses positron as the default basemap when none is provided', () => {
    const { getByTestId } = render(<DeckGLComponent id='m4' layers={[]} />)
    // positron → maplibre
    expect(getByTestId('maplibre-stub')).toBeDefined()
  })
})

describe('DeckGLComponent prop forwarding', () => {
  it('forwards mapStyle (URL from MAPS_BASEMAP) to MapLibre', () => {
    render(
      <DeckGLComponent id='m5' basemap={CartoBasemaps.positron} layers={[]} />,
    )
    const props = mapLibreProps.at(-1) as { mapStyle: string }
    expect(typeof props.mapStyle).toBe('string')
    expect(props.mapStyle.length).toBeGreaterThan(0)
  })

  it('forwards basemap + gmapsProps + onLoad bridge to GoogleMaps', () => {
    const onLoad = vi.fn()
    render(
      <DeckGLComponent
        id='m6'
        basemap={GmapsBasemap.roadmap}
        layers={[]}
        onLoad={onLoad}
        gmapsProps={{ apiKey: 'key-1' }}
      />,
    )
    const props = googleMapsProps.at(-1) as {
      basemap: string
      gmapsProps: { apiKey: string }
      onLoad: (e: { type: string; value: boolean }) => void
    }
    expect(props.basemap).toBe('roadmap')
    expect(props.gmapsProps.apiKey).toBe('key-1')
    // The onLoad prop is the inner handleLoad bridge (not the consumer onLoad)
    expect(typeof props.onLoad).toBe('function')
  })
})

describe('DeckGLComponent handleLoad / onLoad bridge', () => {
  it('fires onLoad(true) only after both deckgl and load have arrived', () => {
    const onLoad = vi.fn()
    render(
      <DeckGLComponent
        id='m7'
        basemap={CartoBasemaps.positron}
        layers={[]}
        onLoad={onLoad}
      />,
    )
    const childProps = mapLibreProps.at(-1) as {
      onLoad: (e: { type: 'deckgl' | 'load'; value: boolean }) => void
    }

    // Initially: false (both unloaded)
    expect(onLoad).toHaveBeenLastCalledWith(false)

    // First half: only deckgl arrives → still false
    act(() => childProps.onLoad({ type: 'deckgl', value: true }))
    expect(onLoad).toHaveBeenLastCalledWith(false)

    // Second half: load arrives → fires true
    act(() => childProps.onLoad({ type: 'load', value: true }))
    expect(onLoad).toHaveBeenLastCalledWith(true)
  })

  it('does not re-fire onLoad when only the callback identity changes', () => {
    // `onLoad` is read through an Effect Event, so the notification effect
    // depends on `isLoaded` alone. Re-rendering with a brand-new callback
    // identity (load state unchanged) must NOT re-fire the notification.
    const first = vi.fn()
    const { rerender } = render(
      <DeckGLComponent
        id='m-id'
        basemap={CartoBasemaps.positron}
        layers={[]}
        onLoad={first}
      />,
    )
    // Mount fires exactly once with the initial (false) load state.
    expect(first).toHaveBeenCalledTimes(1)
    expect(first).toHaveBeenLastCalledWith(false)

    const second = vi.fn()
    rerender(
      <DeckGLComponent
        id='m-id'
        basemap={CartoBasemaps.positron}
        layers={[]}
        onLoad={second}
      />,
    )
    expect(first).toHaveBeenCalledTimes(1)
    expect(second).not.toHaveBeenCalled()
  })

  it('does not throw when onLoad is omitted (the `?.` short-circuit branch)', () => {
    render(
      <DeckGLComponent id='m8' basemap={CartoBasemaps.positron} layers={[]} />,
    )
    const childProps = mapLibreProps.at(-1) as {
      onLoad: (e: { type: 'deckgl' | 'load'; value: boolean }) => void
    }
    expect(() =>
      childProps.onLoad({ type: 'deckgl', value: true }),
    ).not.toThrow()
  })

  it('resets isLoaded to false on unmount via the cleanup effect', () => {
    const onLoad = vi.fn()
    const { unmount } = render(
      <DeckGLComponent
        id='m9'
        basemap={CartoBasemaps.positron}
        layers={[]}
        onLoad={onLoad}
      />,
    )
    const childProps = mapLibreProps.at(-1) as {
      onLoad: (e: { type: 'deckgl' | 'load'; value: boolean }) => void
    }
    // Get to "all loaded" state
    act(() => childProps.onLoad({ type: 'deckgl', value: true }))
    act(() => childProps.onLoad({ type: 'load', value: true }))
    expect(onLoad).toHaveBeenLastCalledWith(true)
    // Unmount triggers cleanup which resets both back to false
    unmount()
    // After unmount, onLoad fires with false at some point (the cleanup
    // synchronously sets the state through handleLoad calls)
    expect(onLoad).toHaveBeenCalled()
  })
})
