// bounding box in geojson/mapbox order (lng first); the wire format shared
// by the map endpoint and its clients

export type Bbox = [
  minLng: number,
  minLat: number,
  maxLng: number,
  maxLat: number,
]

const inLngRange = (n: number): boolean => n >= -180 && n <= 180
const inLatRange = (n: number): boolean => n >= -90 && n <= 90

// parse a "minLng,minLat,maxLng,maxLat" csv, validating ranges and lat order.
// a box spanning the antimeridian (minLng > maxLng) is allowed; an inverted
// latitude span is not. returns null on any malformed input
export const parseBbox = (csv: string): Bbox | null => {
  const parts = csv.split(',').map(Number)
  if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n)))
    return null
  const [minLng, minLat, maxLng, maxLat] = parts as Bbox
  if (!inLngRange(minLng) || !inLngRange(maxLng)) return null
  if (!inLatRange(minLat) || !inLatRange(maxLat)) return null
  if (minLat > maxLat) return null
  return [minLng, minLat, maxLng, maxLat]
}

export const serializeBbox = (bbox: Bbox): string => bbox.join(',')

// a box crosses the antimeridian when its western edge sits east of its
// eastern edge, so its longitude span wraps past ±180
export const crossesAntimeridian = (bbox: Bbox): boolean =>
  bbox[0] > bbox[2]

const KM_PER_DEG_LAT = 111.32

// keep the longitude span finite as cos(lat) approaches 0 at the poles
const MIN_COS_LAT = 0.01

const clamp = (n: number, min: number, max: number): number =>
  Math.min(Math.max(n, min), max)

// bounding box covering a radius around a point. edges clamp at the
// coordinate ranges rather than wrapping the antimeridian, so it is only
// suitable for modest radii away from ±180
export const bboxAroundKm = (
  center: {latitude: number; longitude: number},
  radiusKm: number,
): Bbox => {
  const dLat = radiusKm / KM_PER_DEG_LAT
  const cosLat = Math.max(
    Math.cos((center.latitude * Math.PI) / 180),
    MIN_COS_LAT,
  )
  const dLng = radiusKm / (KM_PER_DEG_LAT * cosLat)
  return [
    clamp(center.longitude - dLng, -180, 180),
    clamp(center.latitude - dLat, -90, 90),
    clamp(center.longitude + dLng, -180, 180),
    clamp(center.latitude + dLat, -90, 90),
  ]
}
