// Корень страницы «Редактор»: выбор активного КА/плана, черновик включений,
// drag/resize, добавление съёмки (карта) и сброса (станции), контекст других КА,
// undo/redo, проверка конфликтов (симуляция сервиса), применение правок.
const { useState: useStateE, useMemo: useMemoE, useEffect: useEffectE, useRef: useRefE, useCallback: useCbE } = React;
const HOUR_E = D.HOUR, DAY_E = D.DAY;

function guessMode(scType, label) {
  const modes = SENSOR_MODES[scType] || [];
  const l = (label || "").toLowerCase();
  if (l.includes("стерео")) return "stereo";
  if (l.includes("мульти")) return "ms";
  if (l.includes("прожектор")) return "spot";
  if (l.includes("скан")) return "scan";
  if (l.includes("панхром")) return "pan";
  return modes[0] ? modes[0].id : "pan";
}
function activePlanOf(scId) {
  const ps = D.plans.filter((p) => p.scId === scId);
  const act = ps.find((p) => p.start <= D.NOW && D.NOW <= p.end);
  return (act || ps.sort((a, b) => b.start - a.start)[0] || null);
}
function seedDraft(plan, sc) {
  if (!plan) return [];
  const p = orbitParams(sc);
  return plan.works.map((w) => {
    const base = { id: w.id, kind: w.kind, label: w.label, start: w.start, end: w.end, origin: "plan" };
    if (w.kind === "shoot") base.modeId = guessMode(sc.type, w.label);
    if (w.kind === "downlink") {
      const s = subPoint(p, (w.start + w.end) / 2);
      base.stationId = nearestStation(s.lat, s.lon, window.WorldGeo.GROUND_STATIONS).st.id;
    }
    return base;
  });
}

function EditorApp() {
  const params = new URLSearchParams(location.search);
  const urlPlan = params.get("plan");
  const urlSc = params.get("sc");
  const planById = useMemoE(() => { const m = {}; D.plans.forEach((p) => (m[p.id] = p)); return m; }, []);
  const scById = useMemoE(() => { const m = {}; D.spacecraft.forEach((s) => (m[s.id] = s)); return m; }, []);

  const initScId = urlSc || (urlPlan && planById[urlPlan] ? planById[urlPlan].scId : D.spacecraft[0].id);
  const [activeScId, setActiveScId] = useStateE(initScId);
  const activeSc = scById[activeScId];
  const p = useMemoE(() => orbitParams(activeSc), [activeScId]);

  const initPlanId = (urlPlan && planById[urlPlan] && planById[urlPlan].scId === activeScId) ? urlPlan : (activePlanOf(activeScId) || {}).id;
  const [planId, setPlanId] = useStateE(initPlanId);
  const plan = planById[planId];

  // Черновик + история.
  const [hist, setHist] = useStateE(() => ({ draft: seedDraft(plan, activeSc), past: [], future: [] }));
  const draft = hist.draft;
  const mutate = useCbE((fn) => setHist((h) => ({ draft: fn(h.draft), past: [...h.past, h.draft].slice(-50), future: [] })), []);
  const undo = () => setHist((h) => h.past.length ? ({ draft: h.past[h.past.length - 1], past: h.past.slice(0, -1), future: [h.draft, ...h.future] }) : h);
  const redo = () => setHist((h) => h.future.length ? ({ draft: h.future[0], past: [...h.past, h.draft], future: h.future.slice(1) }) : h);

  const reseed = (sid, pid) => {
    const sc = scById[sid]; const pl = planById[pid];
    setHist({ draft: seedDraft(pl, sc), past: [], future: [] });
    setSelected(null); setRightMode("none"); setTarget(null); setSelPass(null); setServiceFlags({});
  };
  const onPickSc = (sid) => {
    const pid = (activePlanOf(sid) || {}).id;
    setActiveScId(sid); setPlanId(pid); reseed(sid, pid);
  };
  const onPickPlan = (pid) => { setPlanId(pid); reseed(activeScId, pid); };

  // Окно планирования.
  const [win, setWin] = useStateE(() => {
    if (plan) return { t0: plan.start - 3 * HOUR_E, t1: plan.end + 3 * HOUR_E };
    return { t0: D.NOW - 6 * HOUR_E, t1: D.NOW + 18 * HOUR_E };
  });
  useEffectE(() => {
    if (plan) setWin({ t0: plan.start - 3 * HOUR_E, t1: plan.end + 3 * HOUR_E });
  }, [planId]);
  const zoom = (f) => setWin((wv) => { const c = (wv.t0 + wv.t1) / 2, half = (wv.t1 - wv.t0) / 2 * f; return { t0: c - half, t1: c + half }; });
  const panBy = (f) => setWin((wv) => { const d = (wv.t1 - wv.t0) * f; return { t0: wv.t0 + d, t1: wv.t1 + d }; });

  // Правый режим: none | inspect | shoot | downlink.
  const [rightMode, setRightMode] = useStateE("none");
  const [selected, setSelected] = useStateE(null);
  const selWork = draft.find((w) => w.id === selected) || null;

  // Цели съёмки.
  const [target, setTarget] = useStateE(null);
  const [selPass, setSelPass] = useStateE(null);
  const [modeId, setModeId] = useStateE(null);
  const passes = useMemoE(() => target ? targetPasses(p, target.lat, target.lon, win.t0, win.t1) : [], [target, p, win]);

  const onPickPoint = (ll) => {
    const name = "Цель " + String.fromCharCode(65 + (Math.abs(Math.round(ll.lat * 7 + ll.lon * 3)) % 26));
    setTarget({ id: "tg-" + Date.now(), name, lat: ll.lat, lon: ll.lon });
    setSelPass(null); setModeId((SENSOR_MODES[activeSc.type][0] || {}).id);
  };

  // Станции для сброса.
  const stationOptions = useMemoE(() => {
    if (rightMode !== "downlink") return [];
    return window.WorldGeo.GROUND_STATIONS.map((st) => ({ station: st, passes: stationPasses(p, st, win.t0, win.t1) }))
      .sort((a, b) => b.passes.length - a.passes.length);
  }, [rightMode, p, win]);
  const [dlSel, setDlSel] = useStateE(null);

  // Добавление включений.
  let newCtr = useRefE(0);
  const addShoot = () => {
    const dur = Math.min(selPass.end - selPass.start, 30 * 60 * 1000);
    const start = selPass.start + Math.floor((selPass.end - selPass.start - dur) / 2);
    const w = { id: "NW-" + (++newCtr.current) + "-" + Date.now() % 10000, kind: "shoot", label: modeLabel(activeSc.type, modeId), start, end: start + dur, modeId, targetName: target.name, targetLat: target.lat, targetLon: target.lon, isNew: true, origin: "added" };
    mutate((d) => [...d, w]); setSelected(w.id); setRightMode("inspect"); setTarget(null); setSelPass(null);
  };
  const addDownlink = () => {
    const ps = dlSel.pass, dur = Math.min(ps.end - ps.start, 12 * 60 * 1000);
    const start = ps.start + Math.floor((ps.end - ps.start - dur) / 2);
    const w = { id: "NW-" + (++newCtr.current) + "-" + Date.now() % 10000, kind: "downlink", label: "Сброс на НКПОР", start, end: start + dur, stationId: dlSel.station.id, isNew: true, origin: "added" };
    mutate((d) => [...d, w]); setSelected(w.id); setRightMode("inspect"); setDlSel(null);
  };
  const updateWork = (id, patch) => mutate((d) => d.map((w) => w.id === id ? { ...w, ...patch } : w));
  const onChangeWork = (id, patch) => mutate((d) => d.map((w) => w.id === id ? { ...w, ...patch } : w)); // drag/resize (история на каждый шаг)
  const deleteWork = (id) => { mutate((d) => d.filter((w) => w.id !== id)); setSelected(null); setRightMode("none"); };
  const duplicateWork = (id) => mutate((d) => { const w = d.find((x) => x.id === id); if (!w) return d; const off = 20 * 60 * 1000; const nw = { ...w, id: "NW-" + (++newCtr.current) + "-" + Date.now() % 10000, start: w.start + off, end: w.end + off, isNew: true, origin: "added" }; return [...d, nw]; });

  // Конфликты: пересечения по времени — живые; видимость — по кнопке (сервис).
  const overlaps = useMemoE(() => {
    const res = {};
    for (let i = 0; i < draft.length; i++) for (let j = i + 1; j < draft.length; j++) {
      const a = draft[i], b = draft[j];
      if (Math.max(a.start, b.start) < Math.min(a.end, b.end)) {
        (res[a.id] = res[a.id] || []).push("Пересечение по времени с «" + kindLabel(b.kind) + "»");
        (res[b.id] = res[b.id] || []).push("Пересечение по времени с «" + kindLabel(a.kind) + "»");
      }
    }
    return res;
  }, [draft]);
  const [serviceFlags, setServiceFlags] = useStateE({});
  const [checking, setChecking] = useStateE(false);
  const runCheck = () => {
    setChecking(true);
    setTimeout(() => {
      const res = {};
      for (const w of draft) {
        if (w.kind === "shoot" && w.targetLat != null) {
          const ok = targetPasses(p, w.targetLat, w.targetLon, w.start - 6 * HOUR_E, w.end + 6 * HOUR_E)
            .some((ps) => w.start >= ps.start - 1 && w.end <= ps.end + 1);
          if (!ok) (res[w.id] = res[w.id] || []).push("Съёмка вне зоны видимости цели");
        }
        if (w.kind === "downlink" && w.stationId) {
          const st = window.WorldGeo.GROUND_STATIONS.find((s) => s.id === w.stationId);
          const ok = stationPasses(p, st, w.start - 6 * HOUR_E, w.end + 6 * HOUR_E)
            .some((ps) => w.start >= ps.start - 1 && w.end <= ps.end + 1);
          if (!ok) (res[w.id] = res[w.id] || []).push("Сброс вне сеанса связи со станцией");
        }
      }
      setServiceFlags(res); setChecking(false);
    }, 450);
  };
  const conflicts = useMemoE(() => {
    const m = {};
    for (const k in overlaps) m[k] = [...overlaps[k]];
    for (const k in serviceFlags) m[k] = [...(m[k] || []), ...serviceFlags[k]];
    return m;
  }, [overlaps, serviceFlags]);
  const conflictCount = Object.keys(conflicts).length;

  // Контекст: другие КА (по умолчанию — своя группа).
  const groupMates = useMemoE(() => D.spacecraft.filter((s) => s.groupId === activeSc.groupId && s.id !== activeScId), [activeScId]);
  const [otherShown, setOtherShown] = useStateE(() => new Set());
  useEffectE(() => { setOtherShown(new Set(groupMates.slice(0, 6).map((s) => s.id))); }, [activeScId]);
  const [ctxScope, setCtxScope] = useStateE("group");
  const [ctxSearch, setCtxSearch] = useStateE("");
  const ivBySc = useMemoE(() => { const m = {}; D.plans.forEach((pl) => pl.works.forEach((w) => { (m[pl.scId] = m[pl.scId] || []).push({ ...w, scId: pl.scId }); })); return m; }, []);
  const otherLanes = useMemoE(() => [...otherShown].map((sid) => ({ sc: scById[sid], works: (ivBySc[sid] || []).filter((w) => w.end >= win.t0 && w.start <= win.t1) })).filter((l) => l.sc), [otherShown, win]);

  const ctxList = useMemoE(() => {
    const base = ctxScope === "group" ? groupMates : D.spacecraft.filter((s) => s.id !== activeScId);
    const q = ctxSearch.trim().toLowerCase();
    return base.filter((s) => !q || s.short.toLowerCase().includes(q) || s.name.toLowerCase().includes(q)).slice(0, 60);
  }, [ctxScope, ctxSearch, activeScId, groupMates]);
  const toggleOther = (id) => setOtherShown((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });

  // Дорожки видимости (цель + станция выбранного сброса).
  const visTracks = useMemoE(() => {
    const out = [];
    if (target) out.push({ id: "tgt", label: target.name, kind: "target", color: "var(--now)", windows: passes });
    if (selWork && selWork.kind === "downlink" && selWork.stationId) {
      const st = window.WorldGeo.GROUND_STATIONS.find((s) => s.id === selWork.stationId);
      if (st) out.push({ id: "st", label: st.name, kind: "station", color: "var(--accent)", windows: stationPasses(p, st, win.t0, win.t1) });
    }
    return out;
  }, [target, passes, selWork, p, win]);

  const [basemap, setBasemap] = useStateE("dark");
  const addedCount = draft.filter((w) => w.isNew).length;

  const onSelectWork = (id) => { setSelected(id); setRightMode("inspect"); };
  const applyDraft = () => {
    // запись в in-memory план (прототип)
    if (plan) plan.works = draft.map(({ id, kind, label, start, end, modeId, stationId, targetName }) => ({ id, kind, label, start, end, modeId, stationId, targetName }));
    setHist((h) => ({ ...h, past: [], future: [] }));
    setServiceFlags({});
    alert("Правки применены к плану " + planId + " (прототип, без записи во внешний сервис).");
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
      <TopTabs active="editor" />
      <EditorToolbar {...{ activeSc, scById, activeScId, onPickSc, planId, plan, onPickPlan, planById,
        undo, redo, canUndo: hist.past.length > 0, canRedo: hist.future.length > 0,
        runCheck, checking, conflictCount, applyDraft, addedCount, zoom, panBy, win, setRightMode, rightMode }} />
      <div style={{ flex: 1, display: "grid", gridTemplateColumns: `230px 1fr ${rightMode === "none" ? "0px" : "340px"}`, minHeight: 0 }}>
        <EditorRail {...{ activeSc, plan, draft, conflictCount, ctxList, ctxScope, setCtxScope, ctxSearch, setCtxSearch,
          otherShown, toggleOther, scById, setRightMode, basemap, setBasemap }} />

        <div style={{ display: "flex", flexDirection: "column", minWidth: 0, borderRight: rightMode === "none" ? "none" : "1px solid var(--line)" }}>
          {/* карта (для съёмки и контекста) */}
          <div style={{ height: "44%", position: "relative", borderBottom: "1px solid var(--line)", background: "#0a1410" }}>
            <MapView scList={[activeSc]} selectedPlan={{ scId: activeScId, works: draft }}
              window={win} now={D.NOW} layers={{ tracks: true, swath: true, shoot: true, downlink: true, stations: true, terminator: true }}
              onSelectSc={() => {}} paramsBy={paramsByOf([activeSc])} scById={scById}
              basemap={basemap} pickMode={rightMode === "shoot"} onPickPoint={onPickPoint} targets={target ? [target] : []} />
            {rightMode === "shoot" && (
              <div className="mono" style={{ position: "absolute", top: 10, left: "50%", transform: "translateX(-50%)", background: "var(--now)", color: "var(--bg-0)", fontSize: 11, fontWeight: 600, padding: "4px 12px", borderRadius: 20, boxShadow: "0 0 12px var(--now)", pointerEvents: "none" }}>
                Кликните точку съёмки на карте
              </div>
            )}
          </div>
          {/* таймлайн */}
          <div style={{ flex: 1, minHeight: 0 }} onClick={() => { if (rightMode === "inspect") { setSelected(null); setRightMode("none"); } }}>
            <EditorTimeline {...{ activeSc, draft, selectedWorkId: selected, onSelectWork, t0: win.t0, t1: win.t1, now: D.NOW, onChangeWork, otherLanes, visTracks, conflicts }} />
          </div>
        </div>

        {rightMode !== "none" && (
          <div style={{ background: "var(--bg-1)", overflowY: "auto", padding: "16px 16px 28px" }}>
            {rightMode === "inspect" && selWork && <WorkInspector work={selWork} activeSc={activeSc} conflicts={conflicts} onUpdate={updateWork} onDelete={deleteWork} onDuplicate={duplicateWork} onClose={() => { setSelected(null); setRightMode("none"); }} />}
            {rightMode === "inspect" && !selWork && <div style={{ color: "var(--ink-3)", fontSize: 12 }}>Включение не выбрано.</div>}
            {rightMode === "shoot" && <ShootPanel {...{ activeSc, target, passes, selectedPass: selPass, setSelectedPass: setSelPass, modeId, setModeId, onAdd: addShoot, onClose: () => { setRightMode("none"); setTarget(null); setSelPass(null); }, onClearTarget: () => { setTarget(null); setSelPass(null); } }} />}
            {rightMode === "downlink" && <DownlinkPanel {...{ activeSc, stationOptions, selected: dlSel, setSelected: setDlSel, onAdd: addDownlink, onClose: () => { setRightMode("none"); setDlSel(null); } }} />}
          </div>
        )}
      </div>
    </div>
  );
}

function paramsByOf(scs) { const m = {}; scs.forEach((s) => { if (s) m[s.id] = orbitParams(s); }); return m; }

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