import type { ViewHook } from "phoenix_live_view"
import { updateGridOverlay } from "./maidenhead_grid"

interface LocationMapHook extends ViewHook {
  map: L.Map
  marker: L.Marker
  gridLayer: L.LayerGroup
  gridVisible: boolean
  visibilityHandler: (() => void) | null
}

// Same red dot the old CircleMarker rendered, recreated as a divIcon so
// we can drop it on a real L.Marker (CircleMarker has no `dragging`
// handler and can't be made draggable).
const dotIcon = L.divIcon({
  html:
    '<div style="width:16px;height:16px;border-radius:50%;background:#ef4444;border:2px solid #fff;box-shadow:0 0 4px rgba(0,0,0,.4)"></div>',
  className: "rover-location-dot",
  iconSize: [16, 16],
  iconAnchor: [8, 8]
})

const draggingDotIcon = L.divIcon({
  html:
    '<div style="width:18px;height:18px;border-radius:50%;background:#f59e0b;border:2px solid #fff;box-shadow:0 0 6px rgba(0,0,0,.5);cursor:grab"></div>',
  className: "rover-location-dot rover-location-dot-edit",
  iconSize: [18, 18],
  iconAnchor: [9, 9]
})

export const LocationMap: Partial<LocationMapHook> = {
  mounted(this: LocationMapHook) {
    const lat = parseFloat(this.el.dataset.lat || "0")
    const lon = parseFloat(this.el.dataset.lon || "0")

    const osm = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      maxZoom: 19
    })
    const satellite = L.tileLayer(
      "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
      { maxZoom: 21, maxNativeZoom: 19 }
    )
    const topo = L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", {
      maxZoom: 17
    })

    const map = L.map(this.el, {
      center: [lat, lon],
      zoom: 16,
      zoomControl: true,
      attributionControl: false
    })
    this.map = map

    satellite.addTo(map)

    L.control.layers(
      { "Satellite": satellite, "OSM": osm, "Topo": topo },
      {},
      { position: "topright", collapsed: false }
    ).addTo(map)

    // Draggable Marker (not CircleMarker — that one can't be dragged).
    // Server flips draggability via `set_marker_draggable` push_event.
    this.marker = L.marker([lat, lon], { icon: dotIcon, draggable: false }).addTo(map)
    this.marker.bindPopup(`${lat.toFixed(6)}, ${lon.toFixed(6)}`)

    this.marker.on("drag", (e: L.LeafletEvent) => {
      const ll = (e.target as L.Marker).getLatLng()
      this.marker.setPopupContent(`${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}`)
    })

    // dragend pushes the new coords up so the LiveView can update its
    // `working_lat` / `working_lon` and render the live grid preview.
    this.marker.on("dragend", (e: L.DragEndEvent) => {
      const ll = (e.target as L.Marker).getLatLng()
      this.pushEvent("location_dragged", { lat: ll.lat, lon: ll.lng })
    })

    // Maidenhead grid overlay + toggle
    this.gridLayer = L.layerGroup().addTo(map)
    this.gridVisible = true
    updateGridOverlay(map, this.gridLayer)
    map.on("moveend", () => {
      if (this.gridVisible) updateGridOverlay(map, this.gridLayer)
    })

    const GridToggle = L.Control.extend({
      options: { position: "topright" as const },
      onAdd: () => {
        const btn = L.DomUtil.create("button", "leaflet-bar leaflet-control")
        btn.innerHTML = "Grid"
        btn.title = "Toggle Maidenhead grid"
        btn.style.cssText =
          "background:#fff;padding:4px 8px;font-size:12px;cursor:pointer;font-weight:600;"
        L.DomEvent.disableClickPropagation(btn)
        btn.onclick = () => {
          this.gridVisible = !this.gridVisible
          if (this.gridVisible) {
            this.gridLayer.addTo(map)
            updateGridOverlay(map, this.gridLayer)
            btn.style.opacity = "1"
          } else {
            this.gridLayer.remove()
            btn.style.opacity = "0.5"
          }
        }
        return btn
      }
    })
    new GridToggle().addTo(map)

    // Server → hook events for edit mode.
    this.handleEvent("set_marker_draggable", ({ draggable }: { draggable: boolean }) => {
      if (draggable) {
        this.marker.setIcon(draggingDotIcon)
        this.marker.dragging?.enable()
      } else {
        this.marker.setIcon(dotIcon)
        this.marker.dragging?.disable()
      }
    })

    this.handleEvent("reset_marker", ({ lat, lon }: { lat: number; lon: number }) => {
      this.marker.setLatLng([lat, lon])
      this.marker.setPopupContent(`${lat.toFixed(6)}, ${lon.toFixed(6)}`)
    })

    // LiveView reconnect / tab visibility — Leaflet needs invalidateSize.
    this.visibilityHandler = () => {
      if (document.visibilityState === "visible") this.map.invalidateSize()
    }
    document.addEventListener("visibilitychange", this.visibilityHandler)

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

  reconnected(this: LocationMapHook) {
    this.map?.invalidateSize()
  },

  destroyed(this: LocationMapHook) {
    if (this.visibilityHandler) {
      document.removeEventListener("visibilitychange", this.visibilityHandler)
      this.visibilityHandler = null
    }
    this.map?.remove()
  }
}
