import { describe, it, expect, vi } from 'vitest'
import { CustomMeasureDistanceMode } from './custom-measurement-distance.class'
import { EditType } from '../../const'
import { MapMeasurementUnitAuto } from '../const'
import type {
  ModeProps,
  FeatureCollection,
} from '@deck.gl-community/editable-layers'
import type {
  BasePointerEvent,
  PointerMoveEvent,
} from 'node_modules/@deck.gl-community/editable-layers/dist/edit-modes/types'

describe('CustomMeasureDistanceMode', () => {
  const mockLastPointerMoveEvent: PointerMoveEvent = {
    picks: [],
    screenCoords: [0, 0],
    mapCoords: [0, 0],
    sourceEvent: null,
    cancelPan: vi.fn(),
    pointerDownPicks: null,
    pointerDownScreenCoords: null,
    pointerDownMapCoords: null,
    isDragging: false,
  }

  const mockProps: ModeProps<FeatureCollection> = {
    data: {
      type: 'FeatureCollection',
      features: [],
    },
    onEdit: vi.fn(),
    onUpdateCursor: vi.fn(),
    selectedIndexes: [],
    lastPointerMoveEvent: mockLastPointerMoveEvent,
    modeConfig: {},
    cursor: null,
  }

  describe('handleClick', () => {
    it('should handle click event and call onEdit with distance and positions', () => {
      const mode = new CustomMeasureDistanceMode()
      const mockEvent: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockEvent, mockProps)

      expect(mockProps.onEdit).toHaveBeenCalledWith({
        updatedData: mockProps.data,
        editType: EditType.addPosition,
        editContext: expect.objectContaining({
          distance: expect.any(Number) as number,
          finished: expect.any(Boolean) as boolean,
          positions: expect.any(Array) as unknown[],
        }) as Record<string, unknown>,
      })
    })
  })

  describe('handleKeyUp', () => {
    it('should handle Escape key to cancel measurement', () => {
      const mode = new CustomMeasureDistanceMode()
      const mockEvent = new KeyboardEvent('keyup', { key: 'Escape' })
      mockEvent.stopPropagation = vi.fn()

      mode.handleKeyUp(mockEvent, mockProps)

      // eslint-disable-next-line @typescript-eslint/unbound-method
      expect(mockEvent.stopPropagation).toHaveBeenCalled()
      expect(mockProps.onEdit).toHaveBeenCalledWith({
        updatedData: {
          type: 'FeatureCollection',
          features: [],
        },
        editType: EditType.cancel,
        editContext: {},
      })
    })

    it('should handle Enter key to finish measurement with multiple clicks', () => {
      const mode = new CustomMeasureDistanceMode()

      // Simulate adding two points by clicking twice
      const mockClickEvent1: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      const mockClickEvent2: BasePointerEvent = {
        mapCoords: [1, 1],
        screenCoords: [100, 100],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClickEvent1, mockProps)
      mode.handleClick(mockClickEvent2, mockProps)

      vi.clearAllMocks()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'Enter' })
      mockKeyEvent.stopPropagation = vi.fn()

      mode.handleKeyUp(mockKeyEvent, mockProps)

      // eslint-disable-next-line @typescript-eslint/unbound-method
      expect(mockKeyEvent.stopPropagation).toHaveBeenCalled()
      expect(mockProps.onEdit).toHaveBeenCalledWith({
        updatedData: mockProps.data,
        editType: EditType.finish,
        editContext: {
          finished: true,
        },
      })
      expect(mockProps.onUpdateCursor).toHaveBeenCalledWith('cell')
    })

    it('should handle Enter key with single click to cancel', () => {
      const mode = new CustomMeasureDistanceMode()

      // Simulate single click
      const mockClickEvent: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClickEvent, mockProps)
      vi.clearAllMocks()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'Enter' })
      mockKeyEvent.stopPropagation = vi.fn()

      mode.handleKeyUp(mockKeyEvent, mockProps)

      expect(mockProps.onEdit).toHaveBeenCalledWith(
        expect.objectContaining({
          updatedData: mockProps.data,
          editType: EditType.cancel,
          editContext: expect.objectContaining({
            finished: expect.any(Boolean) as boolean,
          }) as Record<string, unknown>,
        }),
      )
    })

    it('should return early when measurement session is finished', () => {
      const mode = new CustomMeasureDistanceMode()

      // First finish a measurement by pressing Enter with 2 points
      const mockClickEvent1: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      const mockClickEvent2: BasePointerEvent = {
        mapCoords: [1, 1],
        screenCoords: [100, 100],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClickEvent1, mockProps)
      mode.handleClick(mockClickEvent2, mockProps)

      const mockKeyEvent1 = new KeyboardEvent('keyup', { key: 'Enter' })
      mockKeyEvent1.stopPropagation = vi.fn()
      mode.handleKeyUp(mockKeyEvent1, mockProps)

      vi.clearAllMocks()

      // Try to use Escape after finishing - should return early
      const mockKeyEvent2 = new KeyboardEvent('keyup', { key: 'Escape' })
      mockKeyEvent2.stopPropagation = vi.fn()
      mode.handleKeyUp(mockKeyEvent2, mockProps)

      // Should return early and not call onEdit (but stopPropagation might still be called)
      expect(mockProps.onEdit).not.toHaveBeenCalled()
    })

    it('should handle unrecognized keys without calling onEdit', () => {
      const mode = new CustomMeasureDistanceMode()

      const mockKeyEvent = new KeyboardEvent('keyup', { key: 'a' })
      mockKeyEvent.stopPropagation = vi.fn()

      mode.handleKeyUp(mockKeyEvent, mockProps)

      // stopPropagation is called for any key
      // eslint-disable-next-line @typescript-eslint/unbound-method
      expect(mockKeyEvent.stopPropagation).toHaveBeenCalled()
      // onEdit should not be called for unrecognized keys
      expect(mockProps.onEdit).not.toHaveBeenCalled()
    })
  })

  describe('resetClickSequence', () => {
    it('should reset measurement state', () => {
      const mode = new CustomMeasureDistanceMode()

      // Set up some state
      const mockClickEvent: BasePointerEvent = {
        mapCoords: [0, 0],
        screenCoords: [0, 0],
        picks: [],
        sourceEvent: new MouseEvent('click'),
      }

      mode.handleClick(mockClickEvent, mockProps)

      // Reset
      mode.resetClickSequence()

      // Click sequence should be cleared
      expect(mode.getClickSequence()).toEqual([])
    })
  })

  describe('getTooltipText', () => {
    it('should format distance text for metric units', () => {
      const mode = new CustomMeasureDistanceMode()

      const result = mode.getTooltipText({
        meters: 1500,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['m-km'],
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
      expect(result).toContain('1,500')
    })

    it('should format distance text for imperial units', () => {
      const mode = new CustomMeasureDistanceMode()

      const result = mode.getTooltipText({
        meters: 1000,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['ft-mi'],
      })

      expect(result).toBeDefined()
      expect(typeof result).toBe('string')
    })

    it('should respect custom text options', () => {
      const mode = new CustomMeasureDistanceMode()

      const result = mode.getTooltipText({
        meters: 1234.567,
        locale: 'en-US',
        unit: MapMeasurementUnitAuto['m-km'],
        options: {
          minimumFractionDigits: 0,
          maximumFractionDigits: 0,
        },
      })

      expect(result).toBeDefined()
      expect(result).toContain('1,235')
    })
  })
})
