import type {Bbox} from '@crumbs/core/geo'

// the visible map region as reported by the camera, plus the math to turn it
// into a fetch bbox and decide when a new region needs a refetch

export type ClusterRegion = {
  center: {latitude: number; longitude: number}
  latitudeDelta: number
  longitudeDelta: number
  zoom: number
}

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

export const regionToBbox = (region: ClusterRegion): Bbox => {
  const {latitude, longitude} = region.center
  const halfLat = region.latitudeDelta / 2
  const halfLng = region.longitudeDelta / 2
  return [
    clamp(longitude - halfLng, -180, 180),
    clamp(latitude - halfLat, -90, 90),
    clamp(longitude + halfLng, -180, 180),
    clamp(latitude + halfLat, -90, 90),
  ]
}

// grow a bbox by a fraction of its span on every side so pins exist just
// off-screen and small pans don't refetch; clamped to valid ranges
export const padBbox = (bbox: Bbox, factor: number): Bbox => {
  const [minLng, minLat, maxLng, maxLat] = bbox
  const padLng = (maxLng - minLng) * factor
  const padLat = (maxLat - minLat) * factor
  return [
    clamp(minLng - padLng, -180, 180),
    clamp(minLat - padLat, -90, 90),
    clamp(maxLng + padLng, -180, 180),
    clamp(maxLat + padLat, -90, 90),
  ]
}

export const bboxContains = (outer: Bbox, inner: Bbox): boolean =>
  inner[0] >= outer[0] &&
  inner[1] >= outer[1] &&
  inner[2] <= outer[2] &&
  inner[3] <= outer[3]

// integer zoom step; while a result is capped, zooming in past a step is
// worth a refetch since a tighter bbox may return the full set
export const zoomBand = (zoom: number): number => Math.floor(zoom)
