/* global React */
const { useState, useEffect, useRef, useCallback } = React;

// ============================================================
//  Optimizer Lab — pick stops on a map, run GraphHopper VRP
//  Endpoint: https://geooptimizer.sensordata.com.uy
// ============================================================

const DEFAULT_ENDPOINT = "https://geooptimizer.sensordata.com.uy";
const MVD_CENTER = [-34.9011, -56.1645];

// pleasant palette for stop markers
const STOP_COLORS = [
  "#2F6FE8", "#A8E640", "#E8742F", "#7B5BE8",
  "#0EA5A5", "#E84B7B", "#E8C72F", "#5BA8E8",
  "#7BE85B", "#E85B5B", "#5BE8C7", "#B85BE8",
];

function uid() { return Math.random().toString(36).slice(2, 9); }

function haversine(a, b) {
  const R = 6371;
  const toRad = (x) => (x * Math.PI) / 180;
  const dLat = toRad(b[0] - a[0]);
  const dLon = toRad(b[1] - a[1]);
  const lat1 = toRad(a[0]);
  const lat2 = toRad(b[0]);
  const h = Math.sin(dLat / 2) ** 2 + Math.sin(dLon / 2) ** 2 * Math.cos(lat1) * Math.cos(lat2);
  return 2 * R * Math.asin(Math.sqrt(h));
}

// Local nearest-neighbor TSP fallback (used when the endpoint is unreachable)
function localOptimize(depot, stops, returnToDepot) {
  if (!stops.length) return { order: [], distance_km: 0, duration_min: 0, source: "local" };
  const remaining = [...stops];
  const order = [];
  let cur = depot.coords;
  let total = 0;
  while (remaining.length) {
    let best = 0;
    let bestD = Infinity;
    for (let i = 0; i < remaining.length; i++) {
      const d = haversine(cur, remaining[i].coords);
      if (d < bestD) { bestD = d; best = i; }
    }
    total += bestD;
    cur = remaining[best].coords;
    order.push(remaining[best]);
    remaining.splice(best, 1);
  }
  if (returnToDepot) total += haversine(cur, depot.coords);
  // assume 28 km/h average urban speed
  const duration_min = (total / 28) * 60;
  return { order, distance_km: total, duration_min, source: "local" };
}

// Build a GraphHopper /vrp request body from our state
function buildVrpRequest(depot, stops, options) {
  return {
    vehicles: [
      {
        vehicle_id: "veh-1",
        start_address: { location_id: "depot", lon: depot.coords[1], lat: depot.coords[0] },
        ...(options.returnToDepot
          ? { end_address: { location_id: "depot", lon: depot.coords[1], lat: depot.coords[0] } }
          : { return_to_depot: false }),
        type_id: "type-1",
      },
    ],
    vehicle_types: [
      {
        type_id: "type-1",
        profile: options.profile,
      },
    ],
    services: stops.map((s, i) => ({
      id: s.id,
      name: s.name || `Stop ${i + 1}`,
      address: { location_id: s.id, lon: s.coords[1], lat: s.coords[0] },
      duration: options.serviceTimeSec,
    })),
    objectives: [
      { type: "min", value: options.objective },
    ],
    configuration: { routing: { calc_points: true } },
  };
}

function OptimizerPage({ lang, onBack }) {
  const isEs = lang === "es";

  // state
  const [endpoint, setEndpoint] = useState(DEFAULT_ENDPOINT);
  const [apiKey, setApiKey] = useState("");
  const [depot, setDepot] = useState({ coords: MVD_CENTER, name: isEs ? "Depósito" : "Depot" });
  const [stops, setStops] = useState([]); // [{id, coords, name}]
  const [picking, setPicking] = useState("stop"); // "stop" | "depot"
  const [options, setOptions] = useState({
    profile: "small_truck",
    objective: "completion_time",
    returnToDepot: true,
    serviceTimeSec: 120,
    maxDistance: 0, // 0 = unlimited
    considerTraffic: true,
  });
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);
  const [logs, setLogs] = useState([]);

  const log = useCallback((msg, kind = "info") => {
    setLogs((l) => [...l.slice(-30), { t: new Date().toLocaleTimeString(), msg, kind }]);
  }, []);

  // Map refs
  const mapEl = useRef(null);
  const mapRef = useRef(null);
  const layersRef = useRef({ stops: [], depot: null, route: null });

  // Init Leaflet map
  useEffect(() => {
    let cancelled = false;
    function ensureLeaflet() {
      return new Promise((resolve) => {
        if (window.L) return resolve(window.L);
        const css = document.createElement("link");
        css.rel = "stylesheet";
        css.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
        css.integrity = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=";
        css.crossOrigin = "";
        document.head.appendChild(css);
        const s = document.createElement("script");
        s.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
        s.integrity = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=";
        s.crossOrigin = "";
        s.onload = () => resolve(window.L);
        document.head.appendChild(s);
      });
    }

    ensureLeaflet().then((L) => {
      if (cancelled || !mapEl.current || mapRef.current) return;
      const map = L.map(mapEl.current, {
        center: MVD_CENTER,
        zoom: 12,
        zoomControl: true,
        attributionControl: true,
      });
      // Carto dark-matter for a lab feel
      L.tileLayer(
        "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
        {
          maxZoom: 19,
          attribution: "© OpenStreetMap · © CARTO",
        }
      ).addTo(map);
      mapRef.current = map;
      // Pickup mode click
      map.on("click", (e) => {
        const coords = [e.latlng.lat, e.latlng.lng];
        if (pickingRef.current === "depot") {
          setDepot((d) => ({ ...d, coords }));
        } else {
          setStops((arr) => [
            ...arr,
            { id: uid(), coords, name: `${isEs ? "Parada" : "Stop"} ${arr.length + 1}` },
          ]);
        }
      });
      // expose marker icon helper
      L._roundIcon = (color, label) =>
        L.divIcon({
          className: "opt-marker",
          html: `<span style="background:${color}">${label}</span>`,
          iconSize: [26, 26],
          iconAnchor: [13, 13],
        });
      L._depotIcon = () =>
        L.divIcon({
          className: "opt-marker opt-marker-depot",
          html: `<span style="background:#0F172A;color:#fff">★</span>`,
          iconSize: [30, 30],
          iconAnchor: [15, 15],
        });
    });

    return () => {
      cancelled = true;
    };
  }, []);

  // keep picking ref so map handler reads latest
  const pickingRef = useRef(picking);
  useEffect(() => { pickingRef.current = picking; }, [picking]);

  // Render markers when stops/depot change
  useEffect(() => {
    const L = window.L;
    const map = mapRef.current;
    if (!L || !map) return;

    // depot
    if (layersRef.current.depot) map.removeLayer(layersRef.current.depot);
    layersRef.current.depot = L.marker(depot.coords, { icon: L._depotIcon(), draggable: true })
      .addTo(map)
      .bindTooltip(depot.name, { permanent: false, direction: "top" });
    layersRef.current.depot.on("dragend", (e) => {
      const ll = e.target.getLatLng();
      setDepot((d) => ({ ...d, coords: [ll.lat, ll.lng] }));
    });

    // stops
    layersRef.current.stops.forEach((m) => map.removeLayer(m));
    layersRef.current.stops = stops.map((s, i) => {
      const m = L.marker(s.coords, {
        icon: L._roundIcon(STOP_COLORS[i % STOP_COLORS.length], i + 1),
        draggable: true,
      })
        .addTo(map)
        .bindTooltip(s.name, { permanent: false, direction: "top" });
      m.on("dragend", (e) => {
        const ll = e.target.getLatLng();
        setStops((arr) => arr.map((x) => (x.id === s.id ? { ...x, coords: [ll.lat, ll.lng] } : x)));
      });
      return m;
    });
  }, [stops, depot]);

  // Render optimized route polyline
  useEffect(() => {
    const L = window.L;
    const map = mapRef.current;
    if (!L || !map) return;
    if (layersRef.current.route) {
      map.removeLayer(layersRef.current.route);
      layersRef.current.route = null;
    }
    if (!result || !result.order.length) return;
    const path = [depot.coords, ...result.order.map((s) => s.coords)];
    if (options.returnToDepot) path.push(depot.coords);
    layersRef.current.route = L.polyline(path, {
      color: "#2F6FE8",
      weight: 4,
      opacity: 0.85,
      dashArray: result.source === "local" ? "8 6" : null,
    }).addTo(map);
  }, [result, depot, options.returnToDepot]);

  // Run optimization
  const runOptimize = async () => {
    if (!stops.length) {
      setError(isEs ? "Agregá al menos una parada haciendo clic en el mapa." : "Add at least one stop by clicking the map.");
      return;
    }
    setBusy(true);
    setError(null);
    setResult(null);
    log(`POST ${endpoint}/api/1/vrp · ${stops.length} ${isEs ? "paradas" : "stops"}`);

    const body = buildVrpRequest(depot, stops, options);

    try {
      const url = `${endpoint.replace(/\/$/, "")}/api/1/vrp${apiKey ? `?key=${encodeURIComponent(apiKey)}` : ""}`;
      const res = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
      const data = await res.json();
      log(`✓ ${isEs ? "respuesta recibida" : "response received"} · ${res.status}`, "ok");
      // Parse GraphHopper VRP response
      const sol = data.solution || data;
      const route = (sol.routes && sol.routes[0]) || null;
      if (!route) throw new Error("Empty solution");
      const ordered = route.activities
        .filter((a) => a.type === "service")
        .map((a) => stops.find((s) => s.id === a.id))
        .filter(Boolean);
      const distance_km = (route.distance || sol.distance || 0) / 1000;
      const duration_min = (route.transport_time || sol.time || 0) / 60;
      setResult({ order: ordered, distance_km, duration_min, source: "graphhopper", raw: data });
    } catch (e) {
      log(`✗ ${e.message} — ${isEs ? "usando fallback local" : "falling back local"}`, "warn");
      const r = localOptimize(depot, stops, options.returnToDepot);
      setResult(r);
      setError(
        (isEs
          ? "No se pudo contactar el servidor de optimización (CORS / red). Mostrando una solución local de demostración."
          : "Could not reach the optimization server (CORS / network). Showing a local demo solution.") +
          ` · ${e.message}`
      );
    } finally {
      setBusy(false);
    }
  };

  const removeStop = (id) => setStops((arr) => arr.filter((s) => s.id !== id));
  const renameStop = (id, name) => setStops((arr) => arr.map((s) => (s.id === id ? { ...s, name } : s)));
  const moveStop = (i, dir) =>
    setStops((arr) => {
      const j = i + dir;
      if (j < 0 || j >= arr.length) return arr;
      const c = [...arr];
      [c[i], c[j]] = [c[j], c[i]];
      return c;
    });
  const clearAll = () => { setStops([]); setResult(null); setError(null); };

  const seedExample = () => {
    setStops([
      { id: uid(), coords: [-34.8835, -56.1819], name: "Pocitos" },
      { id: uid(), coords: [-34.9056, -56.1986], name: "Punta Carretas" },
      { id: uid(), coords: [-34.9211, -56.1691], name: "Centro" },
      { id: uid(), coords: [-34.8722, -56.1582], name: "Buceo" },
      { id: uid(), coords: [-34.8546, -56.2089], name: "Carrasco" },
      { id: uid(), coords: [-34.8967, -56.0846], name: "Aeropuerto" },
    ]);
    setResult(null);
  };

  return (
    <main className="opt-page">
      <style>{`
        .opt-page { display: grid; grid-template-columns: 380px 1fr 360px; height: calc(100vh - 64px); background: var(--bone); color: var(--ink); font-family: var(--font-display); }

        /* ===== Marker styles (global) ===== */
        .opt-marker { background: transparent !important; border: none !important; }
        .opt-marker span {
          display: flex; align-items: center; justify-content: center;
          width: 100%; height: 100%; border-radius: 50%;
          color: #fff; font-family: var(--font-mono); font-size: 12px; font-weight: 600;
          box-shadow: 0 2px 8px rgba(15, 23, 42, 0.35), 0 0 0 2px rgba(255,255,255,0.95);
          transition: transform 0.15s;
        }
        .opt-marker:hover span { transform: scale(1.12); }

        /* ===== Left panel: stops list ===== */
        .opt-left { border-right: 1px solid var(--line); background: oklch(0.985 0.003 240); display: flex; flex-direction: column; min-width: 0; }
        .opt-left-head { padding: 18px 20px 14px; border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: 8px; }
        .opt-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.14em; color: var(--mist); text-transform: uppercase; display: flex; align-items: center; gap: 8px; }
        .opt-eyebrow-dot { width: 7px; height: 7px; border-radius: 50%; background: oklch(0.65 0.18 145); box-shadow: 0 0 0 3px oklch(0.65 0.18 145 / 0.18); animation: optPulse 2s infinite; }
        @keyframes optPulse { 50% { box-shadow: 0 0 0 6px oklch(0.65 0.18 145 / 0); } }
        .opt-h1 { font-family: var(--font-display); font-size: 22px; font-weight: 500; letter-spacing: -0.01em; color: var(--ink); margin: 0; }
        .opt-sub { font-size: 13px; color: var(--fog); margin: 0; line-height: 1.45; }

        .opt-pick-toggle { display: flex; gap: 4px; padding: 4px; background: oklch(0.93 0.005 240); border-radius: 8px; margin: 12px 20px 0; }
        .opt-pick-toggle button { flex: 1; padding: 8px 10px; border: none; background: transparent; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; border-radius: 6px; color: var(--fog); transition: all 0.15s; }
        .opt-pick-toggle button.on { background: var(--ink); color: var(--bone); box-shadow: 0 2px 6px rgba(15, 23, 42, 0.18); }

        .opt-stops { flex: 1; overflow-y: auto; padding: 12px 14px 16px; }
        .opt-stops-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 6px 8px; }
        .opt-stops-head h4 { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em; color: var(--mist); text-transform: uppercase; margin: 0; }
        .opt-stops-actions { display: flex; gap: 8px; }
        .opt-link-btn { background: none; border: none; cursor: pointer; font-family: var(--font-mono); font-size: 11px; color: var(--accent); padding: 0; }
        .opt-link-btn:hover { text-decoration: underline; }

        .opt-stop { display: flex; align-items: center; gap: 10px; padding: 10px 10px; border-radius: 8px; transition: background 0.15s; }
        .opt-stop:hover { background: oklch(0.96 0.005 240); }
        .opt-stop-pin { width: 26px; height: 26px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size: 12px; font-weight: 600; color: #fff; flex-shrink: 0; box-shadow: 0 0 0 2px #fff, 0 1px 4px rgba(15, 23, 42, 0.2); }
        .opt-stop-body { flex: 1; min-width: 0; }
        .opt-stop-name { background: none; border: none; padding: 0; width: 100%; font-family: var(--font-display); font-size: 13.5px; font-weight: 500; color: var(--ink); outline: none; }
        .opt-stop-name:focus { color: var(--accent); }
        .opt-stop-coord { font-family: var(--font-mono); font-size: 10.5px; color: var(--mist); margin-top: 2px; }
        .opt-stop-ctrls { display: flex; flex-direction: column; gap: 1px; }
        .opt-stop-ctrl { background: none; border: none; cursor: pointer; padding: 1px 4px; color: var(--fog); border-radius: 3px; }
        .opt-stop-ctrl:hover { background: oklch(0.92 0.005 240); color: var(--ink); }
        .opt-stop-del { background: none; border: none; cursor: pointer; padding: 6px; color: var(--mist); border-radius: 4px; }
        .opt-stop-del:hover { background: oklch(0.95 0.04 25); color: oklch(0.55 0.18 25); }

        .opt-empty { padding: 32px 18px; text-align: center; border: 1px dashed var(--line); border-radius: 10px; color: var(--fog); font-size: 13px; line-height: 1.5; margin: 8px 6px; background: #fff; }
        .opt-empty strong { color: var(--ink); display: block; margin-bottom: 4px; font-weight: 500; }

        /* ===== Center: map ===== */
        .opt-center { position: relative; min-width: 0; min-height: 0; }
        .opt-map { position: absolute; inset: 0; }
        .opt-map-overlay { position: absolute; left: 16px; top: 16px; z-index: 500; display: flex; gap: 8px; align-items: center; background: rgba(255,255,255,0.95); backdrop-filter: blur(8px); padding: 8px 14px; border-radius: 999px; box-shadow: 0 4px 18px rgba(15, 23, 42, 0.12); font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.06em; color: var(--fog); border: 1px solid var(--line); }
        .opt-map-overlay-pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px oklch(0.55 0.18 240 / 0.2); animation: optPulse 1.6s infinite; }
        .opt-map-cta { position: absolute; right: 16px; bottom: 16px; z-index: 500; display: flex; gap: 8px; }

        /* ===== Right panel: options + results ===== */
        .opt-right { border-left: 1px solid var(--line); background: oklch(0.99 0.002 240); display: flex; flex-direction: column; }
        .opt-right-tabs { display: flex; padding: 12px 16px 0; border-bottom: 1px solid var(--line); gap: 4px; background: #fff; }
        .opt-right-tabs button { padding: 10px 14px; border: none; background: transparent; font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; cursor: pointer; color: var(--fog); border-bottom: 2px solid transparent; margin-bottom: -1px; }
        .opt-right-tabs button.on { color: var(--ink); border-bottom-color: var(--accent); }

        .opt-options { flex: 1; overflow-y: auto; padding: 16px 18px 8px; }
        .opt-section { margin-bottom: 22px; }
        .opt-section-label { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.14em; color: var(--mist); text-transform: uppercase; margin-bottom: 8px; }
        .opt-radio-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
        .opt-radio { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: #fff; cursor: pointer; transition: all 0.15s; }
        .opt-radio:hover { border-color: var(--mist); }
        .opt-radio.on { border-color: var(--accent); background: oklch(0.98 0.02 240); box-shadow: 0 0 0 3px oklch(0.55 0.18 240 / 0.10); }
        .opt-radio-title { font-size: 12.5px; font-weight: 500; color: var(--ink); }
        .opt-radio-desc { font-family: var(--font-mono); font-size: 9.5px; color: var(--mist); letter-spacing: 0.04em; }

        .opt-toggle { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: #fff; cursor: pointer; }
        .opt-toggle-track { width: 32px; height: 18px; background: oklch(0.88 0.005 240); border-radius: 999px; position: relative; transition: background 0.18s; flex-shrink: 0; }
        .opt-toggle-track::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25); transition: transform 0.18s; }
        .opt-toggle.on .opt-toggle-track { background: var(--accent); }
        .opt-toggle.on .opt-toggle-track::after { transform: translateX(14px); }
        .opt-toggle-label { font-size: 13px; font-weight: 500; }

        .opt-slider { width: 100%; -webkit-appearance: none; height: 4px; background: oklch(0.90 0.005 240); border-radius: 999px; outline: none; }
        .opt-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: var(--accent); border-radius: 50%; cursor: pointer; box-shadow: 0 0 0 3px #fff, 0 1px 4px rgba(15, 23, 42, 0.25); }

        .opt-input { width: 100%; padding: 9px 12px; border: 1px solid var(--line); border-radius: 7px; font-family: var(--font-mono); font-size: 12px; background: #fff; color: var(--ink); outline: none; transition: border 0.15s; }
        .opt-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px oklch(0.55 0.18 240 / 0.12); }

        /* ===== Buttons ===== */
        .opt-btn { display: inline-flex; align-items: center; gap: 8px; justify-content: center; padding: 11px 18px; border: 1px solid transparent; border-radius: 8px; font-family: var(--font-display); font-size: 13.5px; font-weight: 500; cursor: pointer; transition: all 0.15s; text-decoration: none; }
        .opt-btn-primary { background: var(--ink); color: var(--bone); }
        .opt-btn-primary:hover { background: oklch(0.18 0.03 240); transform: translateY(-1px); }
        .opt-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
        .opt-btn-ghost { background: #fff; border-color: var(--line); color: var(--ink); }
        .opt-btn-ghost:hover { border-color: var(--mist); }
        .opt-btn-spin { width: 14px; height: 14px; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; animation: optSpin 0.7s linear infinite; }
        @keyframes optSpin { to { transform: rotate(360deg); } }

        /* ===== CTA bar ===== */
        .opt-cta-bar { padding: 14px 18px; border-top: 1px solid var(--line); background: #fff; display: flex; flex-direction: column; gap: 8px; }
        .opt-cta-meta { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 10.5px; color: var(--mist); letter-spacing: 0.06em; }

        /* ===== Result + log ===== */
        .opt-result-banner { margin: 0 0 16px; padding: 14px 16px; background: linear-gradient(135deg, oklch(0.98 0.04 240), oklch(0.97 0.06 145)); border-radius: 10px; border: 1px solid var(--line); }
        .opt-result-row { display: flex; gap: 18px; }
        .opt-result-stat-num { font-family: var(--font-display); font-size: 22px; font-weight: 500; color: var(--ink); line-height: 1; }
        .opt-result-stat-num .unit { font-family: var(--font-mono); font-size: 11px; font-weight: 400; margin-left: 3px; color: var(--fog); }
        .opt-result-stat-lbl { font-family: var(--font-mono); font-size: 9.5px; color: var(--mist); letter-spacing: 0.10em; text-transform: uppercase; margin-top: 4px; }
        .opt-result-source { font-family: var(--font-mono); font-size: 9.5px; padding: 3px 7px; border-radius: 4px; background: oklch(0.95 0.005 240); color: var(--fog); display: inline-block; margin-top: 8px; letter-spacing: 0.06em; }
        .opt-result-source.live { background: oklch(0.94 0.10 145); color: oklch(0.42 0.18 145); }

        .opt-order { list-style: none; padding: 0; margin: 0; }
        .opt-order li { display: flex; align-items: center; gap: 10px; padding: 9px 4px; border-bottom: 1px solid var(--line-soft); }
        .opt-order li:last-child { border-bottom: none; }
        .opt-order-pin { width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size: 11px; color: #fff; font-weight: 600; flex-shrink: 0; }
        .opt-order-name { flex: 1; font-size: 13px; }
        .opt-order-d { font-family: var(--font-mono); font-size: 10.5px; color: var(--mist); }

        .opt-error { margin: 0 0 12px; padding: 10px 14px; border-left: 3px solid oklch(0.65 0.18 35); background: oklch(0.98 0.03 35); color: oklch(0.45 0.18 35); font-size: 12.5px; line-height: 1.4; border-radius: 4px; }

        .opt-log { background: var(--ink); border-radius: 8px; padding: 10px 12px; font-family: var(--font-mono); font-size: 10.5px; color: oklch(0.85 0.005 240); max-height: 160px; overflow-y: auto; line-height: 1.55; }
        .opt-log-row { display: flex; gap: 8px; padding: 1px 0; }
        .opt-log-t { color: oklch(0.55 0.005 240); flex-shrink: 0; }
        .opt-log-row.ok .opt-log-msg { color: oklch(0.78 0.18 145); }
        .opt-log-row.warn .opt-log-msg { color: oklch(0.78 0.18 60); }

        /* responsive */
        @media (max-width: 1100px) {
          .opt-page { grid-template-columns: 320px 1fr 320px; }
        }
        @media (max-width: 900px) {
          .opt-page { grid-template-columns: 1fr; height: auto; }
          .opt-center { height: 50vh; }
        }
      `}</style>

      {/* ====================== LEFT: stops list ====================== */}
      <aside className="opt-left">
        <div className="opt-left-head">
          <div className="opt-eyebrow"><span className="opt-eyebrow-dot"></span>{isEs ? "Laboratorio · en vivo" : "Lab · live"}</div>
          <h1 className="opt-h1">{isEs ? "Optimizador de rutas" : "Route Optimizer"}</h1>
          <p className="opt-sub">{isEs
            ? "Hacé clic en el mapa para colocar paradas. El depósito y el orden óptimo se calculan con un GraphHopper privado."
            : "Click the map to drop stops. The depot and optimal order are computed with a private GraphHopper instance."}</p>
        </div>

        <div className="opt-pick-toggle">
          <button className={picking === "stop" ? "on" : ""} onClick={() => setPicking("stop")}>
            {isEs ? "+ Parada" : "+ Stop"}
          </button>
          <button className={picking === "depot" ? "on" : ""} onClick={() => setPicking("depot")}>
            ★ {isEs ? "Depósito" : "Depot"}
          </button>
        </div>

        <div className="opt-stops">
          <div className="opt-stops-head">
            <h4>{isEs ? `Paradas · ${stops.length}` : `Stops · ${stops.length}`}</h4>
            <div className="opt-stops-actions">
              <button className="opt-link-btn" onClick={seedExample}>{isEs ? "ejemplo" : "example"}</button>
              {stops.length > 0 && <button className="opt-link-btn" onClick={clearAll}>{isEs ? "limpiar" : "clear"}</button>}
            </div>
          </div>

          {!stops.length && (
            <div className="opt-empty">
              <strong>{isEs ? "Sin paradas todavía" : "No stops yet"}</strong>
              {isEs
                ? "Hacé clic en el mapa para colocar la primera, o cargá un ejemplo."
                : "Click the map to drop the first one, or load an example."}
            </div>
          )}

          {stops.map((s, i) => (
            <div className="opt-stop" key={s.id}>
              <span className="opt-stop-pin" style={{ background: STOP_COLORS[i % STOP_COLORS.length] }}>{i + 1}</span>
              <div className="opt-stop-body">
                <input
                  className="opt-stop-name"
                  value={s.name}
                  onChange={(e) => renameStop(s.id, e.target.value)}
                />
                <div className="opt-stop-coord">{s.coords[0].toFixed(4)}, {s.coords[1].toFixed(4)}</div>
              </div>
              <div className="opt-stop-ctrls">
                <button className="opt-stop-ctrl" onClick={() => moveStop(i, -1)} disabled={i === 0} title="up">▲</button>
                <button className="opt-stop-ctrl" onClick={() => moveStop(i, +1)} disabled={i === stops.length - 1} title="down">▼</button>
              </div>
              <button className="opt-stop-del" onClick={() => removeStop(s.id)} title="delete">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
              </button>
            </div>
          ))}
        </div>

        <div className="opt-cta-bar">
          <button className="opt-btn opt-btn-primary" onClick={runOptimize} disabled={busy || !stops.length}>
            {busy ? <><span className="opt-btn-spin"></span>{isEs ? "Optimizando…" : "Optimizing…"}</>
                  : <>{isEs ? "Optimizar ruta" : "Optimize route"} <span>→</span></>}
          </button>
        </div>
      </aside>

      {/* ====================== CENTER: map ====================== */}
      <section className="opt-center">
        <div ref={mapEl} className="opt-map"></div>
        <div className="opt-map-overlay">
          <span className="opt-map-overlay-pulse"></span>
          <span>{picking === "depot"
            ? (isEs ? "MODO · COLOCAR DEPÓSITO" : "MODE · PLACE DEPOT")
            : (isEs ? "MODO · AGREGAR PARADAS" : "MODE · ADD STOPS")}</span>
        </div>
      </section>

      {/* ====================== RIGHT: options + results ====================== */}
      <RightPanel
        isEs={isEs}
        options={options}
        setOptions={setOptions}
        endpoint={endpoint}
        setEndpoint={setEndpoint}
        apiKey={apiKey}
        setApiKey={setApiKey}
        busy={busy}
        result={result}
        error={error}
        logs={logs}
        depot={depot}
        stops={stops}
      />
    </main>
  );
}

function RightPanel({ isEs, options, setOptions, endpoint, setEndpoint, apiKey, setApiKey, result, error, logs, depot, stops }) {
  const [tab, setTab] = useState("options"); // "options" | "result" | "log"

  return (
    <aside className="opt-right">
      <div className="opt-right-tabs">
        <button className={tab === "options" ? "on" : ""} onClick={() => setTab("options")}>{isEs ? "Opciones" : "Options"}</button>
        <button className={tab === "result" ? "on" : ""} onClick={() => setTab("result")}>{isEs ? "Resultado" : "Result"}</button>
        <button className={tab === "log" ? "on" : ""} onClick={() => setTab("log")}>{isEs ? "Log" : "Log"}</button>
      </div>

      {tab === "options" && (
        <div className="opt-options">
          {/* Vehicle profile */}
          <div className="opt-section">
            <div className="opt-section-label">{isEs ? "Perfil del vehículo" : "Vehicle profile"}</div>
            <div className="opt-radio-row">
              {[
                { id: "car",         t: isEs ? "Auto"        : "Car",         d: "≤ 1.5t" },
                { id: "small_truck", t: isEs ? "Camioneta"   : "Van",         d: "≤ 3.5t" },
                { id: "truck",       t: isEs ? "Camión"      : "Truck",       d: "≤ 12t" },
                { id: "scooter",     t: isEs ? "Moto"        : "Scooter",     d: "≤ 200kg" },
              ].map((o) => (
                <button
                  key={o.id}
                  className={`opt-radio ${options.profile === o.id ? "on" : ""}`}
                  onClick={() => setOptions({ ...options, profile: o.id })}
                >
                  <span className="opt-radio-title">{o.t}</span>
                  <span className="opt-radio-desc">{o.d}</span>
                </button>
              ))}
            </div>
          </div>

          {/* Objective */}
          <div className="opt-section">
            <div className="opt-section-label">{isEs ? "Objetivo" : "Objective"}</div>
            <div className="opt-radio-row">
              {[
                { id: "completion_time", t: isEs ? "Tiempo total"  : "Total time",      d: "min · ETA" },
                { id: "transport_time",  t: isEs ? "Tiempo viaje"  : "Travel time",     d: "min · drive" },
                { id: "vehicles",        t: isEs ? "Vehículos"     : "Vehicles",        d: "min · fleet" },
                { id: "completion_distance", t: isEs ? "Distancia"  : "Distance",       d: "min · km" },
              ].map((o) => (
                <button
                  key={o.id}
                  className={`opt-radio ${options.objective === o.id ? "on" : ""}`}
                  onClick={() => setOptions({ ...options, objective: o.id })}
                >
                  <span className="opt-radio-title">{o.t}</span>
                  <span className="opt-radio-desc">{o.d}</span>
                </button>
              ))}
            </div>
          </div>

          {/* Toggles */}
          <div className="opt-section" style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <div className="opt-section-label">{isEs ? "Restricciones" : "Constraints"}</div>
            <div
              className={`opt-toggle ${options.returnToDepot ? "on" : ""}`}
              onClick={() => setOptions({ ...options, returnToDepot: !options.returnToDepot })}
            >
              <span className="opt-toggle-label">{isEs ? "Volver al depósito" : "Return to depot"}</span>
              <span className="opt-toggle-track"></span>
            </div>
            <div
              className={`opt-toggle ${options.considerTraffic ? "on" : ""}`}
              onClick={() => setOptions({ ...options, considerTraffic: !options.considerTraffic })}
            >
              <span className="opt-toggle-label">{isEs ? "Considerar tráfico" : "Consider traffic"}</span>
              <span className="opt-toggle-track"></span>
            </div>
          </div>

          {/* Service time */}
          <div className="opt-section">
            <div className="opt-section-label" style={{ display: "flex", justifyContent: "space-between" }}>
              <span>{isEs ? "Tiempo en parada" : "Service time"}</span>
              <span style={{ color: "var(--accent)", fontFamily: "var(--font-mono)" }}>{Math.round(options.serviceTimeSec / 60)} min</span>
            </div>
            <input
              type="range" min="30" max="900" step="30" value={options.serviceTimeSec}
              onChange={(e) => setOptions({ ...options, serviceTimeSec: +e.target.value })}
              className="opt-slider"
            />
          </div>
        </div>
      )}

      {tab === "result" && (
        <div className="opt-options">
          {error && <div className="opt-error">{error}</div>}
          {!result && !error && (
            <div className="opt-empty" style={{ marginTop: 0 }}>
              <strong>{isEs ? "Sin resultado todavía" : "No result yet"}</strong>
              {isEs
                ? "Agregá paradas y ejecutá la optimización para ver el orden óptimo y la ruta dibujada en el mapa."
                : "Add stops and run optimization to see the optimal order and the route drawn on the map."}
            </div>
          )}
          {result && (
            <>
              <div className="opt-result-banner">
                <div className="opt-result-row">
                  <div>
                    <div className="opt-result-stat-num">{result.distance_km.toFixed(1)}<span className="unit">km</span></div>
                    <div className="opt-result-stat-lbl">{isEs ? "Distancia" : "Distance"}</div>
                  </div>
                  <div>
                    <div className="opt-result-stat-num">{result.duration_min.toFixed(0)}<span className="unit">min</span></div>
                    <div className="opt-result-stat-lbl">{isEs ? "Duración" : "Duration"}</div>
                  </div>
                  <div>
                    <div className="opt-result-stat-num">{result.order.length}<span className="unit">{isEs ? "par." : "stp."}</span></div>
                    <div className="opt-result-stat-lbl">{isEs ? "Visitadas" : "Visited"}</div>
                  </div>
                </div>
                <span className={`opt-result-source ${result.source === "graphhopper" ? "live" : ""}`}>
                  {result.source === "graphhopper" ? (isEs ? "● GRAPHHOPPER · LIVE" : "● GRAPHHOPPER · LIVE") : (isEs ? "○ DEMO LOCAL · NEAREST-NEIGHBOR" : "○ LOCAL DEMO · NEAREST-NEIGHBOR")}
                </span>
              </div>

              <div className="opt-section-label">{isEs ? "Orden de visita" : "Visit order"}</div>
              <ol className="opt-order">
                <li>
                  <span className="opt-order-pin" style={{ background: "#0F172A" }}>★</span>
                  <span className="opt-order-name">{depot.name}</span>
                  <span className="opt-order-d">{isEs ? "salida" : "start"}</span>
                </li>
                {result.order.map((s, i) => {
                  const idx = stops.findIndex((x) => x.id === s.id);
                  const color = STOP_COLORS[idx % STOP_COLORS.length];
                  const cumD = result.order.slice(0, i + 1).reduce((acc, x, j) => {
                    const prev = j === 0 ? depot.coords : result.order[j - 1].coords;
                    return acc + haversine(prev, x.coords);
                  }, 0);
                  return (
                    <li key={s.id}>
                      <span className="opt-order-pin" style={{ background: color }}>{i + 1}</span>
                      <span className="opt-order-name">{s.name}</span>
                      <span className="opt-order-d">{cumD.toFixed(1)} km</span>
                    </li>
                  );
                })}
                <li>
                  <span className="opt-order-pin" style={{ background: "#0F172A" }}>★</span>
                  <span className="opt-order-name">{depot.name}</span>
                  <span className="opt-order-d">{isEs ? "regreso" : "return"}</span>
                </li>
              </ol>
            </>
          )}
        </div>
      )}

      {tab === "log" && (
        <div className="opt-options">
          <div className="opt-section-label">{isEs ? "Consola de red" : "Network console"}</div>
          <div className="opt-log">
            {!logs.length && <div style={{ color: "oklch(0.55 0.005 240)" }}>{isEs ? "Sin actividad. Ejecutá una optimización para ver los logs." : "No activity. Run an optimization to see logs."}</div>}
            {logs.map((l, i) => (
              <div key={i} className={`opt-log-row ${l.kind}`}>
                <span className="opt-log-t">{l.t}</span>
                <span className="opt-log-msg">{l.msg}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </aside>
  );
}

window.OptimizerPage = OptimizerPage;
