import {formatDistanceKm} from "./format"

interface CoverageCell {
  lat: number
  lon: number
  color: string
  label: string
  rx_dbm: number
  distance_km: number
  score: number
}

interface ToggleCoveragePayload {
  show: boolean
}

interface LoadCoveragePayload {
  grid_step: number
  cells: CoverageCell[]
  show: boolean
}

interface BeaconMapHook extends ViewHook {
  mounted(this: BeaconMapHook): void
}

export const BeaconMap = {
  mounted(this: BeaconMapHook) {
    const dataset = this.el.dataset
    const lat = parseFloat(dataset.lat!)
    const lon = parseFloat(dataset.lon!)
    const label = dataset.label || ""
    const onTheAir = dataset.onTheAir === "true"

    const map = L.map(this.el, {
      zoomControl: true,
      attributionControl: false,
      scrollWheelZoom: false
    }).setView([lat, lon], 11)

    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      maxZoom: 19
    }).addTo(map)

    // Beacon marker on top.
    const markerColor = onTheAir ? "#16a34a" : "#94a3b8"
    const markerFill = onTheAir ? "#22c55e" : "#cbd5e1"
    L.circleMarker([lat, lon], {
      radius: 7,
      color: markerColor,
      fillColor: markerFill,
      fillOpacity: 1.0,
      weight: 2
    }).addTo(map).bindTooltip(label, {
      permanent: true,
      direction: "top",
      offset: [0, -10]
    })

    // Coverage layer is lazy — populated on the first `load_coverage`
    // event, which the server only sends when the user flips the
    // toggle on. Keeping this off the mount path avoids parsing tens
    // of thousands of cells into Leaflet rects on every page load.
    let cellLayer: L.LayerGroup | null = null
    let cellBounds: [number, number][] = []

    const buildLayer = (cells: CoverageCell[], step: number): void => {
      if (cellLayer) {
        cellLayer.clearLayers()
      } else {
        cellLayer = L.layerGroup()
      }
      cellBounds = []
      const half = step / 2.0

      for (const c of cells) {
        const sw: [number, number] = [c.lat - half, c.lon - half]
        const ne: [number, number] = [c.lat + half, c.lon + half]
        const rect = L.rectangle([sw, ne], {
          color: c.color,
          weight: 0,
          fillColor: c.color,
          fillOpacity: 0.55,
          interactive: true
        })
        rect.bindTooltip(
          `${c.label}<br>${c.rx_dbm} dBm<br>${formatDistanceKm(c.distance_km)} · score ${c.score}`,
          {sticky: true}
        )
        rect.addTo(cellLayer)
        cellBounds.push(sw, ne)
      }
    }

    const showLayer = (): void => {
      if (!cellLayer) return
      if (!map.hasLayer(cellLayer)) cellLayer.addTo(map)
      if (cellBounds.length > 0) {
        map.fitBounds(L.latLngBounds(cellBounds), {padding: [20, 20]})
        map.setZoom(map.getZoom() + 2)
      }
    }

    const hideLayer = (): void => {
      if (cellLayer && map.hasLayer(cellLayer)) map.removeLayer(cellLayer)
      map.setView([lat, lon], 11)
    }

    this.handleEvent("load_coverage", ({grid_step, cells, show}: LoadCoveragePayload) => {
      buildLayer(cells, grid_step)
      if (show) showLayer()
    })

    this.handleEvent("toggle_coverage", ({show}: ToggleCoveragePayload) => {
      if (show) {
        showLayer()
      } else {
        hideLayer()
      }
    })

    setTimeout(() => map.invalidateSize(), 50)
  }
}
