// AttackGraphPane — right pane. Hand-rolled tiny force simulation (no deps).
// 8 nodes, animated edges when chain-analysis runs, subtle drift when idle.

function AttackGraphPane({ graphState, counters, elapsedMs, loopMs }) {
  const { React } = window;
  const { useRef, useEffect, useMemo, useState } = React;

  const W = 520, H = 560;

  // Canonical node positions (seeded layout; drift happens on top)
  const baseNodes = useMemo(() => ([
    { id: 'api.gateway',  label: 'api.gateway',  kind: 'edge', x: 260, y:  80 },
    { id: 'auth.svc',     label: 'auth.svc',     kind: 'svc',  x: 100, y: 190 },
    { id: 'payments.api', label: 'payments.api', kind: 'svc',  x: 420, y: 200 },
    { id: 'users.api',    label: 'users.api',    kind: 'svc',  x: 180, y: 330 },
    { id: 'orders.api',   label: 'orders.api',   kind: 'svc',  x: 350, y: 340 },
    { id: 'db.postgres',  label: 'db.postgres',  kind: 'db',   x: 430, y: 470 },
    { id: 'redis.cache',  label: 'redis.cache',  kind: 'db',   x: 260, y: 490 },
    { id: 's3.receipts',  label: 's3.receipts',  kind: 'db',   x:  90, y: 460 },
  ]), []);

  // Static edges (infrastructure topology, always visible but faint)
  const topology = useMemo(() => ([
    ['api.gateway','auth.svc'], ['api.gateway','payments.api'],
    ['api.gateway','users.api'],['api.gateway','orders.api'],
    ['auth.svc','redis.cache'], ['users.api','db.postgres'],
    ['payments.api','db.postgres'], ['orders.api','db.postgres'],
    ['users.api','s3.receipts'],
  ]), []);

  // Per-frame drift (deterministic from elapsed so SSR-safe)
  const drift = (id, i) => {
    const t = elapsedMs / 1000;
    const seed = i * 1.37;
    return {
      dx: Math.sin(t * 0.6 + seed) * 3.2,
      dy: Math.cos(t * 0.5 + seed * 1.3) * 3.2,
    };
  };

  const nodes = baseNodes.map((n, i) => {
    const d = drift(n.id, i);
    return { ...n, x: n.x + d.dx, y: n.y + d.dy, state: graphState.nodes[n.id] || 'idle' };
  });
  const byId = Object.fromEntries(nodes.map(n => [n.id, n]));

  const stateColor = {
    idle:      { fill: '#E7E1D4', stroke: '#B8AF9A', text: '#5A5446', halo: 'transparent' },
    scanning:  { fill: '#F0E8D3', stroke: '#D4915D', text: '#3A2E1F', halo: 'rgba(212,145,93,0.28)' },
    finding:   { fill: '#F9DCD6', stroke: '#E8847C', text: '#4A1C19', halo: 'rgba(232,132,124,0.32)' },
    exploited: { fill: '#FFCBC4', stroke: '#EF4444', text: '#3C0A0A', halo: 'rgba(239,68,68,0.45)' },
  };

  const nodeShape = (n) => {
    const c = stateColor[n.state];
    if (n.kind === 'db') {
      // cylinder-ish
      return (
        <g>
          <ellipse cx={n.x} cy={n.y - 8} rx={44} ry={6} fill={c.stroke} opacity="0.35" />
          <rect x={n.x - 44} y={n.y - 8} width={88} height={26} fill={c.fill} stroke={c.stroke} strokeWidth="1.4" />
          <ellipse cx={n.x} cy={n.y + 18} rx={44} ry={6} fill={c.fill} stroke={c.stroke} strokeWidth="1.4" />
        </g>
      );
    }
    if (n.kind === 'edge') {
      return <polygon
        points={`${n.x-48},${n.y} ${n.x-36},${n.y-16} ${n.x+36},${n.y-16} ${n.x+48},${n.y} ${n.x+36},${n.y+16} ${n.x-36},${n.y+16}`}
        fill={c.fill} stroke={c.stroke} strokeWidth="1.4" />;
    }
    return <rect x={n.x - 48} y={n.y - 16} width={96} height={32} rx={3}
      fill={c.fill} stroke={c.stroke} strokeWidth="1.4" />;
  };

  // Pulse ring for non-idle nodes
  const pulseR = (n) => {
    const t = elapsedMs / 1000;
    const phase = (t * 1.4 + n.id.length) % 1;
    return 24 + phase * 24;
  };
  const pulseO = (phase) => Math.max(0, 0.45 * (1 - phase));

  // Live edges (chain analysis) — only show edges whose tAdded has passed;
  // dash-animate the most recent.
  const liveEdges = graphState.edges;

  return (
    <div className="graph-root">
      <div className="graph-header">
        <div className="graph-title">attack surface</div>
        <div className="graph-meta">
          <span className="graph-counter">
            <span className="graph-counter-num">{counters.findings}</span>
            <span className="graph-counter-lbl">findings</span>
          </span>
          <span className="graph-counter graph-counter--exploited">
            <span className="graph-counter-num">{counters.exploited}</span>
            <span className="graph-counter-lbl">exploited</span>
          </span>
          <span className="graph-counter graph-counter--chain">
            <span className="graph-counter-num">{liveEdges.length ? 1 : 0}</span>
            <span className="graph-counter-lbl">critical chain</span>
          </span>
        </div>
      </div>

      <svg viewBox={`0 0 ${W} ${H}`} className="graph-svg" preserveAspectRatio="xMidYMid meet">
        {/* grid */}
        <defs>
          <pattern id="grid" width="24" height="24" patternUnits="userSpaceOnUse">
            <path d="M 24 0 L 0 0 0 24" fill="none" stroke="#D9D2BF" strokeWidth="0.5" opacity="0.5" />
          </pattern>
          <filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
            <feGaussianBlur stdDeviation="4" />
          </filter>
        </defs>
        <rect width={W} height={H} fill="url(#grid)" />

        {/* topology edges (faint) */}
        {topology.map(([a,b],i) => {
          const na = byId[a], nb = byId[b];
          if (!na || !nb) return null;
          return <line key={`t-${i}`} x1={na.x} y1={na.y} x2={nb.x} y2={nb.y}
            stroke="#B8AF9A" strokeWidth="1" opacity="0.55" />;
        })}

        {/* live chain edges */}
        {liveEdges.map((e,i) => {
          const na = byId[e.from], nb = byId[e.to];
          if (!na || !nb) return null;
          const isLatest = i === liveEdges.length - 1;
          const dashOffset = isLatest ? -((elapsedMs / 24) % 40) : 0;
          const mx = (na.x + nb.x) / 2;
          const my = (na.y + nb.y) / 2 - 8;
          return (
            <g key={`le-${i}`}>
              <line x1={na.x} y1={na.y} x2={nb.x} y2={nb.y}
                stroke="#EF4444" strokeWidth="2.2"
                strokeDasharray="7 6" strokeDashoffset={dashOffset}
                opacity="0.95" />
              {e.label && (
                <g>
                  <rect x={mx - 42} y={my - 11} width={84} height={18} rx={2}
                        fill="#0B0F0E" />
                  <text x={mx} y={my + 2} textAnchor="middle"
                        fontFamily="'JetBrains Mono', ui-monospace, monospace"
                        fontSize="10" fill="#E8847C" letterSpacing="0.5">
                    {e.label}
                  </text>
                </g>
              )}
            </g>
          );
        })}

        {/* node halos */}
        {nodes.map((n,i) => {
          if (n.state === 'idle') return null;
          const r = pulseR(n);
          const phase = ((elapsedMs/1000) * 1.4 + n.id.length) % 1;
          const c = stateColor[n.state];
          return (
            <circle key={`h-${i}`} cx={n.x} cy={n.y} r={r}
              fill="none" stroke={c.stroke} strokeWidth="1.5"
              opacity={pulseO(phase)} />
          );
        })}

        {/* nodes */}
        {nodes.map((n,i) => {
          const c = stateColor[n.state];
          return (
            <g key={n.id} className={`node node--${n.state}`}>
              {nodeShape(n)}
              <text x={n.x} y={n.y + 5} textAnchor="middle"
                    fontFamily="'JetBrains Mono', ui-monospace, monospace"
                    fontSize="11" fill={c.text} letterSpacing="0.3">
                {n.label}
              </text>
            </g>
          );
        })}
      </svg>

      <div className="graph-legend">
        <span className="legend-dot legend-dot--idle" /> idle
        <span className="legend-dot legend-dot--scanning" /> scanning
        <span className="legend-dot legend-dot--finding" /> finding
        <span className="legend-dot legend-dot--exploited" /> exploited
      </div>
    </div>
  );
}

window.AttackGraphPane = AttackGraphPane;
