/* ============================================================
   ConstellationDashboard.jsx — wiring, RAF clock, memory sim (ESM)
   Default export: <ConstellationDashboard /> — монтируется как вкладка.
   ============================================================ */
import React, { useState, useEffect, useRef, useMemo } from "react";
import MapView from "./MapView.jsx";
import Timeline from "./Timeline.jsx";
import { Header, SidePanel, STATUS_META, fmtDur } from "./Panels.jsx";
import * as SIM from "./sim.js";
import { SATELLITES, STATIONS, ROIS, COL } from "./data.js";
import "./dashboard.css";

function buildEnv() {
  const epochMs = Date.now();
  const sats = SATELLITES, stations = STATIONS, rois = ROIS;
  const prohibits = SIM.buildProhibits(sats, epochMs);
  const env = { epochMs, sats, stations, rois, prohibits };
  const domain = { start: epochMs - 6 * 3600e3, end: epochMs + 18 * 3600e3 };
  const sched = SIM.buildSchedule(sats, env, domain.start, domain.end, 1);
  return { env, domain, sched };
}

function findInterval(list, t) {
  if (!list) return null;
  for (const iv of list) if (t >= iv.start && t < iv.end) return iv;
  return null;
}

function App() {
  const cfg = useMemo(buildEnv, []);
  const { env, domain, sched } = cfg;

  const apiRef = useRef({});
  const simRef = useRef(env.epochMs);
  const playingRef = useRef(true);
  const speedRef = useRef(10);
  const liveRef = useRef(false);
  const focusRef = useRef(null);
  const hoverRef = useRef(null);
  const memRef = useRef(Object.fromEntries(env.sats.map((s) => [s.id, s.mem0])));
  const lastT = useRef(performance.now());

  const [, setTick] = useState(0);
  const [playing, setPlaying] = useState(true);
  const [speed, setSpeed] = useState(10);
  const [live, setLive] = useState(false);
  const [focusId, setFocusId] = useState(null);
  const [query, setQuery] = useState("");
  const [cursor, setCursor] = useState(null);
  const [hover, setHover] = useState(null);

  function setFocus(id) { focusRef.current = id; setFocusId(id); }

  // map api callbacks
  useEffect(() => {
    const a = apiRef.current;
    a.simTime = () => simRef.current;
    a.onCursor = (ll) => setCursor(ll);
    a.onPick = (id, x, y) => { hoverRef.current = id; setHover(id ? { id, x, y } : null); };
    a.onClickPick = () => { if (hoverRef.current) setFocus(focusRef.current === hoverRef.current ? null : hoverRef.current); };
    a.onReady = () => a.draw && a.draw(simRef.current, { focusId: focusRef.current });
  }, []);

  // RAF loop
  useEffect(() => {
    let raf;
    let acc = 0;
    const loop = (now) => {
      const dt = Math.min(100, now - lastT.current);
      lastT.current = now;
      if (playingRef.current) {
        if (liveRef.current) simRef.current = Date.now();
        else simRef.current += dt * speedRef.current;
        if (simRef.current > domain.end) simRef.current = domain.end;
        if (simRef.current < domain.start) simRef.current = domain.start;
        // memory dynamics
        const secEq = (dt * speedRef.current) / 1000;
        for (const s of env.sats) {
          const stt = SIM.statusAt(s, simRef.current, env);
          let m = memRef.current[s.id];
          if (stt.state === "capture") m += 0.7 * secEq;
          else if (stt.state === "downlink") m -= 1.5 * secEq;
          else m += 0.02 * secEq; // idle housekeeping drift
          memRef.current[s.id] = Math.max(0, Math.min(100, m));
        }
      }
      if (apiRef.current.draw) apiRef.current.draw(simRef.current, { focusId: focusRef.current });
      acc += dt;
      if (acc >= 80) { acc = 0; setTick((t) => (t + 1) % 1e6); }
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, []);

  // ---- controls ----
  function onPlay() { const p = !playingRef.current; playingRef.current = p; setPlaying(p); if (!p) { liveRef.current = false; setLive(false); } }
  function onSpeed(s) { speedRef.current = s; setSpeed(s); if (s !== 1) { liveRef.current = false; setLive(false); } }
  function onNow() { liveRef.current = true; playingRef.current = true; speedRef.current = 1; simRef.current = Date.now(); setLive(true); setPlaying(true); setSpeed(1); }
  function onStep(d) { liveRef.current = false; setLive(false); simRef.current = Math.max(domain.start, Math.min(domain.end, simRef.current + d)); }
  function onScrub(t) { liveRef.current = false; setLive(false); simRef.current = Math.max(domain.start, Math.min(domain.end, t)); }

  // ---- derived (per render) ----
  const tNow = simRef.current;
  const statuses = {};
  const counts = { capture: 0, downlink: 0, prohibit: 0, eclipse: 0, optic: 0, radar: 0, total: env.sats.length };
  for (const s of env.sats) {
    const st = SIM.statusAt(s, tNow, env);
    statuses[s.id] = st;
    if (counts[st.state] !== undefined) counts[st.state]++;
    if (st.eclipse) counts.eclipse++;
    if (s.payload === "optic") counts.optic++; else counts.radar++;
  }
  const events = [];
  for (const s of env.sats) {
    const st = statuses[s.id];
    let iv = null, type = null, target = null, color = null;
    if (st.state === "capture") { iv = findInterval(sched[s.id].capture, tNow); type = "СЪЁМКА"; target = st.roi.name; color = STATUS_META.capture.c; }
    else if (st.state === "downlink") { iv = findInterval(sched[s.id].downlink, tNow); type = "СБРОС"; target = "ст. " + st.station.name; color = STATUS_META.downlink.c; }
    else if (st.state === "prohibit") { iv = findInterval(sched[s.id].prohibit, tNow); type = "ЗАПРЕТ"; target = st.prohibit.reason; color = STATUS_META.prohibit.c; }
    if (type) events.push({ id: s.id, satId: s.id, sat: s.name, type, target, color, remain: fmtDur((iv ? iv.end : tNow) - tNow) });
  }
  events.sort((a, b) => a.remain.localeCompare(b.remain));

  const focusSat = focusId ? env.sats.find((s) => s.id === focusId) : null;

  return (
    <div className="app">
      <Header simTime={tNow} playing={playing} speed={speed} live={live}
        onPlay={onPlay} onSpeed={onSpeed} onNow={onNow} onStep={onStep} />
      <div className="body">
        <SidePanel sats={env.sats} statuses={statuses} memory={memRef.current} counts={counts}
          events={events} focusId={focusId} onFocus={setFocus} query={query} setQuery={setQuery} />
        <div className="main">
          <div className="map-area">
            <MapView env={env} apiRef={apiRef} />
            <div className="map-caption">
              {focusSat ? (
                <FocusCard sat={focusSat} status={statuses[focusId]} mem={memRef.current[focusId]} onClose={() => setFocus(null)} />
              ) : (
                <div className="cap-hint">КЛИК ПО АППАРАТУ — ФОКУС И ПОДСВЕТКА СОБЫТИЙ</div>
              )}
            </div>
            <MapLegend />
            <div className="map-cursor">
              {cursor ? `${Math.abs(cursor.lat).toFixed(3)}° ${cursor.lat >= 0 ? "с.ш." : "ю.ш."}, ${Math.abs(cursor.lon).toFixed(3)}° ${cursor.lon >= 0 ? "в.д." : "з.д."}` : "—"}
            </div>
            {hover && hover.id !== focusId && (
              <div className="map-tip" style={{ left: hover.x + 14, top: hover.y - 8 }}>
                {env.sats.find((s) => s.id === hover.id)?.name}
              </div>
            )}
          </div>
          <div className="tl-area">
            <div className="tl-head">
              <span className="tl-title">ТАЙМЛАЙН СОБЫТИЙ</span>
              <div className="tl-legend">
                <LegItem c="#d99a4e" t="СЪЁМКА" />
                <LegItem c="#5b9bd5" t="СБРОС" />
                <LegItem c="#d9685f" t="ЗАПРЕТ РАБОТЫ" hatch />
                <LegItem c="rgba(120,140,180,0.5)" t="ТЕНЬ ЗЕМЛИ" />
              </div>
              <span className="tl-hint">{live ? "РЕЖИМ LIVE · ×1" : "СИМУЛЯЦИЯ ×" + speed} · перетащите для скраба</span>
            </div>
            <Timeline env={env} sched={sched} simTime={tNow} domain={domain} focusId={focusId}
              onScrub={onScrub} onFocus={setFocus} live={live} />
          </div>
        </div>
      </div>
    </div>
  );
}

function FocusCard({ sat, status, mem, onClose }) {
  const meta = STATUS_META[status.state];
  return (
    <div className="focus-card">
      <span className="fc-pay" style={{ background: sat.color }} />
      <div className="fc-info">
        <div className="fc-name">{sat.name}<button className="fc-x" onClick={onClose}>✕</button></div>
        <div className="fc-meta">
          <span>NORAD {sat.norad}</span><span className="fc-dot">·</span><span>{sat.group}</span>
          <span className="fc-dot">·</span><span style={{ color: meta.c }}>{status.eclipse ? "☾ " : ""}{meta.t}</span>
        </div>
      </div>
      <div className="fc-pos">
        <div>{Math.abs(status.p.lat).toFixed(2)}° {status.p.lat >= 0 ? "N" : "S"} · {Math.abs(status.p.lon).toFixed(2)}° {status.p.lon >= 0 ? "E" : "W"}</div>
        <div className="fc-mem">ПАМЯТЬ <b>{Math.round(mem)}%</b></div>
      </div>
    </div>
  );
}

function MapLegend() {
  return (
    <div className="map-legend">
      <div className="ml-row"><span className="ml-tri" />СТАНЦИЯ ПРИЁМА</div>
      <div className="ml-row"><span className="ml-sw" style={{ background: COL.optic }} />ОПТИКА</div>
      <div className="ml-row"><span className="ml-sw" style={{ background: COL.radar }} />ЛОКАЦИЯ</div>
      <div className="ml-row"><span className="ml-sw amber" />МАРШРУТ СЪЁМКИ</div>
      <div className="ml-row"><span className="ml-sw blue dash" />СБРОС НА СТАНЦИЮ</div>
      <div className="ml-row"><span className="ml-sw night" />ТЕНЬ ЗЕМЛИ</div>
    </div>
  );
}
function LegItem({ c, t, hatch }) {
  return <span className="leg-item"><i className={"leg-sw" + (hatch ? " hatch" : "")} style={{ background: hatch ? undefined : c }} />{t}</span>;
}

export default App;
