// Map used by /beacons — renders every approved beacon as a circle
// marker with a popup linking to the detail page.

interface BeaconPoint {
  id: string
  callsign: string
  lat: number
  lon: number
  frequency_mhz: number
  grid: string
  keying: string
  on_the_air: boolean
}

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

function formatFreq(freq: number): string {
  // Mirror Microwaveprop.Beacons.Beacon.format_freq/1 — integers get
  // comma separators, floats keep up to three decimals trimmed.
  if (Number.isInteger(freq)) return freq.toLocaleString("en-US")
  const trimmed = freq.toFixed(3).replace(/\.?0+$/, "")
  const [whole, frac] = trimmed.split(".")
  const wholeFormatted = parseInt(whole, 10).toLocaleString("en-US")
  return frac ? `${wholeFormatted}.${frac}` : wholeFormatted
}

function escapeHtml(str: string): string {
  return str.replace(/[&<>"']/g, ch => {
    switch (ch) {
      case "&": return "&amp;"
      case "<": return "&lt;"
      case ">": return "&gt;"
      case "\"": return "&quot;"
      case "'": return "&#39;"
      default: return ch
    }
  })
}

export const BeaconsListMap = {
  mounted(this: BeaconsListMapHook) {
    const beacons: BeaconPoint[] = JSON.parse(this.el.dataset.beacons || "[]")

    // DFW fallback so the map still renders when no beacons exist yet.
    const defaultCenter: [number, number] = [32.897, -97.038]

    const map = L.map(this.el, {
      zoomControl: true,
      scrollWheelZoom: false,
      attributionControl: true
    }).setView(defaultCenter, 6)

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

    const bounds: [number, number][] = []

    for (const b of beacons) {
      if (!Number.isFinite(b.lat) || !Number.isFinite(b.lon)) continue

      const color = b.on_the_air ? "#16a34a" : "#94a3b8"
      const fill = b.on_the_air ? "#22c55e" : "#cbd5e1"

      const popupHtml =
        `<div style="font-size:12px;line-height:1.5;min-width:160px;">` +
        `<strong>${escapeHtml(b.callsign)}</strong>` +
        (b.on_the_air
          ? ""
          : ` <span style="color:#ca8a04;">&middot; off-air</span>`) +
        `<br/>` +
        `${formatFreq(b.frequency_mhz)} MHz<br/>` +
        `Grid ${escapeHtml(b.grid)}<br/>` +
        `Keying: ${escapeHtml(b.keying)}<br/>` +
        `<a href="/beacons/${b.id}" style="color:#3b82f6;">View details &rarr;</a>` +
        `</div>`

      L.circleMarker([b.lat, b.lon], {
        radius: 7,
        color,
        fillColor: fill,
        fillOpacity: 0.9,
        weight: 2
      })
        .bindPopup(popupHtml, { closeButton: true })
        .bindTooltip(b.callsign, { direction: "top", offset: [0, -8] })
        .addTo(map)

      bounds.push([b.lat, b.lon])
    }

    if (bounds.length > 0) {
      map.fitBounds(L.latLngBounds(bounds), { padding: [30, 30], maxZoom: 10 })
    }

    // Invalidate once after mount in case the parent was hidden.
    setTimeout(() => map.invalidateSize(), 50)
  }
}
