// US Network Map — ambient background animation.
// JSX source for packages/design-system/src/primitives/NetworkMap (the typed port).
//
// Cycle:
//   1. A random city pulses and sends a line to the vault.
//   2. The vault flashes.
//   3. 3–5 outgoing lines fan from the vault to random cities.
//   4. Pause, then loop.
//
// Color contract lives in CSS — see brand/components.css under
// NETWORK MAP. The component reads its palette from scoped custom
// properties on .network-map, with [data-theme="light"] overrides
// doing the swap. No NET_THEMES, no theme prop, no Context bridge.
//
// Load AFTER lib/onrail-ui.jsx so the global `React` is bound.

const VAULT = { x: 712, y: 430, label: "Onrail Vault" };

const CITIES = [
  // Real-city baseline
  { id: "sea", name: "Seattle",        x: 180,  y: 165 },
  { id: "spo", name: "Spokane",        x: 220,  y: 178 },
  { id: "pdx", name: "Portland",       x: 175,  y: 215 },
  { id: "sfo", name: "San Francisco",  x: 150,  y: 360 },
  { id: "lax", name: "Los Angeles",    x: 235,  y: 480 },
  { id: "san", name: "San Diego",      x: 265,  y: 525 },
  { id: "lvs", name: "Las Vegas",      x: 315,  y: 455 },
  { id: "phx", name: "Phoenix",        x: 365,  y: 525 },
  { id: "den", name: "Denver",         x: 530,  y: 395 },
  { id: "slc", name: "Salt Lake",      x: 425,  y: 355 },
  { id: "boi", name: "Boise",          x: 365,  y: 235 },
  { id: "abq", name: "Albuquerque",    x: 530,  y: 500 },
  { id: "hou", name: "Houston",        x: 775,  y: 605 },
  { id: "msy", name: "New Orleans",    x: 875,  y: 618 },
  { id: "pns", name: "Pensacola",      x: 950,  y: 605 },
  { id: "chi", name: "Chicago",        x: 875,  y: 315 },
  { id: "msp", name: "Minneapolis",    x: 815,  y: 230 },
  { id: "det", name: "Detroit",        x: 970,  y: 285 },
  { id: "cle", name: "Cleveland",      x: 1015, y: 320 },
  { id: "ind", name: "Indianapolis",   x: 925,  y: 385 },
  { id: "nas", name: "Nashville",      x: 925,  y: 475 },
  { id: "atl", name: "Atlanta",        x: 975,  y: 535 },
  { id: "clt", name: "Charlotte",      x: 1060, y: 465 },
  { id: "tpa", name: "Tampa",          x: 1080, y: 615 },
  { id: "mia", name: "Miami",          x: 1145, y: 655 },
  { id: "jax", name: "Jacksonville",   x: 1060, y: 575 },
  { id: "dc",  name: "Washington DC",  x: 1130, y: 385 },
  { id: "phl", name: "Philadelphia",   x: 1160, y: 345 },
  { id: "nyc", name: "New York",       x: 1190, y: 320 },
  { id: "buf", name: "Buffalo",        x: 1090, y: 308 },
  { id: "bos", name: "Boston",         x: 1220, y: 280 },
  { id: "btv", name: "Burlington",     x: 1170, y: 248 },
  { id: "pit", name: "Pittsburgh",     x: 1040, y: 365 },
  // Designer-tuned constellation infill (from map-editor sessions —
  // fills perceptual density gaps so the random-sample animation
  // reads as a network, not a sparse cluster).
  { id: "p01", name: "Pt 34", x: 589,  y: 599 },
  { id: "p02", name: "Pt 35", x: 626,  y: 283 },
  { id: "p03", name: "Pt 36", x: 548,  y: 230 },
  { id: "p04", name: "Pt 37", x: 707,  y: 187 },
  { id: "p05", name: "Pt 38", x: 379,  y: 181 },
  { id: "p06", name: "Pt 39", x: 113,  y: 187 },
  { id: "p07", name: "Pt 40", x: 976,  y: 230 },
  { id: "p08", name: "Pt 41", x: 1375, y: 297 },
  { id: "p09", name: "Pt 42", x: 819,  y: 294 },
  { id: "p10", name: "Pt 43", x: 1209, y: 193 },
  { id: "p11", name: "Pt 44", x: 1074, y: 199 },
  { id: "p12", name: "Pt 45", x: 478,  y: 630 },
  { id: "p13", name: "Pt 46", x: 688,  y: 526 },
  { id: "p14", name: "Pt 47", x: 947,  y: 679 },
];

// Pick `n` random distinct cities from a provided list.
function sampleCities(pool, n, exclude = []) {
  const candidates = pool.filter((c) => !exclude.includes(c.id));
  const out = [];
  while (out.length < n && candidates.length) {
    const i = Math.floor(Math.random() * candidates.length);
    out.push(candidates.splice(i, 1)[0]);
  }
  return out;
}

// ── NetConnection ────────────────────────────────────────────────
function NetConnection({ from, to, delay = 0, duration = 1700, onDone, onArrive }) {
  const [progress, setProgress] = React.useState(0);
  const [started, setStarted]   = React.useState(delay === 0);

  React.useEffect(() => {
    if (!started) {
      const t = setTimeout(() => setStarted(true), delay);
      return () => clearTimeout(t);
    }
    let raf, calledArrive = false;
    const start = performance.now();
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      setProgress(t);
      if (t > 0.92 && !calledArrive) { calledArrive = true; onArrive && onArrive(); }
      if (t < 1) raf = requestAnimationFrame(tick);
      else setTimeout(() => onDone && onDone(), 320);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [started]);

  if (!started) return null;

  const px = from.x + (to.x - from.x) * progress;
  const py = from.y + (to.y - from.y) * progress;
  const len = Math.hypot(to.x - from.x, to.y - from.y);
  const drawn = len * progress;
  // Tail fade — per-frame opacity is geometry, not a visual atom.
  const lineOpacity =
    progress < 0.95 ? 0.55 : 0.55 * (1 - (progress - 0.95) / 0.05);

  return (
    <g className="network-map-conn">
      <line
        x1={from.x} y1={from.y} x2={to.x} y2={to.y}
        className="network-map-conn-line"
        strokeDasharray={`${drawn} ${len}`}
        opacity={lineOpacity}
      />
      <circle cx={px} cy={py} r={8} className="network-map-conn-glow" />
      <circle cx={px} cy={py} r={3} className="network-map-conn-particle" />
    </g>
  );
}

// ── CityDot ──────────────────────────────────────────────────────
function CityDot({ city, hot }) {
  return (
    <g data-role="city" data-city-id={city.id}>
      {hot && (
        <circle
          cx={city.x} cy={city.y} r={4}
          className="network-map-city-ping"
        />
      )}
      <circle
        cx={city.x} cy={city.y} r={hot ? 3 : 2.2}
        className={hot ? "network-map-city network-map-city-hot" : "network-map-city"}
      />
    </g>
  );
}

// ── VaultNode ────────────────────────────────────────────────────
function VaultNode({ vault, flashing }) {
  return (
    <g data-role="vault">
      <circle
        cx={vault.x} cy={vault.y} r={flashing ? 32 : 20}
        className={flashing
          ? "network-map-vault-glow network-map-vault-glow-flash"
          : "network-map-vault-glow"}
      />
      <circle
        cx={vault.x} cy={vault.y} r={11}
        className="network-map-vault-ring"
        opacity={flashing ? 1 : 0.85}
      />
      <circle
        cx={vault.x} cy={vault.y} r={11}
        className="network-map-vault-breath"
      />
      <circle
        cx={vault.x} cy={vault.y} r={3.5}
        className="network-map-vault-core"
      />
    </g>
  );
}

// ── USNetworkMap ─────────────────────────────────────────────────
// Props:
//   cities   — optional city list (defaults to module-level CITIES)
//   vault    — optional vault position (defaults to module-level VAULT)
//   mask     — "bottom-fade" (default) | "none"
//   opacity  — 0..1, tunneled via --map-opacity (defaults to 0.85)
//
// Legacy props from the NET_THEMES era (`theme`, `editing`,
// `onAddCity`, `onRemoveCity`) are silently accepted and discarded
// so existing screens calling <USNetworkMap theme="dark"> keep
// working unchanged. Theme now flows from [data-theme] on any
// ancestor via CSS — no JS bridge. Drop the legacy props from the
// screens and they can come out of this destructure too.
function USNetworkMap({
  cities,
  vault = VAULT,
  mask = "bottom-fade",
  opacity = 0.85,
  // ── back-compat (no-op) — remove once call sites stop passing them
  theme: _legacyTheme,
  editing: _legacyEditing,
  onRemoveCity: _legacyOnRemoveCity,
  onAddCity: _legacyOnAddCity,
}) {
  const pool = cities || CITIES;
  const [conns, setConns] = React.useState([]);
  const [hotIds, setHotIds] = React.useState(new Set());
  const [vaultFlash, setVaultFlash] = React.useState(false);
  const idRef = React.useRef(0);
  // Latest-pool ref so the orchestrator closure reads the current
  // cities list when a parent mutates the array at runtime.
  const poolRef = React.useRef(pool);
  React.useEffect(() => { poolRef.current = pool; });

  const addConn = (c) => setConns((cs) => [...cs, { id: idRef.current++, ...c }]);
  const removeConn = (id) => setConns((cs) => cs.filter((c) => c.id !== id));
  const flashCity = (cityId, ms = 1500) => {
    setHotIds((s) => new Set([...s, cityId]));
    setTimeout(() => setHotIds((s) => { const n = new Set(s); n.delete(cityId); return n; }), ms);
  };
  const flashVault = () => {
    setVaultFlash(true);
    setTimeout(() => setVaultFlash(false), 600);
  };

  // Orchestrator — runs phases on a setTimeout schedule. Vault is
  // captured at mount; remount if the consumer needs to move it.
  React.useEffect(() => {
    let cancelled = false;
    const timers = [];
    const after = (ms, fn) => { const t = setTimeout(fn, ms); timers.push(t); };

    const cycle = () => {
      if (cancelled) return;
      const curPool = poolRef.current;
      if (!curPool || curPool.length === 0) { after(800, cycle); return; }

      // PHASE 1 — incoming deposit
      const source = sampleCities(curPool, 1)[0];
      flashCity(source.id, 1900);
      addConn({ from: source, to: { x: vault.x, y: vault.y } });

      after(1800, () => { if (!cancelled) flashVault(); });

      after(2100, () => {
        if (cancelled) return;
        const n = 3 + Math.floor(Math.random() * 3);
        const targets = sampleCities(poolRef.current, n, [source.id]);
        targets.forEach((t, i) => {
          flashCity(t.id, 2000 + i * 120);
          addConn({ from: { x: vault.x, y: vault.y }, to: t, delay: i * 110 });
        });
      });

      after(4800, cycle);
    };

    after(400, cycle);
    return () => { cancelled = true; timers.forEach(clearTimeout); };
  }, []);

  const className =
    mask === "none" ? "network-map" : `network-map network-map-mask-${mask}`;
  const rootStyle = { "--map-opacity": String(opacity) };

  return (
    <svg
      viewBox="0 0 1440 900"
      width="100%"
      height="100%"
      preserveAspectRatio="xMidYMid slice"
      aria-hidden="true"
      className={className}
      style={rootStyle}
    >
      {pool.map((c) => (
        <CityDot key={c.id} city={c} hot={hotIds.has(c.id)} />
      ))}
      <VaultNode vault={vault} flashing={vaultFlash} />
      {conns.map((c) => (
        <NetConnection
          key={c.id}
          from={c.from}
          to={c.to}
          delay={c.delay || 0}
          duration={1700}
          onDone={() => removeConn(c.id)}
        />
      ))}
    </svg>
  );
}

Object.assign(window, { USNetworkMap, NetworkMap: USNetworkMap, CITIES, VAULT });
