// Main React app for Buenos Aires Topographic Data Landscape

const { useState, useEffect, useRef } = React;

// ============================================================
// Tweaks defaults — must be a single JSON block between markers
// ============================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "zExag": 1.0,
  "glow": 0.0,
  "gridDensity": 0.5,
  "showContours": true,
  "showLabels": true,
  "showCoastline": true,
  "showMapOverlay": true,
  "showMetroLines": true,
  "style": "contour",
  "autoOrbit": true,
  "playbackSpeed": 1.0
}/*EDITMODE-END*/;

// ============================================================
// App
// ============================================================
function App() {
  const canvasRef = useRef(null);
  const rendererRef = useRef(null);
  const animRef = useRef(null);

  // Tweaks
  const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Active layer state — array of layer ids with weights, supports stacking
  const [activeLayers, setActiveLayers] = useState([
    { id: "traffic", weight: 1.0 },
  ]);
  const [hour, setHour] = useState(14);
  const [playing, setPlaying] = useState(true);
  const [useRealElev, setUseRealElev] = useState(false);
  const [hovered, setHovered] = useState(null);
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const [selected, setSelected] = useState(null);
  const [compareMode, setCompareMode] = useState(false);
  const [compareLayer, setCompareLayer] = useState("green");
  const [realLayers, setRealLayers] = useState(() => window.REAL_DATA_LOADED || {});

  // Listen for async real-data load
  useEffect(() => {
    const onReady = () => setRealLayers({ ...(window.REAL_DATA_LOADED || {}) });
    window.addEventListener("realDataReady", onReady);
    // If it loaded synchronously before mount, pick it up now
    if (window.REAL_DATA_LOADED && Object.keys(window.REAL_DATA_LOADED).length) {
      setRealLayers({ ...window.REAL_DATA_LOADED });
    }
    return () => window.removeEventListener("realDataReady", onReady);
  }, []);

  // Init renderer
  useEffect(() => {
    const r = window.LandscapeRenderer(canvasRef.current, {
      activeLayers: activeLayers.map(l => l.id),
      layerWeights: activeLayers.map(l => l.weight),
      hour,
      useRealElev,
      zExag: tweaks.zExag,
      glow: tweaks.glow,
      gridDensity: tweaks.gridDensity,
      contours: tweaks.showContours,
      barrioLabels: tweaks.showLabels,
      sun: false,
      coastline: tweaks.showCoastline,
      mapOverlay: tweaks.showMapOverlay,
      metroLines: tweaks.showMetroLines,
      style: tweaks.style,
    });
    rendererRef.current = r;
    r.resize();
    r.render();

    const onResize = () => { r.resize(); r.render(); };
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  // Sync state to renderer + redraw
  useEffect(() => {
    const r = rendererRef.current;
    if (!r) return;
    r.setState({
      activeLayers: activeLayers.map(l => l.id),
      layerWeights: activeLayers.map(l => l.weight),
      hour,
      useRealElev,
      zExag: tweaks.zExag,
      glow: tweaks.glow,
      gridDensity: tweaks.gridDensity,
      contours: tweaks.showContours,
      barrioLabels: tweaks.showLabels,
      sun: false,
      style: tweaks.style,
      coastline: tweaks.showCoastline,
      mapOverlay: tweaks.showMapOverlay,
      metroLines: tweaks.showMetroLines,
      compareLayer: compareMode ? compareLayer : null,
      hovered,
    });
    r.render();
  }, [activeLayers, hour, useRealElev, tweaks, hovered, compareMode, compareLayer, realLayers]);

  // Auto-play time
  useEffect(() => {
    if (!playing) return;
    let last = performance.now();
    let acc = 0;
    const tick = (t) => {
      const dt = t - last; last = t;
      acc += dt * tweaks.playbackSpeed;
      if (acc > 1100) {  // ~1s per hour at 1x
        acc = 0;
        setHour(h => (h + 1) % 24);
      }
      animRef.current = requestAnimationFrame(tick);
    };
    animRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(animRef.current);
  }, [playing, tweaks.playbackSpeed]);

  // Auto orbit
  useEffect(() => {
    if (!tweaks.autoOrbit || hovered) return;
    let frame;
    const tick = () => {
      const r = rendererRef.current;
      if (r) {
        r.setCamera({ yaw: r.camera.yaw + 0.0025 });
        r.render();
      }
      frame = requestAnimationFrame(tick);
    };
    frame = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(frame);
  }, [tweaks.autoOrbit, hovered]);

  // Camera drag
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    let dragging = false;
    let dragMode = null; // "orbit" | "pan"
    let lastX = 0, lastY = 0;
    let moveTimer = null;

    const onDown = (e) => {
      if (e.button === 1) {
        e.preventDefault();
        dragMode = "pan";
      } else if (e.button === 0) {
        dragMode = "orbit";
      } else {
        return;
      }
      dragging = true;
      lastX = e.clientX; lastY = e.clientY;
      canvas.style.cursor = dragMode === "pan" ? "move" : "grabbing";
    };
    const onMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      const sx = e.clientX - rect.left;
      const sy = e.clientY - rect.top;
      setMousePos({ x: sx, y: sy });
      if (dragging) {
        const dx = e.clientX - lastX;
        const dy = e.clientY - lastY;
        lastX = e.clientX; lastY = e.clientY;
        const r = rendererRef.current;
        if (dragMode === "pan") {
          r.setCamera({
            panX: r.camera.panX + dx,
            panY: r.camera.panY + dy,
          });
        } else {
          r.setCamera({
            yaw: r.camera.yaw + dx * 0.005,
            pitch: Math.max(0.4, Math.min(1.45, r.camera.pitch - dy * 0.005)),
          });
        }
        r.render();
      } else {
        // Hover pick — throttle
        if (!moveTimer) {
          moveTimer = setTimeout(() => {
            moveTimer = null;
            const r = rendererRef.current;
            if (r) {
              const hit = r.pick(sx, sy);
              setHovered(hit);
            }
          }, 30);
        }
      }
    };
    const onUp = () => {
      dragging = false;
      dragMode = null;
      canvas.style.cursor = "grab";
    };
    const onWheel = (e) => {
      e.preventDefault();
      const r = rendererRef.current;
      const z = Math.max(12, Math.min(80, r.camera.zoom * (e.deltaY > 0 ? 0.92 : 1.08)));
      r.setCamera({ zoom: z });
      r.render();
    };
    const onAuxClick = (e) => {
      if (e.button === 1) e.preventDefault();
    };

    canvas.addEventListener("pointerdown", onDown);
    window.addEventListener("pointermove", onMove);
    window.addEventListener("pointerup", onUp);
    canvas.addEventListener("wheel", onWheel, { passive: false });
    canvas.addEventListener("auxclick", onAuxClick);
    canvas.style.cursor = "grab";
    return () => {
      canvas.removeEventListener("pointerdown", onDown);
      window.removeEventListener("pointermove", onMove);
      window.removeEventListener("pointerup", onUp);
      canvas.removeEventListener("wheel", onWheel);
      canvas.removeEventListener("auxclick", onAuxClick);
    };
  }, []);

  // Layer toggle helpers
  const toggleLayer = (id) => {
    setActiveLayers(prev => {
      if (prev.find(l => l.id === id)) {
        const filtered = prev.filter(l => l.id !== id);
        return filtered.length === 0 ? prev : filtered; // keep at least 1
      }
      return [...prev, { id, weight: 1.0 }];
    });
  };
  const setLayerWeight = (id, w) => {
    setActiveLayers(prev => prev.map(l => l.id === id ? { ...l, weight: w } : l));
  };
  const isActive = (id) => !!activeLayers.find(l => l.id === id);

  return (
    <div className="app">
      <canvas ref={canvasRef} className="stage" />
      <Hud
        hour={hour}
        playing={playing}
        setPlaying={setPlaying}
        setHour={setHour}
        activeLayers={activeLayers}
        useRealElev={useRealElev}
        setUseRealElev={setUseRealElev}
      />
      <SidePanel
        activeLayers={activeLayers}
        toggleLayer={toggleLayer}
        setLayerWeight={setLayerWeight}
        isActive={isActive}
        hour={hour}
        selected={selected}
        setSelected={setSelected}
        hovered={hovered}
        compareMode={compareMode}
        setCompareMode={setCompareMode}
        compareLayer={compareLayer}
        setCompareLayer={setCompareLayer}
        tweaks={tweaks}
        setTweak={setTweak}
      />
      {hovered && (
        <Tooltip hovered={hovered} hour={hour} activeLayers={activeLayers} mousePos={mousePos} />
      )}
    </div>
  );
}

// ============================================================
// HUD — top + bottom overlay (time scrubber, status)
// ============================================================
function Hud({ hour, playing, setPlaying, setHour, activeLayers, useRealElev, setUseRealElev }) {
  const layerNames = activeLayers.map(l => window.LAYERS.find(x => x.id === l.id).name).join(" + ");
  const hh = String(hour).padStart(2, "0");
  return (
    <>
      {/* Top bar */}
      <div className="hud hud-top">
        <div className="brandline">
          <span className="brand-mark">◢◤</span>
          <div className="brand-stack">
            <div>
              <span className="brand-text">CABA · TOPOGRAPHIC DATA LANDSCAPE</span>
              <span className="brand-sub"> · v1.0 · 48 BARRIOS · 8 INDICES</span>
            </div>
            <span className="brand-subtitle">Ciudad Autónoma de Buenos Aires</span>
          </div>
        </div>
        <div className="hud-controls">
          <button
            className={`pill ${useRealElev ? "active" : ""}`}
            onClick={() => setUseRealElev(v => !v)}
          >
            <span className="pill-dot" /> REAL ELEVATION
          </button>
        </div>
      </div>

      {/* Bottom: time scrubber */}
      <div className="hud hud-bottom">
        <button className="play-btn" onClick={() => setPlaying(p => !p)} aria-label={playing ? "pause" : "play"}>
          {playing ? <PauseIcon /> : <PlayIcon />}
        </button>
        <div className="time-block">
          <div className="time-display">
            <div className="time-clock">{hh}<span className="time-colon">:</span>00</div>
            <div className="time-meta">
              <span className="time-period">{periodLabel(hour)}</span>
              <span className="time-divider">·</span>
              <span className="time-layer">{layerNames}</span>
            </div>
          </div>
          <div className="scrubber">
            <div className="scrubber-track">
              {[...Array(24)].map((_, i) => (
                <div
                  key={i}
                  className={`scrubber-tick ${i === hour ? "active" : ""} ${[0, 6, 12, 18].includes(i) ? "major" : ""}`}
                  style={{ left: `${(i / 23) * 100}%` }}
                  onClick={() => setHour(i)}
                />
              ))}
              <div
                className="scrubber-thumb"
                style={{ left: `${(hour / 23) * 100}%` }}
              />
              <div
                className="scrubber-fill"
                style={{ width: `${(hour / 23) * 100}%` }}
              />
            </div>
            <div className="scrubber-labels">
              <span>00</span><span>06</span><span>12</span><span>18</span><span>24</span>
            </div>
          </div>
        </div>
      </div>
    </>
  );
}

function periodLabel(h) {
  if (h < 6) return "MADRUGADA";
  if (h < 12) return "MAÑANA";
  if (h < 14) return "MEDIODÍA";
  if (h < 19) return "TARDE";
  if (h < 22) return "ATARDECER";
  return "NOCHE";
}

const PlayIcon = () => <svg width="16" height="16" viewBox="0 0 16 16"><polygon points="3,2 14,8 3,14" fill="currentColor" /></svg>;
const PauseIcon = () => <svg width="16" height="16" viewBox="0 0 16 16"><rect x="3" y="2" width="3.5" height="12" fill="currentColor" /><rect x="9.5" y="2" width="3.5" height="12" fill="currentColor" /></svg>;

// ============================================================
// Side Panel
// ============================================================
function SidePanel({ activeLayers, toggleLayer, setLayerWeight, isActive, hour, selected, setSelected, hovered, compareMode, setCompareMode, compareLayer, setCompareLayer, tweaks, setTweak }) {
  const selectedBarrio = selected ? window.BARRIOS.find(b => b.id === selected) : null;

  return (
    <div className="panel">
      <div className="panel-header">
        <div className="panel-title">
          <span className="panel-bullet" />
          <span>LAYERS</span>
        </div>
        <div className="panel-sub">stack to build a terrain of livability</div>
        <div className="compare-row">
          <button
            className={`compare-toggle ${compareMode ? "on" : ""}`}
            onClick={() => setCompareMode(v => !v)}
          >
            <span className="compare-dot" /> COMPARE
          </button>
          {compareMode && (
            <select
              className="compare-select"
              value={compareLayer}
              onChange={e => setCompareLayer(e.target.value)}
            >
              {window.LAYERS.map(l => (
                <option key={l.id} value={l.id}>vs. {l.name}</option>
              ))}
            </select>
          )}
        </div>
      </div>

      <div className="layers">
        {window.LAYERS.map(layer => {
          const active = isActive(layer.id);
          const layerEntry = activeLayers.find(l => l.id === layer.id);
          // Live current value across all barrios (mean) for sparkline-ish
          const meanNow = mean(window.BARRIOS.map(b => window.DATA[layer.id][b.id][hour]));
          return (
            <div key={layer.id} className={`layer ${active ? "active" : ""}`}>
              <div className="layer-card">
                <button className="layer-row" onClick={() => toggleLayer(layer.id)}>
                  <span className="layer-glyph" style={{ color: layer.color, borderColor: active ? layer.color : "rgba(122,138,196,0.3)" }}>
                    {layer.icon}
                  </span>
                  <div className="layer-meta">
                    <div className="layer-name">{layer.name}</div>
                    <div className="layer-bar">
                      <div className="layer-bar-fill" style={{ width: `${meanNow * 100}%`, background: layer.color }} />
                    </div>
                  </div>
                  <span className="layer-short">{layer.short}</span>
                  {active && activeLayers.length > 1 && (
                    <span className="layer-weight-inline" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                      <input
                        type="range"
                        min="0.1" max="2" step="0.1"
                        value={layerEntry.weight}
                        onChange={e => setLayerWeight(layer.id, parseFloat(e.target.value))}
                        aria-label={`${layer.name} weight`}
                      />
                      <span className="weight-val">{layerEntry.weight.toFixed(1)}×</span>
                    </span>
                  )}
                  <span className={`layer-tick ${active ? "on" : ""}`}>{active ? "●" : "○"}</span>
                </button>
              </div>
            </div>
          );
        })}
      </div>

      <div className="stats">
        <div className="stats-header">
          <span>{selectedBarrio ? "BARRIO DETAIL" : (hovered ? "HOVER" : "SUMMARY")}</span>
          {selectedBarrio && (
            <button className="close-btn" onClick={() => setSelected(null)}>×</button>
          )}
        </div>
        {selectedBarrio ? (
          <BarrioDetail barrio={selectedBarrio} hour={hour} />
        ) : hovered ? (
          <BarrioDetail barrio={hovered.barrio} hour={hour} />
        ) : (
          <SummaryStats activeLayers={activeLayers} hour={hour} />
        )}
      </div>

      <div className="settings-block">
        <div className="settings-header">
          <span className="settings-bullet" />
          <span>SETTINGS</span>
        </div>
        <Settings tweaks={tweaks} setTweak={setTweak} />
      </div>

      <div className="panel-footer">
        <div className="footer-line">
          <span className="dim">SOURCE</span>
          <span>{buildSourceLine()}</span>
        </div>
        <div className="footer-line">
          <span className="dim">RENDER</span>
          <span>CANVAS 2D · IDW MESH</span>
        </div>
        <div className="footer-line">
          <span className="dim">DRAG</span>
          <span>LMB ORBIT · MMB HOLD PAN · WHEEL ZOOM</span>
        </div>
      </div>
    </div>
  );
}

function mean(xs) { let s = 0; for (const x of xs) s += x; return xs.length ? s / xs.length : 0; }

function cleanUnitLabel(unit) {
  return String(unit || "").replace(/\s*\([^)]*\)/g, "").trim();
}

function extractUnitSources(unit) {
  const text = String(unit || "");
  const out = [];
  const matches = text.match(/\(([^)]*)\)/g);
  if (!matches) return out;
  for (const m of matches) {
    const src = m.slice(1, -1).trim();
    if (src) out.push(src);
  }
  return out;
}

function buildSourceLine() {
  const tags = [];
  const seen = new Set();
  const add = (s) => {
    if (!seen.has(s)) {
      seen.add(s);
      tags.push(s);
    }
  };

  for (const layer of window.LAYERS || []) {
    const text = String(layer.unit || "").toLowerCase();
    if (text.includes("gcba")) add("GCBA");
    if (text.includes("ausa")) add("AUSA");
    if (text.includes("indec")) add("INDEC");
  }

  if (!tags.length) return "GCBA";
  return tags.join(" · ");
}

function BarrioDetail({ barrio, hour }) {
  return (
    <div className="barrio-detail">
      <div className="barrio-name">{barrio.name}</div>
      <div className="barrio-comuna">COMUNA {barrio.comuna}</div>
      <div className="barrio-table">
        {window.LAYERS.map(layer => {
          const v = window.DATA[layer.id][barrio.id][hour];
          const real = window.formatValue(layer.id, barrio.id, hour);
          return (
            <div key={layer.id} className="barrio-row">
              <span className="barrio-row-icon" style={{ color: layer.color }}>{layer.icon}</span>
              <span className="barrio-row-name">{layer.name}</span>
              <span className="barrio-row-bar">
                <span className="barrio-row-bar-fill" style={{ width: `${v * 100}%`, background: layer.color }} />
              </span>
              <span className="barrio-row-val">{real}<span className="dim"> {cleanUnitLabel(layer.unit)}</span></span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function SummaryStats({ activeLayers, hour }) {
  // Top-3 barrios for combined active layers
  const scores = window.BARRIOS.map(b => {
    let s = 0, w = 0;
    for (const l of activeLayers) {
      s += livabilityAdjustedValue(l.id, window.DATA[l.id][b.id][hour]) * l.weight;
      w += l.weight;
    }
    return { b, s: w > 0 ? s / w : 0 };
  }).sort((a, b) => b.s - a.s);

  return (
    <div className="summary">
      <div className="summary-title">PEAKS · HOUR {String(hour).padStart(2, "0")}:00</div>
      <div className="summary-list">
        {scores.slice(0, 5).map((it, i) => (
          <div key={it.b.id} className="summary-row">
            <span className="rank">#{i + 1}</span>
            <span className="rank-name">{it.b.name}</span>
            <span className="rank-bar">
              <span className="rank-bar-fill" style={{ width: `${it.s * 100}%` }} />
            </span>
            <span className="rank-val">{(it.s * 100).toFixed(0)}</span>
          </div>
        ))}
      </div>
      <div className="summary-title" style={{ marginTop: "16px" }}>VALLEYS · LOW READS</div>
      <div className="summary-list">
        {scores.slice(-3).reverse().map((it, i) => (
          <div key={it.b.id} className="summary-row valley">
            <span className="rank">↓</span>
            <span className="rank-name">{it.b.name}</span>
            <span className="rank-bar">
              <span className="rank-bar-fill" style={{ width: `${it.s * 100}%` }} />
            </span>
            <span className="rank-val">{(it.s * 100).toFixed(0)}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function livabilityAdjustedValue(layerId, value) {
  const layer = (window.LAYERS || []).find(l => l.id === layerId);
  const direction = layer?.livability || "negative";
  return direction === "negative" ? (1 - value) : value;
}

// ============================================================
// Tooltip
// ============================================================
function Tooltip({ hovered, hour, activeLayers, mousePos }) {
  const b = hovered.barrio;
  const score = computeBarrioLivabilityScore(b.id, hour, activeLayers);
  return (
    <div className="tooltip" style={{ left: mousePos.x + 18, top: mousePos.y + 18 }}>
      <div className="tt-name">{b.name}</div>
      <div className="tt-comuna">C{String(b.comuna).padStart(2, "0")}</div>
      <div className="tt-rows">
        <div className="tt-row">
          <span style={{ color: "#FFEB7A" }}>◆</span>
          <span>LIVABILITY SCORE <span className="dim">{(score * 100).toFixed(1)} / 100</span></span>
        </div>
      </div>
    </div>
  );
}

function computeBarrioLivabilityScore(barrioId, hour, activeLayers) {
  let s = 0;
  let w = 0;
  for (const l of activeLayers || []) {
    const v = window.DATA?.[l.id]?.[barrioId]?.[hour] ?? 0;
    s += livabilityAdjustedValue(l.id, v) * l.weight;
    w += l.weight;
  }
  return w > 0 ? s / w : 0;
}

// ============================================================
// Settings — inline section in side panel
// ============================================================
function Settings({ tweaks, setTweak }) {
  return (
    <div className="settings">
      <Setting label="Surface">
        <SegBtns
          value={tweaks.style}
          options={[
            { label: "WIRE", value: "wireframe" },
            { label: "FILLED", value: "filled" },
            { label: "CONTOUR", value: "contour" },
          ]}
          onChange={v => setTweak("style", v)}
        />
      </Setting>
      <Setting label="Grid">
        <SegBtns
          value={String(tweaks.gridDensity)}
          options={[
            { label: "SPARSE", value: "0.33" },
            { label: "MED", value: "0.5" },
            { label: "DENSE", value: "1" },
          ]}
          onChange={v => setTweak("gridDensity", parseFloat(v))}
        />
      </Setting>
      <Setting label="Z Exag">
        <SliderRow value={tweaks.zExag} min={0.2} max={3} step={0.1} fmt={v => v.toFixed(1) + "×"} onChange={v => setTweak("zExag", v)} />
      </Setting>
      <Setting label="Glow">
        <SliderRow value={tweaks.glow} min={0} max={2} step={0.1} fmt={v => v.toFixed(1)} onChange={v => setTweak("glow", v)} />
      </Setting>
      <Setting label="Speed">
        <SliderRow value={tweaks.playbackSpeed} min={0.25} max={4} step={0.25} fmt={v => v.toFixed(2) + "×"} onChange={v => setTweak("playbackSpeed", v)} />
      </Setting>
      <div className="settings-toggles">
        <Toggle label="DISTRICTS" value={tweaks.showMapOverlay} onChange={v => setTweak("showMapOverlay", v)} />
        <Toggle label="COAST" value={tweaks.showCoastline} onChange={v => setTweak("showCoastline", v)} />
        <Toggle label="METRO" value={tweaks.showMetroLines} onChange={v => setTweak("showMetroLines", v)} />
        <Toggle label="CONTOUR" value={tweaks.showContours} onChange={v => setTweak("showContours", v)} />
        <Toggle label="LABELS" value={tweaks.showLabels} onChange={v => setTweak("showLabels", v)} />
        <Toggle label="ORBIT" value={tweaks.autoOrbit} onChange={v => setTweak("autoOrbit", v)} />
      </div>
    </div>
  );
}

function Setting({ label, children }) {
  return (
    <div className="setting">
      <div className="setting-label">{label}</div>
      <div className="setting-control">{children}</div>
    </div>
  );
}

function SegBtns({ value, options, onChange }) {
  return (
    <div className="seg">
      {options.map(o => (
        <button
          key={o.value}
          className={`seg-btn ${value === o.value ? "active" : ""}`}
          onClick={() => onChange(o.value)}
        >{o.label}</button>
      ))}
    </div>
  );
}

function SliderRow({ value, min, max, step, fmt, onChange }) {
  return (
    <div className="slider-row">
      <input
        type="range"
        min={min} max={max} step={step}
        value={value}
        onChange={e => onChange(parseFloat(e.target.value))}
      />
      <span className="slider-val">{fmt(value)}</span>
    </div>
  );
}

function Toggle({ label, value, onChange }) {
  return (
    <button className={`toggle ${value ? "on" : ""}`} onClick={() => onChange(!value)}>
      <span className="toggle-pip" />
      <span className="toggle-label">{label}</span>
    </button>
  );
}

// Mount
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
