/**
 * Tests for CustomEditableGeojsonLayer — a class extension of DeckGL's
 * EditableGeoJsonLayer that wires into a custom EventManager.
 *
 * Strategy: mock @deck.gl-community/editable-layers's EditableGeoJsonLayer
 * as a class with the bare minimum surface needed (state, context,
 * updateState, _editableLayerState). The test invokes updateState and the
 * event-handler methods directly on an instance.
 */
/* eslint-disable @typescript-eslint/dot-notation, @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- private-property + spy access is unavoidable in this class test */

/** Spy shape produced by our vi.mock() — used to narrow the real `on`/`off`
 * method types so `.mock.calls[…]` is reachable. */
interface SpyManager {
  on: ReturnType<typeof vi.fn>
  off: ReturnType<typeof vi.fn>
}

/** Casts the real `EventManager | undefined | null` returned by the layer
 * methods to our spy shape. The mock always returns one of these in tests. */
function asSpy(em: unknown): SpyManager {
  return em as SpyManager
}
import { describe, it, expect, vi, beforeEach } from 'vitest'

const updateStateSuper = vi.fn()

vi.mock('@deck.gl-community/editable-layers', () => ({
  EditableGeoJsonLayer: class EditableGeoJsonLayerMock {
    static layerName = 'EditableGeoJsonLayer'
    props: { eventManager?: unknown } = {}
    state = {
      _editableLayerState: {
        eventHandler: vi.fn(),
      },
    }
    context = {
      deck: {
        eventManager: {
          on: vi.fn(),
          off: vi.fn(),
        },
      },
    }
    constructor(props: unknown) {
      Object.assign(this, { props: props ?? {} })
    }
    updateState(args: unknown) {
      updateStateSuper(args)
    }
  },
}))

vi.mock('mjolnir.js', () => ({
  EventManager: class EventManagerMock {},
  Pan: { name: 'Pan' },
  Pinch: { name: 'Pinch' },
  Tap: { name: 'Tap' },
  InputDirection: { name: 'InputDirection' },
}))

import { CustomEditableGeojsonLayer } from './custom-editable-geojson-layer'

beforeEach(() => {
  updateStateSuper.mockReset()
})

function makeLayer(props: Record<string, unknown> = {}) {
  const layer = new CustomEditableGeojsonLayer(props)
  return layer
}

describe('CustomEditableGeojsonLayer', () => {
  it('uses props.eventManager when provided', () => {
    const eventManager = { on: vi.fn(), off: vi.fn() }
    const layer = makeLayer({ eventManager })
    // _getEventManager should return the prop, not context.deck.eventManager
    expect(layer['_getEventManager']()).toBe(eventManager)
  })

  it('falls back to context.deck.eventManager when no prop is set', () => {
    const layer = makeLayer({})
    const eventManager = asSpy(layer['_getEventManager']())
    // The mocked context.deck.eventManager has `on`/`off` spy methods
    expect(typeof eventManager.on).toBe('function')
    expect(typeof eventManager.off).toBe('function')
  })

  it('_addEventHandlers registers every EVENT_TYPE on the event manager', () => {
    const layer = makeLayer({})
    const eventManager = asSpy(layer['_getEventManager']())
    layer['_addEventHandlers']()
    // Should have registered at least one event handler with priority 100
    expect(eventManager.on).toHaveBeenCalled()
    const call = eventManager.on.mock.calls[0] as unknown[]
    expect(call?.[2]).toEqual({ priority: 100 })
  })

  it('_removeEventHandlers uses the passed eventManager when provided', () => {
    const passedManager = { on: vi.fn(), off: vi.fn() }
    const layer = makeLayer({})
    layer['_removeEventHandlers'](passedManager as never)
    expect(passedManager.off).toHaveBeenCalled()
  })

  it('_removeEventHandlers falls back to default eventManager when no arg', () => {
    const layer = makeLayer({})
    const defaultManager = asSpy(layer['_getEventManager']())
    layer['_removeEventHandlers']()
    expect(defaultManager.off).toHaveBeenCalled()
  })

  it('updateState re-registers handlers when eventManager prop changes', () => {
    const oldManager = { on: vi.fn(), off: vi.fn() }
    const newManager = { on: vi.fn(), off: vi.fn() }
    const layer = makeLayer({ eventManager: newManager })

    layer.updateState({
      props: { eventManager: newManager },
      oldProps: { eventManager: oldManager },
      context: {},
      changeFlags: {},
    } as never)

    // Removed handlers from both old and new manager (per the source)
    expect(newManager.off).toHaveBeenCalled()
    expect(oldManager.off).toHaveBeenCalled()
    // Added handlers on the new manager
    expect(newManager.on).toHaveBeenCalled()
    // super.updateState was called once
    expect(updateStateSuper).toHaveBeenCalledOnce()
  })

  it('updateState does NOT re-register when eventManager prop is unchanged', () => {
    const sameManager = { on: vi.fn(), off: vi.fn() }
    const layer = makeLayer({ eventManager: sameManager })

    layer.updateState({
      props: { eventManager: sameManager },
      oldProps: { eventManager: sameManager },
      context: {},
      changeFlags: {},
    } as never)

    // No add/remove operations; just super.updateState
    expect(sameManager.off).not.toHaveBeenCalled()
    expect(sameManager.on).not.toHaveBeenCalled()
    expect(updateStateSuper).toHaveBeenCalledOnce()
  })

  it('has the layerName class property set', () => {
    expect(CustomEditableGeojsonLayer.layerName).toBe(
      'CustomEditableGeojsonLayer',
    )
  })
})
