// lattice-map.jsx — Real Leaflet map with Carto dark/light basemap.
// Real geography, real zoom/pan, HTML node markers (divIcon),
// polyline mesh, animated drone marker positions.

const { useEffect, useRef, useState } = React;

const TILE_DARK  = "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}";
const TILE_LIGHT = "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}";
const TILE_ATTR  = "Tiles © Esri";

// Size-safe view change: Leaflet's flyTo/projection math NaNs on a 0×0 container.
function safeView(map, latlng, zoom, animate, duration) {
  if (!map) return;
  try {
    map.invalidateSize(false);
    const s = map.getSize();
    if (!s.x || !s.y) { requestAnimationFrame(() => safeView(map, latlng, zoom, false)); return; }
    if (animate) map.flyTo(latlng, zoom, { duration: duration || 1 });
    else map.setView(latlng, zoom, { animate: false });
  } catch (e) { try { map.setView(latlng, zoom, { animate: false }); } catch {} }
}

class MapBoundary extends React.Component {
  constructor(p) { super(p); this.state = { err: null }; }
  static getDerivedStateFromError(err) { return { err }; }
  componentDidCatch(err) { console.warn("map error (contained):", err && err.message); }
  render() {
    if (this.state.err) return (
      <div className={this.props.className || "map-wrap"} style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--muted-foreground)", fontSize: 14, minHeight: 120 }}>
        Map unavailable
        <button className="btn btn--secondary btn--sm" onClick={() => this.setState({ err: null })}>Retry</button>
      </div>
    );
    return this.props.children;
  }
}
window.LAT_TILES = { dark: TILE_DARK, light: TILE_LIGHT };

function WorldMap({ nodes, mesh, showMesh, flights, isDark, region, setSelected, selected, onOpenNode }) {
  const containerRef = useRef(null);
  const mapRef = useRef(null);
  const tileLayerRef = useRef(null);
  const meshLayerRef = useRef(null);
  const markersRef = useRef({});
  const droneMarkersRef = useRef({});
  const droneTrailsRef = useRef({});
  const [ready, setReady] = useState(false);
  const [tipPos, setTipPos] = useState(null);

  // ── Init ───────────────────────────────────────────────────────────
  useEffect(() => {
    if (mapRef.current || !containerRef.current) return;
    const map = L.map(containerRef.current, {
      center: [25, 10],
      zoom: 2,
      minZoom: 2,
      maxZoom: 9,
      zoomControl: false,
      attributionControl: true,
      worldCopyJump: true,
      preferCanvas: true,
      fadeAnimation: false,
      zoomAnimation: true,
    });
    mapRef.current = map;
    window.__map = map;

    const tile = L.tileLayer(isDark ? TILE_DARK : TILE_LIGHT, {
      attribution: TILE_ATTR,
      maxZoom: 16,
    }).addTo(map);
    tileLayerRef.current = tile;

    setReady(true);

    const ro = new ResizeObserver(() => { try { map.invalidateSize(false); } catch {} });
    ro.observe(containerRef.current);

    return () => { ro.disconnect(); map.remove(); mapRef.current = null; };
  }, []);

  // ── Theme swap ─────────────────────────────────────────────────────
  useEffect(() => {
    if (!mapRef.current || !tileLayerRef.current) return;
    tileLayerRef.current.setUrl(isDark ? TILE_DARK : TILE_LIGHT);
  }, [isDark]);

  // ── Region focus ──────────────────────────────────────────────────
  useEffect(() => {
    if (!mapRef.current || !region) return;
    const r = REGIONS[region]; if (!r) return;
    // r.center is [lng, lat] — Leaflet wants [lat, lng]
    safeView(mapRef.current, [r.center[1], r.center[0]], r.zoom + 0.5, true, 1.2);
  }, [region]);

  // ── Node markers ──────────────────────────────────────────────────
  useEffect(() => {
    if (!mapRef.current || !ready) return;
    const map = mapRef.current;

    // Clean up old
    Object.values(markersRef.current).forEach(m => map.removeLayer(m));
    markersRef.current = {};

    nodes.forEach(n => {
      const html = `
        <div class="lat-marker lat-marker--${n.type} lat-marker--${n.status}">
          <span class="lat-marker__halo"></span>
          <span class="lat-marker__core"></span>
          ${n.status !== "ok" ? '<span class="lat-marker__pulse"></span>' : ""}
          <span class="lat-marker__label">${n.id.slice(-5)}</span>
        </div>
      `;
      const icon = L.divIcon({
        className: "lat-marker-wrap",
        html,
        iconSize: [14, 14],
        iconAnchor: [7, 7],
      });
      const marker = L.marker([n.lat, n.lng], { icon, riseOnHover: true })
        .addTo(map)
        .on("mouseover", () => setSelected(n.id))
        .on("mouseout",  () => setSelected(null))
        .on("click",     () => onOpenNode && onOpenNode(n));
      markersRef.current[n.id] = marker;
    });
  }, [nodes, ready]);

  // ── Mesh polylines ────────────────────────────────────────────────
  useEffect(() => {
    if (!mapRef.current || !ready) return;
    const map = mapRef.current;
    if (meshLayerRef.current) map.removeLayer(meshLayerRef.current);
    const lines = mesh.map(([a, b]) =>
      L.polyline([[a.lat, a.lng], [b.lat, b.lng]], {
        color: "#7dd3fc",
        weight: 1,
        opacity: 0.5,
        dashArray: "3 4",
      })
    );
    const group = L.layerGroup(lines);
    if (showMesh) group.addTo(map);
    meshLayerRef.current = group;
  }, [mesh, showMesh, ready]);

  // ── Drone flights ─────────────────────────────────────────────────
  useEffect(() => {
    if (!mapRef.current || !ready) return;
    const map = mapRef.current;
    // Remove flights that no longer exist
    Object.keys(droneMarkersRef.current).forEach(id => {
      if (!flights.find(f => f.id === id)) {
        map.removeLayer(droneMarkersRef.current[id]);
        if (droneTrailsRef.current[id]) map.removeLayer(droneTrailsRef.current[id]);
        delete droneMarkersRef.current[id];
        delete droneTrailsRef.current[id];
      }
    });
    // Add new flights
    flights.forEach(f => {
      if (droneMarkersRef.current[f.id]) return;
      const icon = L.divIcon({
        className: "lat-drone-wrap",
        html: `
          <div class="lat-drone">
            <span class="lat-drone__icon">▲</span>
            <span class="lat-drone__label">${f.drone.id.slice(-2)}</span>
          </div>`,
        iconSize: [14, 14],
        iconAnchor: [7, 7],
      });
      // Leaflet wants [lat, lng]; f.from/to are [lng, lat]
      const fromLL = [f.from[1], f.from[0]], toLL = [f.to[1], f.to[0]];
      const marker = L.marker(fromLL, { icon }).addTo(map);
      const trail = L.polyline([fromLL, toLL], { color: "#fbbf24", weight: 1.2, opacity: 0.65, dashArray: "3 4" }).addTo(map);
      droneMarkersRef.current[f.id] = marker;
      droneTrailsRef.current[f.id] = trail;
    });
  }, [flights, ready]);

  // ── Drone animation tick ──────────────────────────────────────────
  useEffect(() => {
    if (!ready || flights.length === 0) return;
    let raf;
    const tick = () => {
      flights.forEach(f => {
        const m = droneMarkersRef.current[f.id]; if (!m) return;
        const elapsed = (Date.now() - f.startedAt) / 8000;
        const p = Math.min(1, elapsed);
        const lng = f.from[0] + (f.to[0] - f.from[0]) * p;
        const lat = f.from[1] + (f.to[1] - f.from[1]) * p;
        m.setLatLng([lat, lng]);
        // Bearing
        const bearing = Math.atan2(f.to[0] - f.from[0], f.to[1] - f.from[1]) * 180 / Math.PI;
        const el = m.getElement()?.querySelector(".lat-drone__icon");
        if (el) el.style.transform = `translate(-50%, -50%) rotate(${bearing}deg)`;
      });
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [flights, ready]);

  // ── Tooltip ───────────────────────────────────────────────────────
  useEffect(() => {
    if (!selected || !mapRef.current) { setTipPos(null); return; }
    const map = mapRef.current;
    const node = nodes.find(n => n.id === selected); if (!node) return;
    const update = () => {
      try {
        const s = map.getSize(); if (!s.x || !s.y) return;
        const p = map.latLngToContainerPoint([node.lat, node.lng]);
        if (Number.isFinite(p.x) && Number.isFinite(p.y)) setTipPos({ x: p.x, y: p.y });
      } catch {}
    };
    update();
    map.on("move zoom", update);
    return () => map.off("move zoom", update);
  }, [selected, nodes]);

  const sel = nodes.find(n => n.id === selected);

  return (
    <div className="map-wrap">
      <div ref={containerRef} className="map-container"/>

      <div className="map-wrap__head">
        <div className="map-wrap__chips">
          <span className="pill pill--info"><span className="dot dot--pulse" style={{ color: "var(--info)" }}/> LIVE</span>
          <span className="pill">{nodes.length} NODES</span>
          <span className="pill pill--warn">{nodes.filter(n => n.status === "warn").length} WARN</span>
          <span className="pill pill--crit"><span className="dot" style={{ color: "var(--destructive)" }}/> {nodes.filter(n => n.status === "crit").length} CRIT</span>
          <span className="pill pill--mute">MESH OK · p99 18ms</span>
        </div>
        <div className="map-wrap__zoom">
          <button aria-label="zoom in"  onClick={() => mapRef.current?.zoomIn()}>+</button>
          <button aria-label="zoom out" onClick={() => mapRef.current?.zoomOut()}>−</button>
          <button aria-label="reset"    onClick={() => {
            const r = REGIONS[region] || REGIONS["Global"];
            safeView(mapRef.current, [r.center[1], r.center[0]], r.zoom + 0.5, true, 0.6);
          }}>⊙</button>
        </div>
      </div>

      {sel && tipPos && (
        <div className="node-tip" style={{
          position: "absolute", left: tipPos.x, top: tipPos.y,
          transform: "translate(-50%, calc(-100% - 18px))", pointerEvents: "none", zIndex: 600,
        }}>
          <div className="node-tip__title">
            <span className="dot" style={{ color: statusColor(sel.status) }}/>
            {sel.city}
            <span className={"pill " + statusPill(sel.status)}>{sel.status}</span>
          </div>
          <div className="node-tip__id">{sel.id} · {sel.type} · {sel.country}</div>
          <div className="node-tip__grid">
            <span className="node-tip__k">cpu</span>      <span className="node-tip__v">{sel.cpu}%</span>
            <span className="node-tip__k">memory</span>   <span className="node-tip__v">{sel.mem}%</span>
            <span className="node-tip__k">ingest</span>   <span className="node-tip__v">{sel.ingest.toFixed(1)} Mpps</span>
            <span className="node-tip__k">coords</span>   <span className="node-tip__v">{sel.lat.toFixed(2)}, {sel.lng.toFixed(2)}</span>
          </div>
        </div>
      )}

      <div className="map-wrap__legend">
        <div className="map-wrap__legend-row"><span className="dot" style={{ color: "var(--info)" }}/> fusion · edge · ok</div>
        <div className="map-wrap__legend-row"><span className="dot" style={{ color: "var(--warning)" }}/> warn · throttled</div>
        <div className="map-wrap__legend-row"><span className="dot" style={{ color: "var(--destructive)" }}/> crit · isolated / breach</div>
        <div className="map-wrap__legend-row"><span style={{ display:"inline-block", width:14, height:1, borderTop:"1px dashed #7dd3fc", marginRight:4 }}/> zenoh mesh</div>
        <div className="map-wrap__legend-row"><span style={{ display:"inline-block", width:14, height:1, borderTop:"1px dashed #fbbf24", marginRight:4 }}/> flight response path</div>
      </div>
    </div>
  );
}

Object.assign(window, { MapBoundary, safeView, WorldMap });
