import { useEffect, useMemo, useRef, useState } from "react";
import { DEFAULT_EXPLORER_URL, fetchLivePoses, toLiveWorld } from "./live";
import {
  AREA,
  MAX_ASPECT,
  areaWidth,
  fmtBytes,
  makeWorld,
  seed,
  tick,
  type World,
} from "./sim";

const VIEW = 1000;
const PANEL_W = 320;
const HEADER_H = 70;

// Patrol field aspect ratio for the current window: wide monitors get a
// wider field instead of empty side margins. Quantized to 0.25 steps so
// minor resizes don't rebuild the world.
function displayAspect(): number {
  const w = Math.max(320, window.innerWidth - PANEL_W);
  const h = Math.max(320, window.innerHeight - HEADER_H);
  const q = Math.round((w / h) * 4) / 4;
  return Math.max(1, Math.min(MAX_ASPECT, q));
}

const LIVE_POLL_MS = 2000;

export default function App(): JSX.Element {
  const [droneCount, setDroneCount] = useState(8);
  const [speedup, setSpeedup] = useState(1);
  const [paused, setPaused] = useState(false);
  const [aspect, setAspect] = useState(displayAspect);
  const [mode, setMode] = useState<"sim" | "live">("sim");
  const [liveError, setLiveError] = useState<string | null>(null);
  const [world, setWorld] = useState<World>(() => {
    seed(42);
    return makeWorld(8, displayAspect());
  });
  const raf = useRef(0);
  const last = useRef(performance.now());
  const liveWorld = useRef<World | null>(null);

  useEffect(() => {
    seed(42);
    setWorld(makeWorld(droneCount, aspect));
  }, [droneCount, aspect]);

  useEffect(() => {
    let timer = 0;
    const onResize = (): void => {
      window.clearTimeout(timer);
      timer = window.setTimeout(() => setAspect(displayAspect()), 250);
    };
    window.addEventListener("resize", onResize);
    return () => {
      window.clearTimeout(timer);
      window.removeEventListener("resize", onResize);
    };
  }, []);

  useEffect(() => {
    if (mode !== "sim") return;
    const loop = (now: number): void => {
      const dt = Math.min(0.05, (now - last.current) / 1000);
      last.current = now;
      if (!paused && dt > 0) {
        setWorld((w) => tick(w, dt * speedup));
      }
      raf.current = requestAnimationFrame(loop);
    };
    raf.current = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf.current);
  }, [paused, speedup, mode]);

  // Live mode: poll the explorer's read-only SQL API for real poses
  useEffect(() => {
    if (mode !== "live") return;
    let cancelled = false;
    const poll = async (): Promise<void> => {
      try {
        const poses = await fetchLivePoses(DEFAULT_EXPLORER_URL);
        if (cancelled) return;
        setLiveError(poses.length === 0 ? "no drone data in the lake yet" : null);
        liveWorld.current = toLiveWorld(poses, liveWorld.current, LIVE_POLL_MS / 1000);
        setWorld(liveWorld.current);
      } catch (err) {
        if (!cancelled) setLiveError(err instanceof Error ? err.message : String(err));
      }
    };
    void poll();
    const timer = window.setInterval(() => void poll(), LIVE_POLL_MS);
    return () => {
      cancelled = true;
      window.clearInterval(timer);
    };
  }, [mode]);

  useEffect(() => {
    if (mode === "sim") {
      liveWorld.current = null;
      setLiveError(null);
      seed(42);
      setWorld(makeWorld(droneCount, aspect));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- reset only on mode change
  }, [mode]);

  const activeLinks = useMemo(
    () => [...world.links.values()].sort((a, b) => b.rate - a.rate),
    [world.links],
  );
  const busiest = activeLinks.slice(0, 6);

  return (
    <div style={styles.shell}>
      <header style={styles.header}>
        <div>
          <div style={styles.title}>SWARM HOUSE</div>
          <div style={styles.subtitle}>
            swarm data plane — pose broadcast + opportunistic bulk sync
          </div>
        </div>
        <div style={styles.controls}>
          <button
            style={{ ...styles.button, ...(mode === "live" ? styles.buttonActive : {}) }}
            onClick={() => setMode((m) => (m === "sim" ? "live" : "sim"))}
            title={`live source: ${DEFAULT_EXPLORER_URL}`}
          >
            {mode === "sim" ? "go live" : "back to sim"}
          </button>
          {mode === "live" && (
            <span style={liveError ? styles.liveError : styles.liveOk}>
              {liveError ?? `live · ${DEFAULT_EXPLORER_URL}`}
            </span>
          )}
          {mode === "sim" && (
            <>
              <label style={styles.label}>
                drones
                <input
                  type="range"
                  min={3}
                  max={24}
                  value={droneCount}
                  onChange={(e) => setDroneCount(Number(e.target.value))}
                />
                <span style={styles.value}>{droneCount}</span>
              </label>
              <label style={styles.label}>
                speed
                <input
                  type="range"
                  min={0.5}
                  max={6}
                  step={0.5}
                  value={speedup}
                  onChange={(e) => setSpeedup(Number(e.target.value))}
                />
                <span style={styles.value}>{speedup}x</span>
              </label>
              <button style={styles.button} onClick={() => setPaused((p) => !p)}>
                {paused ? "resume" : "pause"}
              </button>
            </>
          )}
        </div>
      </header>

      <div style={styles.main}>
        <svg
          viewBox={`0 0 ${sx(areaWidth())} ${VIEW}`}
          style={styles.canvas}
          preserveAspectRatio="xMidYMid meet"
        >
          <defs>
            <radialGradient id="ground" cx="50%" cy="50%" r="75%">
              <stop offset="0%" stopColor="#101826" />
              <stop offset="100%" stopColor="#0a0f18" />
            </radialGradient>
          </defs>
          <rect width={sx(areaWidth())} height={VIEW} fill="url(#ground)" />
          {gridLines()}

          {/* Mesh links */}
          {activeLinks.map((l) => {
            const a = world.drones[l.a];
            const b = world.drones[l.b];
            if (!a || !b) return null;
            const mx = (a.pos.x + b.pos.x) / 2;
            const my = (a.pos.y + b.pos.y) / 2;
            const hot = l.rate > 20_000;
            return (
              <g key={l.key}>
                <line
                  x1={sx(a.pos.x)}
                  y1={sy(a.pos.y)}
                  x2={sx(b.pos.x)}
                  y2={sy(b.pos.y)}
                  stroke={hot ? "#39d98a" : "#3d7bd9"}
                  strokeWidth={hot ? 2.2 : 1}
                  opacity={0.25 + l.flash * 0.75}
                />
                {hot && (
                  <text x={sx(mx)} y={sy(my) - 6} style={styles.linkLabel as never}>
                    {fmtBytes(l.rate)}/s · Σ{fmtBytes(l.totalUp + l.totalDown)}
                  </text>
                )}
              </g>
            );
          })}

          {/* Obstacles */}
          {world.obstacles.map((ob) => (
            <g key={ob.id}>
              <circle
                cx={sx(ob.pos.x)}
                cy={sy(ob.pos.y)}
                r={(ob.radius / AREA) * VIEW}
                fill={ob.moving ? "#5a2a35" : "#2a3245"}
                stroke={ob.moving ? "#c05a6a" : "#46536e"}
                strokeWidth={1.5}
                opacity={0.85}
              />
              {ob.moving && (
                <text x={sx(ob.pos.x)} y={sy(ob.pos.y) + 4} style={styles.obLabel as never}>
                  transit
                </text>
              )}
            </g>
          ))}

          {/* Drones */}
          {world.drones.map((d) => (
            <g
              key={d.id}
              transform={`translate(${sx(d.pos.x)} ${sy(d.pos.y)}) rotate(${(d.heading * 180) / Math.PI + 90})`}
            >
              <polygon points="0,-11 7,9 0,5 -7,9" fill="#e8ecf4" stroke="#7ea0d0" strokeWidth={1} />
              <text y={24} style={styles.droneLabel as never} transform={`rotate(${-((d.heading * 180) / Math.PI + 90)})`}>
                {d.name ?? `dr-${String(d.id + 1).padStart(2, "0")} · ch${d.channel}`} · {d.battery.toFixed(0)}%
              </text>
            </g>
          ))}
        </svg>

        <aside style={styles.panel}>
          <Section title="Mission">
            <Stat name="sim time" value={`${world.t.toFixed(0)} s`} />
            <Stat name="drones" value={String(world.drones.length)} />
            <Stat name="active links" value={String(world.links.size)} />
            <Stat name="pose broadcasts" value={String(world.broadcasts)} />
            <Stat name="total transferred" value={fmtBytes(world.totalBytes)} />
          </Section>
          <Section title="Busiest links">
            {busiest.map((l) => (
              <div key={l.key} style={styles.linkRow}>
                <span style={styles.linkName}>
                  {world.drones[l.a]?.name ?? `dr-${String(l.a + 1).padStart(2, "0")}`} ↔{" "}
                  {world.drones[l.b]?.name ?? `dr-${String(l.b + 1).padStart(2, "0")}`}
                </span>
                <span style={styles.linkStat}>
                  {fmtBytes(l.rate)}/s · ↑{fmtBytes(l.totalUp)} ↓{fmtBytes(l.totalDown)}
                </span>
              </div>
            ))}
            {busiest.length === 0 && <div style={styles.dim}>no links in range</div>}
          </Section>
          <Section title="Legend">
            <div style={styles.dim}>blue line — pose broadcast (5 Hz, ~46 B)</div>
            <div style={styles.dim}>green line — bulk sync (sealed partitions)</div>
            <div style={styles.dim}>flash — broadcast event delivered</div>
            <div style={styles.dim}>red circle — transit object crossing the area</div>
          </Section>
        </aside>
      </div>
    </div>
  );
}

function sx(x: number): number {
  return (x / AREA) * VIEW;
}
function sy(y: number): number {
  return (y / AREA) * VIEW;
}

function gridLines(): JSX.Element {
  const width = sx(areaWidth());
  const step = VIEW / 10; // one cell per 100 m in both directions
  const lines = [];
  for (let p = step; p < width; p += step) {
    lines.push(
      <line key={`v${p}`} x1={p} y1={0} x2={p} y2={VIEW} stroke="#1a2436" strokeWidth={1} />,
    );
  }
  for (let p = step; p < VIEW; p += step) {
    lines.push(
      <line key={`h${p}`} x1={0} y1={p} x2={width} y2={p} stroke="#1a2436" strokeWidth={1} />,
    );
  }
  return <g>{lines}</g>;
}

function Section({ title, children }: { title: string; children: React.ReactNode }): JSX.Element {
  return (
    <div style={styles.section}>
      <div style={styles.sectionTitle}>{title}</div>
      {children}
    </div>
  );
}

function Stat({ name, value }: { name: string; value: string }): JSX.Element {
  return (
    <div style={styles.statRow}>
      <span style={styles.dim}>{name}</span>
      <span style={styles.statValue}>{value}</span>
    </div>
  );
}

const styles: Record<string, React.CSSProperties> = {
  shell: {
    minHeight: "100vh",
    background: "#070b12",
    color: "#dde4f0",
    fontFamily: "'JetBrains Mono', ui-monospace, monospace",
    display: "flex",
    flexDirection: "column",
  },
  header: {
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "14px 22px",
    borderBottom: "1px solid #1a2436",
    flexWrap: "wrap",
    gap: 12,
  },
  title: { fontSize: 18, fontWeight: 700, letterSpacing: 4, color: "#8fb4e8" },
  subtitle: { fontSize: 11, color: "#5a6a85", marginTop: 2 },
  controls: { display: "flex", gap: 18, alignItems: "center", flexWrap: "wrap" },
  label: { display: "flex", gap: 8, alignItems: "center", fontSize: 12, color: "#8b98b0" },
  value: { minWidth: 30, color: "#dde4f0" },
  button: {
    background: "#16233a",
    color: "#8fb4e8",
    border: "1px solid #2a3c5c",
    borderRadius: 6,
    padding: "6px 16px",
    fontFamily: "inherit",
    fontSize: 12,
    cursor: "pointer",
  },
  buttonActive: { background: "#1a3a2a", color: "#39d98a", borderColor: "#2a5c3c" },
  liveOk: { fontSize: 11, color: "#39d98a" },
  liveError: { fontSize: 11, color: "#c05a6a" },
  main: { display: "flex", flex: 1, gap: 0, minHeight: 0 },
  canvas: { flex: 1, minWidth: 0, display: "block" },
  panel: {
    width: 320,
    borderLeft: "1px solid #1a2436",
    padding: 16,
    overflowY: "auto",
    background: "#0a0f18",
  },
  section: { marginBottom: 22 },
  sectionTitle: {
    fontSize: 11,
    letterSpacing: 2,
    color: "#5a6a85",
    textTransform: "uppercase",
    marginBottom: 10,
    borderBottom: "1px solid #1a2436",
    paddingBottom: 5,
  },
  statRow: { display: "flex", justifyContent: "space-between", padding: "3px 0", fontSize: 12 },
  statValue: { color: "#8fb4e8" },
  linkRow: {
    display: "flex",
    flexDirection: "column",
    padding: "4px 0",
    fontSize: 11,
    borderBottom: "1px dotted #141d2e",
  },
  linkName: { color: "#c8d2e4" },
  linkStat: { color: "#5a8c6e", fontSize: 10, marginTop: 1 },
  dim: { color: "#5a6a85", fontSize: 11, padding: "2px 0" },
  linkLabel: { fill: "#39d98a", fontSize: 10, textAnchor: "middle", fontFamily: "inherit" },
  droneLabel: { fill: "#6a7a95", fontSize: 9, textAnchor: "middle", fontFamily: "inherit" },
  obLabel: { fill: "#c05a6a", fontSize: 9, textAnchor: "middle", fontFamily: "inherit" },
};
