/**
 * HeatLayers regression test — post-walkthrough fix.
 *
 * Bug: the heat layer's `setLatLngs` was called BEFORE the layer was added to the
 * map. A leaflet.heat layer that is not on a map has a null `_map`, and
 * `setLatLngs -> redraw` dereferences `_map._animating`, throwing
 * "Cannot read properties of null (reading '_animating')" and white-screening
 * the whole SPA right after login.
 *
 * This test exercises the REAL HeatLayers code path (not a wholesale RecordsMap
 * mock) and asserts the layer is added to the map BEFORE setLatLngs is called.
 * Against the old code (setLatLngs first), the ordering assertion fails.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render } from '@testing-library/react'

const { callLog, setLatLngsSpy, mapAddLayerSpy } = vi.hoisted(() => {
  const callLog: string[] = []
  const setLatLngsSpy = vi.fn((_pts: unknown) => {
    callLog.push('setLatLngs')
  })
  const mapAddLayerSpy = vi.fn((_layer: unknown) => {
    callLog.push('addLayer')
  })
  return { callLog, setLatLngsSpy, mapAddLayerSpy }
})

// Mock leaflet. heatLayer returns a fake layer whose setLatLngs logs call order;
// Icon/DivIcon/marker exist because RecordsMap.tsx runs icon setup at module load.
vi.mock('leaflet', () => {
  class FakeIcon {
    constructor(_opts: unknown) {}
    static Default = { prototype: {}, mergeOptions: vi.fn() }
  }
  return {
    Icon: FakeIcon,
    DivIcon: vi.fn(function FakeDivIcon(_opts: unknown) {
      return {}
    }),
    heatLayer: vi.fn(() => ({ setLatLngs: setLatLngsSpy, setOptions: vi.fn(), addTo: vi.fn() })),
    markerClusterGroup: vi.fn(() => ({ addLayer: vi.fn(), addTo: vi.fn(), clearLayers: vi.fn() })),
    marker: vi.fn(() => ({ bindTooltip: vi.fn().mockReturnThis(), on: vi.fn().mockReturnThis() })),
    default: {},
  }
})

vi.mock('leaflet.heat', () => ({}))
vi.mock('leaflet.markercluster', () => ({}))
vi.mock('leaflet/dist/images/marker-icon-2x.png', () => ({ default: 'marker-icon-2x.png' }))
vi.mock('leaflet/dist/images/marker-icon.png', () => ({ default: 'marker-icon.png' }))
vi.mock('leaflet/dist/images/marker-shadow.png', () => ({ default: 'marker-shadow.png' }))
vi.mock('leaflet/dist/leaflet.css', () => ({}))
vi.mock('leaflet.markercluster/dist/MarkerCluster.css', () => ({}))
vi.mock('leaflet.markercluster/dist/MarkerCluster.Default.css', () => ({}))

// useMap returns a fake map; hasLayer=false so addLayer is exercised.
vi.mock('react-leaflet', () => ({
  MapContainer: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  TileLayer: () => null,
  useMap: () => ({
    addLayer: mapAddLayerSpy,
    removeLayer: vi.fn(),
    hasLayer: () => false,
    getSize: () => ({ x: 800, y: 600 }),
    latLngToContainerPoint: () => ({ x: 100, y: 100 }),
    on: vi.fn(),
    off: vi.fn(),
  }),
}))

import { HeatLayers } from './RecordsMap'
import type { HeatPoint } from './mapUtils'

const heatPoints: HeatPoint[] = [
  [39.9, 116.4, 1],
  [39.91, 116.41, 1],
]

describe('HeatLayers (real code path — regression for null _map crash)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    callLog.length = 0
  })

  it('adds the heat layer to the map BEFORE calling setLatLngs', () => {
    render(
      <HeatLayers
        locationHeatPoints={heatPoints}
        pooHeatPoints={[]}
        showLocationHeat={true}
        showPooHeat={false}
      />,
    )

    // Data was applied...
    expect(setLatLngsSpy).toHaveBeenCalledWith(heatPoints)
    // ...and the layer was added to the map first. The old buggy order
    // (setLatLngs before addLayer) makes this fail.
    expect(callLog).toEqual(['addLayer', 'setLatLngs'])
    expect(callLog.indexOf('addLayer')).toBeLessThan(callLog.indexOf('setLatLngs'))
  })

  it('does not call setLatLngs while the layer is hidden (off the map)', () => {
    render(
      <HeatLayers
        locationHeatPoints={heatPoints}
        pooHeatPoints={heatPoints}
        showLocationHeat={false}
        showPooHeat={false}
      />,
    )

    // Hidden layers are never on the map, so setLatLngs must not run on them.
    expect(setLatLngsSpy).not.toHaveBeenCalled()
    expect(mapAddLayerSpy).not.toHaveBeenCalled()
  })
})
