// Upstream peering map for gohegan.net — the real topology.
//
//   AS198657 gohegan.net
//     ├─ AS212895 ROUTE64 ── AS215638 Cilix    ── { THG → Zayo, RETN → {Sparkle, Telxius, Lumen}, Arelion }
//     │                  └── AS52025  PARADOX  ── { ZET.NET → Orange, Eranium → {Cogent, Lumen}, TATA }
//     ├─ AS207841 Inferno  ── AS61049  Exascale ── { GTT, NTT, Hurricane, PCCW, DTAG, AT&T }
//     ├─ AS11967  Hop179
//     └─ AS209735 Lagrange ── { Hurricane, Liberty }
//
// Packets pulse from origin out through the chain along the actual paths.

const VB_W = 216;
const VB_H = 112;
const BOX_W = 18;
const BOX_H = 6.2;
const T1_BOX_W = 17;
const T1_BOX_H = 5.6;

const NODES = {
  // Origin
  "gohegan.net": {
    x: 11,
    y: 58,
    asn: "198657",
    name: "gohegan.net",
    primary: true,
  },

  // Direct transits (gohegan.net's upstreams)
  route64: { x: 58, y: 18, asn: "212895", name: "ROUTE64" },
  inferno: { x: 58, y: 46, asn: "207841", name: "Inferno" },
  hop179: { x: 58, y: 72, asn: "11967", name: "Hop179" },
  lagrange: { x: 58, y: 98, asn: "209735", name: "Lagrange" },

  // Mid (upstream of upstream)
  cilix: { x: 108, y: 13, asn: "215638", name: "Cilix" },
  paradox: { x: 108, y: 33, asn: "52025", name: "PARADOX" },
  exascale: { x: 108, y: 83, asn: "61049", name: "Exascale" },

  // Sub (upstream feeders into Tier 1)
  thg: { x: 156, y: 6, asn: "13213", name: "THG" },
  retn: { x: 156, y: 19, asn: "9002", name: "RETN" },
  zetnet: { x: 156, y: 33, asn: "6204", name: "ZET.NET" },
  eranium: { x: 156, y: 46, asn: "35133", name: "Eranium" },

  // Tier 1 ISPs — vertical stack at col 4
  zayo: { x: 200, y: 6, asn: "6461", name: "Zayo", tier1: true },
  sparkle: { x: 200, y: 13, asn: "6762", name: "Sparkle", tier1: true },
  arelion: { x: 200, y: 20, asn: "1299", name: "Arelion", tier1: true },
  telxius: { x: 200, y: 27, asn: "12956", name: "Telxius", tier1: true },
  lumen: { x: 200, y: 34, asn: "3356", name: "Lumen", tier1: true },
  orange: { x: 200, y: 41, asn: "5511", name: "Orange", tier1: true },
  tata: { x: 200, y: 48, asn: "6453", name: "TATA", tier1: true },
  cogent: { x: 200, y: 55, asn: "174", name: "Cogent", tier1: true },
  gtt: { x: 200, y: 64, asn: "3257", name: "GTT", tier1: true },
  ntt: { x: 200, y: 71, asn: "2914", name: "NTT", tier1: true },
  hurricane: { x: 200, y: 78, asn: "6939", name: "Hurricane", tier1: true },
  pccw: { x: 200, y: 85, asn: "3491", name: "PCCW", tier1: true },
  dtag: { x: 200, y: 92, asn: "3320", name: "DTAG", tier1: true },
  att: { x: 200, y: 99, asn: "7018", name: "AT&T", tier1: true },
  liberty: { x: 200, y: 106, asn: "6830", name: "Liberty", tier1: true },
};

const EDGES = [
  ["gohegan.net", "route64"],
  ["gohegan.net", "inferno"],
  ["gohegan.net", "hop179"],
  ["gohegan.net", "lagrange"],
  ["route64", "cilix"],
  ["route64", "paradox"],
  ["inferno", "exascale"],
  ["lagrange", "hurricane"],
  ["lagrange", "liberty"],
  ["cilix", "thg"],
  ["cilix", "retn"],
  ["cilix", "arelion"],
  ["paradox", "zetnet"],
  ["paradox", "eranium"],
  ["paradox", "tata"],
  ["exascale", "gtt"],
  ["exascale", "ntt"],
  ["exascale", "hurricane"],
  ["exascale", "pccw"],
  ["exascale", "dtag"],
  ["exascale", "att"],
  ["thg", "zayo"],
  ["retn", "sparkle"],
  ["retn", "telxius"],
  ["retn", "lumen"],
  ["zetnet", "orange"],
  ["eranium", "cogent"],
  ["eranium", "lumen"],
];

// Full origin-to-tier-1 routes for packet animation.
const PATHS = [
  ["gohegan.net", "route64", "cilix", "thg", "zayo"],
  ["gohegan.net", "route64", "cilix", "retn", "sparkle"],
  ["gohegan.net", "route64", "cilix", "retn", "telxius"],
  ["gohegan.net", "route64", "cilix", "retn", "lumen"],
  ["gohegan.net", "route64", "cilix", "arelion"],
  ["gohegan.net", "route64", "paradox", "zetnet", "orange"],
  ["gohegan.net", "route64", "paradox", "eranium", "cogent"],
  ["gohegan.net", "route64", "paradox", "eranium", "lumen"],
  ["gohegan.net", "route64", "paradox", "tata"],
  ["gohegan.net", "inferno", "exascale", "gtt"],
  ["gohegan.net", "inferno", "exascale", "ntt"],
  ["gohegan.net", "inferno", "exascale", "hurricane"],
  ["gohegan.net", "inferno", "exascale", "pccw"],
  ["gohegan.net", "inferno", "exascale", "dtag"],
  ["gohegan.net", "inferno", "exascale", "att"],
  ["gohegan.net", "hop179"],
  ["gohegan.net", "lagrange", "hurricane"],
  ["gohegan.net", "lagrange", "liberty"],
];

// Edge endpoints: clip to the box edges (not the centers) so lines look right.
const edgeEndpoints = (a, b) => {
  const A = NODES[a], B = NODES[b];
  const aw = A.tier1 ? T1_BOX_W : BOX_W;
  const bw = B.tier1 ? T1_BOX_W : BOX_W;
  return {
    x1: A.x + aw / 2,
    y1: A.y,
    x2: B.x - bw / 2,
    y2: B.y,
  };
};

// Polyline length for a path.
const pathPolyline = (path) => {
  const pts = [];
  for (let i = 0; i < path.length - 1; i++) {
    const e = edgeEndpoints(path[i], path[i + 1]);
    if (i === 0) pts.push({ x: e.x1, y: e.y1 });
    pts.push({ x: e.x2, y: e.y2 });
  }
  let total = 0;
  const segs = [];
  for (let i = 0; i < pts.length - 1; i++) {
    const dx = pts[i + 1].x - pts[i].x;
    const dy = pts[i + 1].y - pts[i].y;
    const len = Math.hypot(dx, dy);
    segs.push({ from: pts[i], to: pts[i + 1], len });
    total += len;
  }
  return { pts, segs, total };
};

const PATH_GEOMETRY = PATHS.map(pathPolyline);

const positionAtT = (geom, t) => {
  const target = t * geom.total;
  let acc = 0;
  for (const s of geom.segs) {
    if (acc + s.len >= target) {
      const u = (target - acc) / s.len;
      return {
        x: s.from.x + (s.to.x - s.from.x) * u,
        y: s.from.y + (s.to.y - s.from.y) * u,
      };
    }
    acc += s.len;
  }
  const last = geom.segs[geom.segs.length - 1];
  return { x: last.to.x, y: last.to.y };
};

const NetworkGraph = ({ animate = true }) => {
  const [packets, setPackets] = React.useState(() =>
    // Pre-seed with packets at varied progress so the initial frame looks alive.
    Array.from({ length: 8 }, () => ({
      id: Math.random().toString(36).slice(2),
      pathIdx: Math.floor(Math.random() * PATHS.length),
      t: Math.random() * 0.95,
      duration: 2.4 + Math.random() * 1.6,
      tone: Math.random() < 0.22 ? 'accent' : 'fg',
    }))
  );
  const lastSpawnRef = React.useRef(0.4);
  const rafRef = React.useRef(0);

  React.useEffect(() => {
    if (!animate) return;
    let last = performance.now();
    const step = (now) => {
      const dt = Math.min(0.06, (now - last) / 1000);
      last = now;
      lastSpawnRef.current -= dt;

      setPackets(prev => {
        let next = prev
          .map(p => ({ ...p, t: p.t + dt / p.duration }))
          .filter(p => p.t < 1);
        while (lastSpawnRef.current <= 0) {
          lastSpawnRef.current += 0.18 + Math.random() * 0.32;
          if (next.length >= 18) break;
          next.push({
            id: Math.random().toString(36).slice(2),
            pathIdx: Math.floor(Math.random() * PATHS.length),
            t: 0,
            duration: 2.4 + Math.random() * 1.6,
            tone: Math.random() < 0.22 ? 'accent' : 'fg',
          });
        }
        return next;
      });

      rafRef.current = requestAnimationFrame(step);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => cancelAnimationFrame(rafRef.current);
  }, [animate]);

  return (
    <svg
      viewBox={`0 -6 ${VB_W} ${VB_H + 6}`}
      preserveAspectRatio="xMidYMid meet"
      width="100%"
      role="img"
      aria-label="gohegan.net upstream peering map: AS198657 connects via three direct transits — ROUTE64, Inferno, Lagrange — reaching fifteen Tier 1 ISPs via intermediate networks."
      style={{ display: "block" }}
    >
      <defs>
        <pattern
          id="ng-grid"
          width="4"
          height="4"
          patternUnits="userSpaceOnUse"
        >
          <circle cx="0" cy="0" r="0.25" fill="var(--border-subtle)" />
        </pattern>
      </defs>

      <rect x="0" y="-6" width={VB_W} height={VB_H + 6} fill="url(#ng-grid)" />

      {/* Tier 1 zone backdrop */}
      <g>
        <rect
          x={NODES.zayo.x - T1_BOX_W / 2 - 4}
          y={2}
          width={T1_BOX_W + 8}
          height={VB_H - 4}
          fill="var(--accent-tint)"
          stroke="var(--accent-edge)"
          strokeWidth="0.4"
          vectorEffect="non-scaling-stroke"
          rx="2"
        />
        <text
          x={NODES.zayo.x}
          y={-0.5}
          textAnchor="middle"
          fontFamily="var(--font-mono)"
          fontSize={2.1}
          fontWeight={500}
          fill="var(--accent)"
          style={{ letterSpacing: "0.08em", textTransform: "uppercase" }}
        >
          tier 1 · 15
        </text>
      </g>

      {/* Column labels */}
      {[
        { x: NODES["gohegan.net"].x, label: "origin" },
        { x: NODES["route64"].x, label: "transit" },
        { x: NODES["cilix"].x, label: "upstream" },
        { x: NODES["thg"].x, label: "upstream" },
      ].map((c) => (
        <text
          key={c.label + c.x}
          x={c.x}
          y={-0.5}
          textAnchor="middle"
          fontFamily="var(--font-mono)"
          fontSize={2.1}
          fontWeight={500}
          fill="var(--fg-3)"
          style={{ letterSpacing: "0.08em", textTransform: "uppercase" }}
        >
          {c.label}
        </text>
      ))}

      {/* Edges */}
      {EDGES.map(([a, b], i) => {
        const e = edgeEndpoints(a, b);
        return (
          <line
            key={i}
            x1={e.x1}
            y1={e.y1}
            x2={e.x2}
            y2={e.y2}
            stroke="var(--border-strong)"
            strokeWidth="0.8"
            strokeLinecap="round"
            vectorEffect="non-scaling-stroke"
            opacity="0.65"
          />
        );
      })}

      {/* Packets */}
      {packets.map((p) => {
        const geom = PATH_GEOMETRY[p.pathIdx];
        const pos = positionAtT(geom, p.t);
        const colour = p.tone === "accent" ? "var(--accent)" : "var(--fg-0)";
        // Trail: position slightly behind on the path
        const tt = Math.max(0, p.t - 0.012);
        const trail = positionAtT(geom, tt);
        return (
          <g key={p.id}>
            <circle
              cx={trail.x}
              cy={trail.y}
              r={0.55}
              fill={colour}
              opacity={0.3}
            />
            <circle cx={pos.x} cy={pos.y} r={1.1} fill={colour} />
          </g>
        );
      })}

      {/* Nodes */}
      {Object.entries(NODES).map(([id, n]) => (
        <NodeBox key={id} node={n} />
      ))}
    </svg>
  );
};

const NodeBox = ({ node: n }) => {
  const w = n.tier1 ? T1_BOX_W : BOX_W;
  const h = n.tier1 ? T1_BOX_H : BOX_H;
  const fill = n.primary ? 'var(--accent)' : 'var(--bg-0)';
  const stroke = n.primary ? 'var(--accent)' : (n.tier1 ? 'var(--accent)' : 'var(--fg-0)');
  const asnColor = n.primary ? 'var(--accent-fg)' : 'var(--fg-0)';
  const nameColor = n.primary
    ? 'rgba(255,255,255,0.78)'
    : (n.tier1 ? 'var(--fg-1)' : 'var(--fg-2)');

  return (
    <g>
      <rect
        x={n.x - w / 2} y={n.y - h / 2}
        width={w} height={h}
        rx="0.8"
        fill={fill}
        stroke={stroke}
        strokeWidth={n.primary || n.tier1 ? 1.1 : 0.9}
        vectorEffect="non-scaling-stroke"
      />
      <text
        x={n.x}
        y={n.y - 0.4}
        textAnchor="middle"
        dominantBaseline="middle"
        fontFamily="var(--font-mono)"
        fontSize={1.95}
        fontWeight={600}
        fill={asnColor}
        style={{ letterSpacing: '-0.02em' }}
      >
        AS{n.asn}
      </text>
      <text
        x={n.x}
        y={n.y + 1.7}
        textAnchor="middle"
        dominantBaseline="middle"
        fontFamily="var(--font-mono)"
        fontSize={1.4}
        fontWeight={400}
        fill={nameColor}
        style={{ letterSpacing: '0' }}
      >
        {n.name}
      </text>
    </g>
  );
};

Object.assign(window, { NetworkGraph });
