/**
 * Tests for MapLibre map renderer. Uses module mocks for
 * `react-map-gl/maplibre` and `@deck.gl/react` so the component renders
 * without a WebGL context or a network connection.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { createRef } from 'react'
import { render } from '@testing-library/react'

// Capture props that the children receive so we can verify wiring.
const deckGLProps: unknown[] = []
const mapProps: unknown[] = []
const attributionProps: unknown[] = []
const useMapLibreMock = vi.fn()

vi.mock('react-map-gl/maplibre', () => ({
  MapProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  Map: (props: Record<string, unknown>) => {
    mapProps.push(props)
    return (
      <div data-testid='maplibre-map'>{props.children as React.ReactNode}</div>
    )
  },
  AttributionControl: (props: Record<string, unknown>) => {
    attributionProps.push(props)
    return <div data-testid='maplibre-attribution' />
  },
  useMap: () => useMapLibreMock() as { default: unknown },
}))

vi.mock('@deck.gl/react', () => ({
  __esModule: true,
  default: (props: Record<string, unknown>) => {
    deckGLProps.push(props)
    return (
      <div data-testid='deckgl-canvas'>{props.children as React.ReactNode}</div>
    )
  },
}))

import { MapLibre } from './map-libre'

beforeEach(() => {
  deckGLProps.length = 0
  mapProps.length = 0
  attributionProps.length = 0
  useMapLibreMock.mockReset()
  useMapLibreMock.mockReturnValue({ default: { __id: 'maplibre-instance' } })
})

const baseProps = {
  id: 'mp-1',
  basemap: undefined as never,
  layers: [] as never[],
  onLoad: vi.fn(),
  mapStyle: 'https://example.com/style.json',
}

describe('MapLibre render', () => {
  it('renders DeckGL + Map + AttributionControl', () => {
    const { getByTestId } = render(<MapLibre {...baseProps} />)
    expect(getByTestId('deckgl-canvas')).toBeDefined()
    expect(getByTestId('maplibre-map')).toBeDefined()
    expect(getByTestId('maplibre-attribution')).toBeDefined()
  })

  it('forwards id, controller, layers to DeckGL', () => {
    // A fake Layer class that records its construction args.
    class FakeLayer {
      constructor(public readonly props: unknown) {}
    }
    const layers = [{ type: FakeLayer, props: { data: [1] } }] as never[]
    render(<MapLibre {...baseProps} controller={true} layers={layers} />)
    const props = deckGLProps.at(-1) as {
      id: string
      controller: boolean
    }
    expect(props.id).toBe('mp-1')
    expect(props.controller).toBe(true)
  })

  it('forwards mapStyle + reuseMaps + attributionControl=false to Map', () => {
    render(<MapLibre {...baseProps} />)
    const props = mapProps.at(-1) as {
      mapStyle: string
      reuseMaps: boolean
      attributionControl: boolean
    }
    expect(props.mapStyle).toBe('https://example.com/style.json')
    expect(props.reuseMaps).toBe(false)
    expect(props.attributionControl).toBe(false)
  })

  it('merges parameters with depthTest:false default', () => {
    render(<MapLibre {...baseProps} parameters={{ blend: true } as never} />)
    const props = deckGLProps.at(-1) as {
      parameters: { depthTest: boolean; blend?: boolean }
    }
    expect(props.parameters.depthTest).toBe(false)
    expect(props.parameters.blend).toBe(true)
  })

  it('threads attributionProps into the AttributionControl', () => {
    render(
      <MapLibre
        {...baseProps}
        attributionProps={{ position: 'top-left' } as never}
      />,
    )
    const props = attributionProps.at(-1) as {
      compact: boolean
      position: string
    }
    expect(props.compact).toBe(false)
    expect(props.position).toBe('top-left')
  })
})

describe('MapLibre event bridging', () => {
  it('DeckGL onLoad → onLoad({ type: "deckgl", value: true })', () => {
    const onLoad = vi.fn()
    render(<MapLibre {...baseProps} onLoad={onLoad} />)
    const props = deckGLProps.at(-1) as { onLoad: () => void }
    props.onLoad()
    expect(onLoad).toHaveBeenCalledWith({ type: 'deckgl', value: true })
  })

  it('Map onLoad → onLoad({ type: "load", value: true })', () => {
    const onLoad = vi.fn()
    render(<MapLibre {...baseProps} onLoad={onLoad} />)
    const props = mapProps.at(-1) as { onLoad: () => void }
    props.onLoad()
    expect(onLoad).toHaveBeenCalledWith({ type: 'load', value: true })
  })

  it('forwards onIdle and onViewStateChange', () => {
    const onIdle = vi.fn()
    const onViewStateChange = vi.fn()
    render(
      <MapLibre
        {...baseProps}
        onIdle={onIdle}
        onViewStateChange={onViewStateChange}
      />,
    )
    expect((mapProps.at(-1) as { onIdle: unknown }).onIdle).toBe(onIdle)
    expect(
      (deckGLProps.at(-1) as { onViewStateChange: unknown }).onViewStateChange,
    ).toBe(onViewStateChange)
  })
})

describe('MapLibre MapReferences', () => {
  it('exposes the maplibre instance through overlayRef', () => {
    const overlayRef = createRef<unknown>()
    const fakeMap = { __id: 'maplibre-real' }
    useMapLibreMock.mockReturnValue({ default: fakeMap })
    render(
      <MapLibre
        {...baseProps}
        overlayRef={
          overlayRef as unknown as Parameters<typeof MapLibre>[0]['overlayRef']
        }
      />,
    )
    expect(overlayRef.current).toBe(fakeMap)
  })
})
