/* ============================================================
   QuindioMap — Mapa del departamento sobre Google Maps JS API.
   Reemplaza el mapa SVG esquemático. Mismo contrato de props que antes,
   para no tocar las pantallas que lo montan (mapa / rebote / candado).

   Modos:
     overview — todos los nodos (markers por status), clic → onNodeClick
     pulso    — trayectoria de la placa (polilínea por capturas) + última captura
     rebote   — corredores proyectados (polilíneas origen→nodosClave) + radio
     candado  — corredores atenuados + checkpoints seleccionables

   PARTE A: los corredores se dibujan como polilíneas rectas origen→nodos clave
   (datos del mock). PARTE B: el backend devuelve polilíneas reales (Routes API)
   en `projection.corridors[].polyline` y este componente las decodifica.
   ============================================================ */

// Colores del design system (guardian-tokens.css) — en hex para los Symbols.
const STATUS_COLOR = { active: "#4ADE80", alert: "#E5333F", inactive: "#6B7682", maintenance: "#F2C61D" };
const CONF_COLOR   = { alto: "#4ADE80", medio: "#F2C61D", bajo: "#FF8A3D" };
const PRIO_COLOR   = { "crítica": "#E5333F", critica: "#E5333F", alta: "#F2C61D", media: "#4FD4FF", baja: "#6B7682" };
const ACCENT = "#4FD4FF";

// Límite de navegación: solo el EJE CAFETERO (Quindío + Risaralda + Caldas). El mapa
// no deja salir de este recuadro (strictBounds). Contiene de sobra los nodos del Quindío.
const EJE_CAFETERO_BOUNDS = { north: 5.85, south: 4.05, west: -76.25, east: -75.05 };

// Botones de zoom propios: estilo del diseño (glass oscuro + borde + hover acento)
// y separados del borde del mapa. Reemplazan el control blanco por defecto de Google.
function addZoomControls(gmaps, map) {
  const box = document.createElement("div");
  box.style.cssText = "display:flex;flex-direction:column;gap:8px;margin:0 22px 30px 0;";
  const mk = (sym, label, delta) => {
    const b = document.createElement("button");
    b.type = "button";
    b.setAttribute("aria-label", label);
    b.title = label;
    b.textContent = sym;
    b.style.cssText =
      "width:40px;height:40px;border-radius:11px;cursor:pointer;" +
      "display:grid;place-items:center;font-size:22px;font-weight:500;line-height:1;" +
      "font-family:var(--font-sans);color:var(--g-text-cream);" +
      "background:rgba(17,23,30,0.9);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);" +
      "border:1px solid var(--g-border-on-dark);box-shadow:0 8px 24px rgba(0,0,0,0.5);" +
      "transition:border-color 160ms,color 160ms;";
    b.onmouseenter = () => { b.style.borderColor = ACCENT; b.style.color = ACCENT; };
    b.onmouseleave = () => { b.style.borderColor = "var(--g-border-on-dark)"; b.style.color = "var(--g-text-cream)"; };
    b.onclick = () => { map.setZoom((map.getZoom() || 11) + delta); };
    return b;
  };
  box.appendChild(mk("+", "Acercar", 1));
  box.appendChild(mk("−", "Alejar", -1));
  map.controls[gmaps.ControlPosition.RIGHT_BOTTOM].push(box);
}

// Estilo del mapa derivado de los tokens (tema oscuro de la app).
const MAP_DARK_STYLE = [
  { elementType: "geometry", stylers: [{ color: "#0b0f14" }] },
  { elementType: "labels.text.fill", stylers: [{ color: "#5b6b78" }] },
  { elementType: "labels.text.stroke", stylers: [{ color: "#0b0f14" }] },
  { featureType: "administrative", elementType: "geometry", stylers: [{ color: "#243040" }] },
  { featureType: "administrative.locality", elementType: "labels.text.fill", stylers: [{ color: "#9fb2c0" }] },
  { featureType: "administrative.province", elementType: "geometry.stroke", stylers: [{ color: "#2b3a47" }, { weight: 1.7 }] },
  { featureType: "poi", stylers: [{ visibility: "off" }] },
  { featureType: "road", elementType: "geometry", stylers: [{ color: "#1b2530" }] },
  { featureType: "road", elementType: "labels", stylers: [{ visibility: "off" }] },
  { featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#2b3a47" }] },
  { featureType: "transit", stylers: [{ visibility: "off" }] },
  { featureType: "landscape.natural", elementType: "geometry", stylers: [{ color: "#10171f" }] },
  { featureType: "water", elementType: "geometry", stylers: [{ color: "#0a1820" }] },
];

// ── Helpers geográficos ──────────────────────────────────────────────
const nodeLatLng = (n) => {
  if (!n) return null;
  if (typeof n.lat === "number" && typeof n.lon === "number") return { lat: n.lat, lng: n.lon };
  if (typeof n.x === "number" && window.SONAR_xyToGeo) { const g = window.SONAR_xyToGeo(n.x, n.y); return { lat: g.lat, lng: g.lon }; }
  return null;
};
const cpLatLng = (cp) => {
  if (cp.nodoId) { const ll = nodeLatLng(window.SONAR_findNode(cp.nodoId)); if (ll) return ll; }
  if (typeof cp.lat === "number" && typeof cp.lon === "number") return { lat: cp.lat, lng: cp.lon };
  if (typeof cp.x === "number" && window.SONAR_xyToGeo) { const g = window.SONAR_xyToGeo(cp.x, cp.y); return { lat: g.lat, lng: g.lon }; }
  return null;
};
const showsInCorridors = (nodeId, projection) => {
  if (!projection) return false;
  const dis = Array.isArray(projection.discarded) ? projection.discarded : (projection.discarded ? [projection.discarded] : []);
  return (projection.corridors || []).some(c => (c.nodosClave || []).includes(nodeId))
      || dis.some(c => (c.nodosClave || []).includes(nodeId));
};
const circleSymbol = (gmaps, color, scale, stroke) => ({
  path: gmaps.SymbolPath.CIRCLE, fillColor: color, fillOpacity: 1,
  strokeColor: stroke || "#0B0F14", strokeWeight: 2, scale,
});

const QuindioMap = ({
  mode = "overview",
  plate = null,
  projection = null,
  checkpoints = [],
  selectedCheckpoints = new Set(),
  onToggleCheckpoint,
  onNodeClick,
  selectedCorridor = null,
  showDiscarded = true,
  focusNodeId = null,
  pinNode = null,
  onVerMas,
  onPinClose,
}) => {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const overlaysRef = React.useRef([]);
  const fitKeyRef = React.useRef("");
  const pinRef = React.useRef(null);
  const infoRef = React.useRef(null);
  const [ready, setReady] = React.useState(false);

  // Callbacks en refs para no recrear listeners por identidad.
  const onNodeClickRef = React.useRef(onNodeClick);
  const onToggleRef = React.useRef(onToggleCheckpoint);
  const onVerMasRef = React.useRef(onVerMas);
  const onPinCloseRef = React.useRef(onPinClose);
  React.useEffect(() => {
    onNodeClickRef.current = onNodeClick; onToggleRef.current = onToggleCheckpoint;
    onVerMasRef.current = onVerMas; onPinCloseRef.current = onPinClose;
  });

  // Inicialización del mapa (una vez, cuando Google Maps está listo).
  React.useEffect(() => {
    let cancelled = false;
    if (!window.gmapsReady) return;
    window.gmapsReady.then((gmaps) => {
      if (cancelled || !containerRef.current || mapRef.current) return;
      const c = window.SONAR_MAP_CENTER || { lat: 4.535, lon: -75.665 };
      mapRef.current = new gmaps.Map(containerRef.current, {
        center: { lat: c.lat, lng: c.lon },
        zoom: window.SONAR_MAP_ZOOM || 11,
        disableDefaultUI: true,
        zoomControl: false,            // usamos botones propios (estilo de la página)
        gestureHandling: "greedy",
        clickableIcons: false,
        backgroundColor: "#0b0f14",
        styles: MAP_DARK_STYLE,
        restriction: { latLngBounds: EJE_CAFETERO_BOUNDS, strictBounds: true },
        minZoom: 8,                    // no alejar más allá del eje cafetero
      });
      addZoomControls(gmaps, mapRef.current);
      setReady(true);
    });
    return () => { cancelled = true; };
  }, []);

  // Redibujo de capas cuando cambian los datos.
  React.useEffect(() => {
    if (!ready || !mapRef.current || !window.google) return;
    const gmaps = window.google.maps;
    const map = mapRef.current;

    overlaysRef.current.forEach(o => o.setMap(null));
    overlaysRef.current = [];
    const add = (o) => { overlaysRef.current.push(o); return o; };

    const bounds = new gmaps.LatLngBounds();
    let extended = false;
    const extend = (ll) => { if (ll) { bounds.extend(ll); extended = true; } };

    const nodes = window.SONAR_NODES || [];
    const caps = plate && plate.capturas ? plate.capturas : [];
    const lastCapture = (mode !== "overview") && caps.length ? caps[caps.length - 1] : null;
    const trajIds = new Set(mode === "pulso" ? caps.map(c => c.nodo) : []);
    const origin = projection && projection.origin ? projection.origin : null;
    const originId = origin ? (origin.nodo_id || origin.id) : null;
    const originLL = origin && origin.lat != null ? { lat: origin.lat, lng: origin.lon } : null;

    // ── Rutas proyectadas + radio de alcance (rebote / candado) ────
    if ((mode === "rebote" || mode === "candado") && projection) {
      // Radio de alcance: hasta dónde pudo llegar el vehículo según el tiempo
      // transcurrido desde la última captura (~45 km/h).
      const reachM = projection.elapsed_s != null ? projection.elapsed_s * 12.5 : null;
      if (originLL && reachM && reachM > 250) {
        add(new gmaps.Circle({
          map, center: originLL, radius: Math.min(reachM, 60000), clickable: false,
          strokeColor: ACCENT, strokeOpacity: 0.4, strokeWeight: 1,
          fillColor: ACCENT, fillOpacity: mode === "candado" ? 0.03 : 0.06,
        }));
      }
      // Cada ruta = un "viaje" real por vías hacia un destino (nodo/puesto/frontera).
      const routes = projection.routes || projection.corridors || [];
      routes.forEach(r => {
        const dest = r.dest || {};
        const vencida = r.status === "vencida";
        const color = vencida ? "#6B7682" : (dest.kind === "frontera" ? "#FF8A3D" : ACCENT);
        const sel = selectedCorridor === (dest.id || r.id);
        const path = [];
        if (r.polyline && gmaps.geometry && gmaps.geometry.encoding) {
          try { gmaps.geometry.encoding.decodePath(r.polyline).forEach(p => path.push(p)); } catch (e) {}
        }
        if (path.length < 2 && originLL && dest.lat != null) {
          path.length = 0; path.push(originLL, { lat: dest.lat, lng: dest.lon });
        }
        if (path.length < 2) return;
        const dim = mode === "candado" && !sel;
        add(new gmaps.Polyline({
          map, path, geodesic: false, clickable: false,
          strokeColor: color,
          strokeOpacity: dim ? 0.4 : (sel ? 1 : (vencida ? 0.5 : 0.8)),
          strokeWeight: sel ? 5 : (vencida ? 2 : 3),
          zIndex: sel ? 30 : (vencida ? 5 : 12),
          icons: vencida
            ? [{ icon: { path: "M 0,-1 0,1", strokeColor: color, strokeOpacity: 0.7, scale: 2.5 }, offset: "0", repeat: "12px" }]
            : [{ icon: { path: gmaps.SymbolPath.FORWARD_CLOSED_ARROW, scale: 2.2, strokeColor: color, fillColor: color, fillOpacity: 1, strokeWeight: 0 }, offset: "92%" }],
        }));
        path.forEach(extend);
      });
    }

    // ── Trayectoria (pulso) ────────────────────────────────────────
    if (mode === "pulso" && caps.length) {
      const path = caps.map(c => nodeLatLng(window.SONAR_findNode(c.nodo))).filter(Boolean);
      if (path.length >= 2) {
        add(new gmaps.Polyline({
          map, path, geodesic: true, clickable: false,
          strokeColor: ACCENT, strokeOpacity: 0.9, strokeWeight: 3,
          icons: [{ icon: { path: gmaps.SymbolPath.FORWARD_CLOSED_ARROW, scale: 2.6, fillColor: ACCENT, fillOpacity: 1, strokeWeight: 0 }, offset: "100%" }],
        }));
      }
      path.forEach(extend);
    }

    // ── Nodos ──────────────────────────────────────────────────────
    nodes.forEach(n => {
      const ll = nodeLatLng(n); if (!ll) return;
      const isLast = lastCapture && lastCapture.nodo === n.id;
      const isTraj = trajIds.has(n.id);
      const dim = mode === "rebote" && projection && projection.corridors && originId !== n.id && !showsInCorridors(n.id, projection);
      // Frontera → naranja (siempre); puesto activo → amarillo; resto por estado (verde/gris).
      const color = n.kind === "frontera" ? "#FF8A3D"
        : (n.kind === "puesto" && n.status === "active") ? "#F2C61D"
        : (STATUS_COLOR[n.status] || "#6B7682");
      const scale = isLast ? 8 : (isTraj ? 6 : 5);
      const m = add(new gmaps.Marker({
        map, position: ll, opacity: dim ? 0.35 : 1,
        zIndex: isLast ? 100 : (isTraj ? 50 : 10),
        title: window.SONAR_nodeCode(n) + " · " + n.nombre + (n.alias ? " (" + n.alias + ")" : ""),
        icon: circleSymbol(gmaps, color, scale, isLast ? "#FFFFFF" : "#0B0F14"),
      }));
      m.addListener("click", () => { if (onNodeClickRef.current) onNodeClickRef.current(n); });
      if (mode === "overview" || mode === "pulso") extend(ll);
    });

    // ── Checkpoints (candado) ──────────────────────────────────────
    if (mode === "candado") {
      checkpoints.forEach(cp => {
        const ll = cpLatLng(cp); if (!ll) return;
        const color = PRIO_COLOR[cp.prioridad] || ACCENT;
        const sel = selectedCheckpoints.has(cp.id);
        const m = add(new gmaps.Marker({
          map, position: ll, zIndex: sel ? 200 : 80,
          title: cp.nombre + " · " + cp.prioridad,
          icon: {
            path: gmaps.SymbolPath.CIRCLE, fillColor: color, fillOpacity: sel ? 1 : 0.9,
            strokeColor: sel ? "#FFFFFF" : "#0B0F14", strokeWeight: sel ? 3 : 1.5, scale: sel ? 8 : 6,
          },
        }));
        m.addListener("click", () => { if (onToggleRef.current) onToggleRef.current(cp.id); });
        extend(ll);
      });
    }

    // ── Encadre: solo al cambiar de contexto (no en cada tick de tweaks) ──
    const fitKey = mode + ":" + (plate ? plate.placa : "") + ":" + checkpoints.length;
    if (extended && fitKeyRef.current !== fitKey) {
      map.fitBounds(bounds, mode === "overview" ? 48 : 90);
      fitKeyRef.current = fitKey;
    }
  }, [ready, mode, plate, projection, checkpoints, selectedCheckpoints, selectedCorridor, showDiscarded, focusNodeId]);

  // Pin + burbuja del nodo elegido desde el panel (clic en un código). Vive en sus
  // propios refs (no en overlaysRef) para que el redibujo de capas no lo borre.
  React.useEffect(() => {
    if (!ready || !mapRef.current || !window.google) return;
    const gmaps = window.google.maps;
    const map = mapRef.current;
    if (infoRef.current) { infoRef.current.close(); infoRef.current = null; }
    if (pinRef.current) { pinRef.current.setMap(null); pinRef.current = null; }
    if (!pinNode || pinNode.lat == null) return;

    const ll = { lat: pinNode.lat, lng: pinNode.lon };
    const pin = new gmaps.Marker({ map, position: ll, zIndex: 999, animation: gmaps.Animation.DROP });
    pinRef.current = pin;

    const codigo = window.SONAR_nodeCode(pinNode);
    const activo = pinNode.status === "active";
    const box = document.createElement("div");
    box.style.cssText = "font-family:var(--font-sans);min-width:166px;max-width:236px;padding:11px 12px;line-height:1.3;";

    const esPuesto = pinNode.kind === "puesto";
    const esFrontera = pinNode.kind === "frontera";
    const dotColor = esFrontera ? "#ea7317" : (!activo ? "#94a3b8" : (esPuesto ? "#e0a000" : "#16a34a"));
    const top = document.createElement("div");
    top.style.cssText = "display:flex;align-items:center;gap:6px;";
    top.innerHTML =
      "<span style=\"width:7px;height:7px;border-radius:50%;flex:0 0 auto;background:" + dotColor + "\"></span>" +
      "<span style=\"font-size:10.5px;font-weight:700;letter-spacing:0.8px;text-transform:uppercase;color:#0e7490\">" + codigo + "</span>";
    const close = document.createElement("button");
    close.type = "button";
    close.innerHTML = "✕";
    close.style.cssText = "margin-left:auto;border:0;background:transparent;color:#94a3b8;font-size:12px;line-height:1;cursor:pointer;padding:0 0 0 8px;";
    close.onclick = () => {
      if (infoRef.current) infoRef.current.close();
      if (pinRef.current) { pinRef.current.setMap(null); pinRef.current = null; }
      if (onPinCloseRef.current) onPinCloseRef.current();
    };
    top.appendChild(close);
    box.appendChild(top);

    const nm = document.createElement("div");
    nm.style.cssText = "font-size:14px;font-weight:600;margin-top:5px;color:#0f172a;";
    nm.textContent = pinNode.nombre || "";
    box.appendChild(nm);
    // Subtítulo: municipio (para nodos y puestos); en puestos también el alias.
    const muni = (window.SONAR_MUNICIPIOS || []).find(m => m.id === pinNode.municipio);
    const sub = pinNode.alias
      ? (pinNode.alias + (muni ? " · " + muni.nombre : ""))
      : (muni ? muni.nombre : "");
    if (sub) {
      const al = document.createElement("div");
      al.style.cssText = "font-size:11.5px;color:#64748b;margin-top:1px;";
      al.textContent = sub;
      box.appendChild(al);
    }

    // "Ver más → Operatividad" solo si ese panel está habilitado (onVerMas no nulo); si está
    // bloqueado por el feature gating no se dibuja el botón (evita un botón muerto).
    if (onVerMasRef.current) {
      const btn = document.createElement("button");
      btn.type = "button";
      btn.innerHTML = "Ver más <span style=\"font-size:13px;line-height:1\">→</span>";
      btn.style.cssText = "margin-top:10px;display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:9px;border:0;background:#4FD4FF;color:#04141b;font-family:var(--font-sans);font-size:12px;font-weight:700;letter-spacing:0.2px;cursor:pointer;";
      btn.onmouseenter = function () { this.style.background = "#37c7f5"; };
      btn.onmouseleave = function () { this.style.background = "#4FD4FF"; };
      btn.onclick = () => { if (onVerMasRef.current) onVerMasRef.current(codigo); };
      box.appendChild(btn);
    }

    const info = new gmaps.InfoWindow({ content: box, headerDisabled: true, pixelOffset: new gmaps.Size(0, -12) });
    info.addListener("closeclick", () => {
      if (pinRef.current) { pinRef.current.setMap(null); pinRef.current = null; }
      if (onPinCloseRef.current) onPinCloseRef.current();
    });
    info.open(map, pin);
    infoRef.current = info;
    map.panTo(ll);
    if ((map.getZoom() || 11) < 13) map.setZoom(13);
  }, [ready, pinNode]);

  return <div ref={containerRef} className="quindio-map" style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }} />;
};

window.QuindioMap = QuindioMap;
