import { describe, it, expect, vi } from 'vitest'
import {
  convertMetersTo,
  getTextLayerFormattedDistance,
  getDeckController,
  buildTooltip,
} from './utils'
import { MapMeasurementUnit, MapMeasurementUnitAuto } from './const'

describe('convertMetersTo', () => {
  describe('length conversions', () => {
    it('should convert meters to meters', () => {
      const result = convertMetersTo(1000, MapMeasurementUnit.meter, 'length')
      expect(result).toBe(1000)
    })

    it('should convert meters to kilometers', () => {
      const result = convertMetersTo(
        1000,
        MapMeasurementUnit.kilometer,
        'length',
      )
      expect(result).toBe(1)
    })

    it('should convert meters to feet', () => {
      const result = convertMetersTo(1000, MapMeasurementUnit.feet, 'length')
      expect(result).toBeCloseTo(3280.84, 1)
    })

    it('should convert meters to miles', () => {
      const result = convertMetersTo(1609.34, MapMeasurementUnit.mile, 'length')
      expect(result).toBeCloseTo(1, 1)
    })
  })

  describe('area conversions', () => {
    it('should convert square meters to square meters', () => {
      const result = convertMetersTo(10000, MapMeasurementUnit.meter, 'area')
      expect(result).toBe(10000)
    })

    it('should convert square meters to square kilometers', () => {
      const result = convertMetersTo(
        1000000,
        MapMeasurementUnit.kilometer,
        'area',
      )
      expect(result).toBe(1)
    })

    it('should convert square meters to square feet', () => {
      const result = convertMetersTo(1000, MapMeasurementUnit.feet, 'area')
      expect(result).toBeCloseTo(10763.91, 1)
    })

    it('should convert square meters to square miles', () => {
      const result = convertMetersTo(2589988, MapMeasurementUnit.mile, 'area')
      expect(result).toBeCloseTo(1, 1)
    })
  })

  describe('error handling', () => {
    it('should throw error for invalid dimension', () => {
      expect(() =>
        convertMetersTo(1000, MapMeasurementUnit.meter, 'invalid' as never),
      ).toThrow('Invalid dimension')
    })

    it('should throw error for unsupported unit in area conversion', () => {
      expect(() =>
        convertMetersTo(1000, 'invalid-unit' as never, 'area'),
      ).toThrow('cannot be converted to area')
    })
  })
})

describe('getTextLayerFormattedDistance', () => {
  const locale = 'en-US'

  describe('metric auto units (m-km)', () => {
    it('should format small distances in meters', () => {
      const result = getTextLayerFormattedDistance({
        meters: 500,
        unit: MapMeasurementUnitAuto['m-km'],
        locale,
      })
      expect(result).toContain('500')
      expect(result.toLowerCase()).toMatch(/m/)
    })

    it('should format large distances in kilometers', () => {
      const result = getTextLayerFormattedDistance({
        meters: 15000,
        unit: MapMeasurementUnitAuto['m-km'],
        locale,
      })
      expect(result).toContain('15')
      expect(result.toLowerCase()).toMatch(/km/)
    })

    it('should use custom autoSplitNumber threshold', () => {
      const result = getTextLayerFormattedDistance({
        meters: 5000,
        unit: MapMeasurementUnitAuto['m-km'],
        locale,
        options: {
          autoSplitNumber: 3000,
        },
      })
      expect(result.toLowerCase()).toMatch(/km/)
    })
  })

  describe('imperial auto units (ft-mi)', () => {
    it('should format small distances in feet', () => {
      const result = getTextLayerFormattedDistance({
        meters: 500,
        unit: MapMeasurementUnitAuto['ft-mi'],
        locale,
      })
      expect(result).toContain('1,640')
      expect(result.toLowerCase()).toMatch(/ft/)
    })

    it('should format large distances in miles', () => {
      const result = getTextLayerFormattedDistance({
        meters: 20000,
        unit: MapMeasurementUnitAuto['ft-mi'],
        locale,
      })
      expect(result).toContain('12')
      expect(result.toLowerCase()).toMatch(/mi/)
    })
  })

  describe('fixed units', () => {
    it('should format in fixed meters', () => {
      const result = getTextLayerFormattedDistance({
        meters: 1500,
        unit: MapMeasurementUnit.meter,
        locale,
      })
      expect(result).toContain('1,500')
    })

    it('should format in fixed kilometers', () => {
      const result = getTextLayerFormattedDistance({
        meters: 15000,
        unit: MapMeasurementUnit.kilometer,
        locale,
      })
      expect(result).toContain('15')
    })

    it('should format in fixed feet', () => {
      const result = getTextLayerFormattedDistance({
        meters: 1000,
        unit: MapMeasurementUnit.feet,
        locale,
      })
      expect(result).toContain('3,280')
    })

    it('should format in fixed miles', () => {
      const result = getTextLayerFormattedDistance({
        meters: 10000,
        unit: MapMeasurementUnit.mile,
        locale,
      })
      expect(result).toContain('6')
    })
  })

  describe('area formatting', () => {
    it('should add superscript 2 for area dimension', () => {
      const result = getTextLayerFormattedDistance({
        meters: 10000,
        unit: MapMeasurementUnitAuto['m-km'],
        locale,
        dimension: 'area',
      })
      expect(result).toContain('2')
    })

    it('should format area in square kilometers', () => {
      const result = getTextLayerFormattedDistance({
        meters: 2000000,
        unit: MapMeasurementUnitAuto['m-km'],
        locale,
        dimension: 'area',
      })
      expect(result).toContain('2')
      expect(result.toLowerCase()).toMatch(/km/)
    })
  })

  describe('custom options', () => {
    it('should use custom formatter when provided', () => {
      const customFormatter = vi.fn((value) => `Custom: ${value}`)
      const result = getTextLayerFormattedDistance({
        meters: 1000,
        unit: MapMeasurementUnit.meter,
        locale,
        options: {
          customFormatter,
        },
      })
      expect(customFormatter).toHaveBeenCalled()
      expect(result).toContain('Custom:')
    })

    it('should respect custom Intl options', () => {
      const result = getTextLayerFormattedDistance({
        meters: 1234.567,
        unit: MapMeasurementUnit.meter,
        locale,
        options: {
          minimumFractionDigits: 0,
          maximumFractionDigits: 0,
        },
      })
      expect(result).toContain('1,235')
      expect(result).not.toMatch(/\\./)
    })
  })
})

describe('getDeckController', () => {
  it('should return controller with doubleClickZoom when controller is true', () => {
    const result = getDeckController(true, { hasDoubleClickZoom: true })
    expect(result).toEqual({ doubleClickZoom: true })
  })

  it('should return controller with doubleClickZoom when controller is false', () => {
    const result = getDeckController(false, { hasDoubleClickZoom: false })
    expect(result).toEqual({ doubleClickZoom: false })
  })

  it('should merge existing controller options with doubleClickZoom', () => {
    const existingController = {
      scrollZoom: true,
      dragPan: true,
      keyboard: false,
    }
    const result = getDeckController(existingController, {
      hasDoubleClickZoom: true,
    })
    expect(result).toEqual({
      scrollZoom: true,
      dragPan: true,
      keyboard: false,
      doubleClickZoom: true,
    })
  })

  it('should override doubleClickZoom in existing controller', () => {
    const existingController = {
      doubleClickZoom: true,
      scrollZoom: false,
    }
    const result = getDeckController(existingController, {
      hasDoubleClickZoom: false,
    })
    expect(result).toEqual({
      doubleClickZoom: false,
      scrollZoom: false,
    })
  })
})

describe('buildTooltip', () => {
  const position = [10, 20] as [number, number]
  const mockConfig = {
    turfOptions: {
      units: 'kilometers' as const,
    },
  }

  it('should return tooltip array for positive values', () => {
    const result = buildTooltip(5.5, position, mockConfig)
    expect(result).toHaveLength(1)
    expect(result[0]).toEqual({
      position,
      text: '5.50 kilometers',
    })
  })

  it('should format value with 2 decimal places', () => {
    const result = buildTooltip(3.14159, position, mockConfig)
    expect(result[0]?.text).toBe('3.14 kilometers')
  })

  it('should return empty array for zero value', () => {
    const result = buildTooltip(0, position, mockConfig)
    expect(result).toEqual([])
  })

  it('should return empty array for negative value', () => {
    const result = buildTooltip(-5, position, mockConfig)
    expect(result).toEqual([])
  })

  it('should use custom formatTooltip when provided', () => {
    const customFormatter = vi.fn((value) => `Distance: ${value}km`)
    const config = {
      ...mockConfig,
      formatTooltip: customFormatter,
    }
    const result = buildTooltip(10, position, config)
    expect(customFormatter).toHaveBeenCalledWith(10)
    expect(result[0]?.text).toBe('Distance: 10km')
  })

  it('should call measurementCallback when provided', () => {
    const measurementCallback = vi.fn()
    const config = {
      ...mockConfig,
      measurementCallback,
    }
    buildTooltip(15.5, position, config)
    expect(measurementCallback).toHaveBeenCalledWith(15.5)
  })

  it('should not call measurementCallback for zero value', () => {
    const measurementCallback = vi.fn()
    const config = {
      ...mockConfig,
      measurementCallback,
    }
    buildTooltip(0, position, config)
    expect(measurementCallback).not.toHaveBeenCalled()
  })

  it('should handle config without turfOptions', () => {
    const result = buildTooltip(5.5, position, {} as never)
    expect(result).toHaveLength(1)
    expect(result[0]?.text).toContain('5.50')
  })

  it('should handle config with undefined turfOptions', () => {
    const config = {
      turfOptions: undefined,
    }
    const result = buildTooltip(5.5, position, config as never)
    expect(result).toHaveLength(1)
    expect(result[0]?.text).toContain('5.50')
    expect(result[0]?.text).toContain('undefined')
  })
})
