import {describe, expect, test} from 'bun:test'

import {
  bboxAroundKm,
  crossesAntimeridian,
  parseBbox,
  serializeBbox,
} from './geo'

describe('parseBbox', () => {
  test('parses a valid csv into lng-first tuple', () => {
    expect(parseBbox('-122.5,37.7,-122.3,37.8')).toEqual([
      -122.5, 37.7, -122.3, 37.8,
    ])
  })

  test('allows an antimeridian-crossing box (minLng > maxLng)', () => {
    expect(parseBbox('170,-10,-170,10')).toEqual([170, -10, -170, 10])
  })

  test('rejects an inverted latitude span', () => {
    expect(parseBbox('-10,20,10,-20')).toBeNull()
  })

  test('rejects out-of-range coordinates', () => {
    expect(parseBbox('-200,0,10,10')).toBeNull()
    expect(parseBbox('0,-100,10,10')).toBeNull()
  })

  test('rejects the wrong number of parts', () => {
    expect(parseBbox('1,2,3')).toBeNull()
    expect(parseBbox('1,2,3,4,5')).toBeNull()
  })

  test('rejects non-numeric parts', () => {
    expect(parseBbox('a,b,c,d')).toBeNull()
    expect(parseBbox('1,2,3,x')).toBeNull()
  })
})

describe('crossesAntimeridian', () => {
  test('true when the box wraps past ±180', () => {
    expect(crossesAntimeridian([170, -10, -170, 10])).toBe(true)
  })

  test('false for an ordinary box', () => {
    expect(crossesAntimeridian([-122.5, 37.7, -122.3, 37.8])).toBe(
      false,
    )
  })
})

describe('serializeBbox', () => {
  test('round-trips through parseBbox', () => {
    const csv = '-122.5,37.7,-122.3,37.8'
    expect(serializeBbox(parseBbox(csv)!)).toBe(csv)
  })
})

describe('bboxAroundKm', () => {
  test('spans ~1 degree each way per ~111km at the equator', () => {
    const [minLng, minLat, maxLng, maxLat] = bboxAroundKm(
      {latitude: 0, longitude: 0},
      111.32,
    )
    expect(minLat).toBeCloseTo(-1, 2)
    expect(maxLat).toBeCloseTo(1, 2)
    expect(minLng).toBeCloseTo(-1, 2)
    expect(maxLng).toBeCloseTo(1, 2)
  })

  test('widens longitude span with latitude', () => {
    const [minLng, minLat, maxLng, maxLat] = bboxAroundKm(
      {latitude: 60, longitude: 18},
      111.32,
    )
    const latSpan = maxLat - minLat
    const lngSpan = maxLng - minLng
    // cos(60°) = 0.5, so a km covers twice the longitude degrees
    expect(lngSpan).toBeCloseTo(latSpan * 2, 1)
  })

  test('clamps to valid coordinate ranges near the pole', () => {
    const [minLng, minLat, maxLng, maxLat] = bboxAroundKm(
      {latitude: 89.9, longitude: 179.9},
      50,
    )
    expect(maxLat).toBe(90)
    expect(maxLng).toBe(180)
    expect(minLat).toBeLessThan(89.9)
    expect(minLng).toBeGreaterThanOrEqual(-180)
  })

  test('always contains the center', () => {
    const [minLng, minLat, maxLng, maxLat] = bboxAroundKm(
      {latitude: 59.325, longitude: 18.07},
      10,
    )
    expect(minLat).toBeLessThan(59.325)
    expect(maxLat).toBeGreaterThan(59.325)
    expect(minLng).toBeLessThan(18.07)
    expect(maxLng).toBeGreaterThan(18.07)
  })
})
