/* Krytz · Live data-flow DAG visualization
 *
 * 8-node directed graph showing fragments traveling from input sources
 * (Voice / Text / Files) through Capture → Extract → State → Rank →
 * Decide, with a Learn feedback loop back to Rank.
 *
 * Particles are continuously spawned on a tick and animated along
 * pre-computed bezier edges via SMIL <animateMotion>. Nodes pulse when
 * a particle arrives. Self-cleaning — no leak.
 */

function DataFlowDAG() {
  const svgRef = React.useRef(null);
  const [stats, setStats] = React.useState({ captured: 0, extracted: 0, ranked: 0 });

  // Node layout — DAG, left-to-right with feedback below
  const W = 1100, H = 460;
  const nodes = React.useMemo(() => ([
    // sources
    { id: 'voice', label: 'Voice',    sub: 'realtime',     x: 70,  y: 80,  kind: 'src', icon: 'mic' },
    { id: 'text',  label: 'Text',     sub: 'typed · pasted', x: 70,  y: 200, kind: 'src', icon: 'text' },
    { id: 'files', label: 'Files',    sub: 'pdf · md · img',  x: 70,  y: 320, kind: 'src', icon: 'file' },
    // workflow
    { id: 'cap',   label: 'Capture',  sub: 'buffered',     x: 290, y: 200, kind: 'pipe', icon: 'plus' },
    { id: 'ex',    label: 'Extract',  sub: 'NLU · entities', x: 460, y: 200, kind: 'pipe', icon: 'wand' },
    { id: 'st',    label: 'State',    sub: 'graph',          x: 630, y: 200, kind: 'pipe', icon: 'graph' },
    { id: 'rk',    label: 'Rank',     sub: '7 signals',      x: 800, y: 200, kind: 'pipe', icon: 'bars' },
    { id: 'dec',   label: 'Decide',   sub: 'Primary · Next', x: 990, y: 200, kind: 'sink', icon: 'star' },
    // feedback
    { id: 'lr',    label: 'Learn',    sub: 'tunes weights',  x: 800, y: 380, kind: 'fb',   icon: 'loop' },
  ]), []);

  // Edges (from → to) with a control-point bias for curvature
  const edges = React.useMemo(() => ([
    { from: 'voice', to: 'cap',  curve: 0   },
    { from: 'text',  to: 'cap',  curve: 0   },
    { from: 'files', to: 'cap',  curve: 0   },
    { from: 'cap',   to: 'ex',   curve: 0   },
    { from: 'ex',    to: 'st',   curve: 0   },
    { from: 'st',    to: 'rk',   curve: 0   },
    { from: 'rk',    to: 'dec',  curve: 0   },
    { from: 'dec',   to: 'lr',   curve: 30, dashed: true },
    { from: 'lr',    to: 'rk',   curve: -30, dashed: true },
  ]), []);

  const byId = React.useMemo(() => Object.fromEntries(nodes.map(n => [n.id, n])), [nodes]);

  // Build path "d" for each edge — quadratic bezier through midpoint w/ bias
  function pathFor(e) {
    const a = byId[e.from], b = byId[e.to];
    const mx = (a.x + b.x) / 2;
    const my = (a.y + b.y) / 2 + e.curve;
    return `M ${a.x} ${a.y} Q ${mx} ${my} ${b.x} ${b.y}`;
  }

  // Build edge index for tracking particles per stage
  const sourceEdges = React.useMemo(() => edges.filter(e => byId[e.from].kind === 'src'), [edges, byId]);
  const pipeEdges   = React.useMemo(() => {
    // returns a sequence per starting node id → next edge id
    const map = {};
    edges.forEach(e => { if (!map[e.from]) map[e.from] = e; });
    return map;
  }, [edges]);

  // ── Particle spawner ────────────────────────────────────────────────
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTick(t => t + 1), 280);
    return () => clearInterval(id);
  }, []);

  // Pulse state for each node
  const [pulses, setPulses] = React.useState({});
  function pulseNode(id) {
    setPulses(p => ({ ...p, [id]: Date.now() }));
    setTimeout(() => {
      setPulses(p => {
        const np = { ...p };
        delete np[id];
        return np;
      });
    }, 420);
  }

  // Spawn a particle: it traverses the chain cap → ex → st → rk → dec
  const [particles, setParticles] = React.useState([]);
  React.useEffect(() => {
    // On each tick, emit 1–2 particles from a random source
    const startEdge = sourceEdges[tick % sourceEdges.length];
    if (!startEdge) return;
    const id = `p-${tick}-${Math.random().toString(36).slice(2, 7)}`;
    const colorPool = ['#1B2A4A', '#C8A45A', '#E58B2A', '#1B7A52'];
    const color = colorPool[Math.floor(Math.random() * colorPool.length)];
    const stages = [
      sourceEdges.find(e => e.from === startEdge.from),
      pipeEdges['cap'],
      pipeEdges['ex'],
      pipeEdges['st'],
      pipeEdges['rk'],
    ].filter(Boolean);

    setParticles(prev => [...prev, { id, stages, color, startedAt: Date.now() }]);
    // remove after the whole chain finishes (~ 5 × 800ms + buffer)
    setTimeout(() => {
      setParticles(prev => prev.filter(p => p.id !== id));
    }, stages.length * 820 + 400);

    // Increment stats as it passes nodes
    stages.forEach((e, i) => {
      setTimeout(() => {
        const toNode = byId[e.to];
        pulseNode(toNode.id);
        if (toNode.id === 'cap') setStats(s => ({ ...s, captured: s.captured + 1 }));
        if (toNode.id === 'ex')  setStats(s => ({ ...s, extracted: s.extracted + 1 }));
        if (toNode.id === 'rk')  setStats(s => ({ ...s, ranked: s.ranked + 1 }));
      }, (i + 1) * 800 - 100);
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [tick]);

  // Render
  return (
    <div className="dfdag">
      <div className="dfdag-header">
        <div className="dfdag-title">
          <span className="dfdag-live"><span className="dfdag-live-dot"></span>LIVE</span>
          <span>Workflow · 8 nodes</span>
        </div>
        <div className="dfdag-stats">
          <div className="dfdag-stat"><span className="v">{stats.captured}</span><span className="l">captured</span></div>
          <div className="dfdag-stat"><span className="v">{stats.extracted}</span><span className="l">extracted</span></div>
          <div className="dfdag-stat"><span className="v">{stats.ranked}</span><span className="l">ranked</span></div>
        </div>
      </div>

      <div className="dfdag-svg-wrap">
        <svg ref={svgRef} viewBox={`0 0 ${W} ${H}`} className="dfdag-svg" preserveAspectRatio="xMidYMid meet">
          <defs>
            {edges.map((e, i) => (
              <path key={i} id={`dfe-${i}`} d={pathFor(e)} fill="none" />
            ))}
            <marker id="dfdag-arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto" markerUnits="strokeWidth">
              <path d="M0 0 L9 5 L0 10 Z" fill="#cdc4f0" />
            </marker>
            <marker id="dfdag-arrow-fb" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto" markerUnits="strokeWidth">
              <path d="M0 0 L9 5 L0 10 Z" fill="#cdc4f0" />
            </marker>
          </defs>

          {/* Edges */}
          {edges.map((e, i) => (
            <path
              key={`edge-${i}`}
              d={pathFor(e)}
              fill="none"
              stroke={e.dashed ? '#cdc4f0' : '#cdc4f0'}
              strokeWidth="1.4"
              strokeDasharray={e.dashed ? '5 5' : '0'}
              markerEnd={e.dashed ? 'url(#dfdag-arrow-fb)' : 'url(#dfdag-arrow)'}
              opacity="0.7"
            />
          ))}

          {/* Particles */}
          {particles.map(p => (
            <ParticleTrain key={p.id} p={p} edges={edges} />
          ))}

          {/* Nodes (on top of edges) */}
          {nodes.map(n => (
            <NodeG key={n.id} n={n} pulse={!!pulses[n.id]} />
          ))}
        </svg>
      </div>

      <div className="dfdag-legend">
        <span><span className="lg-d s"></span>Source nodes</span>
        <span><span className="lg-d p"></span>Workflow stages</span>
        <span><span className="lg-d f"></span>Feedback loop</span>
      </div>
    </div>
  );
}

function ParticleTrain({ p, edges }) {
  // Find indices of each stage edge so we can target via SMIL mpath
  const edgeIndex = (edge) => edges.indexOf(edge);
  const STAGE_MS = 800;

  return (
    <g>
      {p.stages.map((stage, i) => {
        const idx = edgeIndex(stage);
        if (idx < 0) return null;
        const begin = `${i * STAGE_MS}ms`;
        return (
          <circle key={i} r="5" fill={p.color} opacity="0">
            <animate attributeName="opacity"
                     values="0;1;1;0"
                     keyTimes="0;0.05;0.95;1"
                     dur={`${STAGE_MS}ms`}
                     begin={begin}
                     fill="freeze" />
            <animateMotion dur={`${STAGE_MS}ms`} begin={begin} fill="freeze">
              <mpath xlinkHref={`#dfe-${idx}`} />
            </animateMotion>
            {/* trailing glow */}
            <animate attributeName="r"
                     values="6;4;6"
                     keyTimes="0;0.5;1"
                     dur={`${STAGE_MS}ms`}
                     begin={begin}
                     fill="freeze" />
          </circle>
        );
      })}
    </g>
  );
}

function NodeG({ n, pulse }) {
  const isSrc  = n.kind === 'src';
  const isFb   = n.kind === 'fb';
  const isSink = n.kind === 'sink';

  const W = isSrc ? 140 : 152;
  const H = 56;
  const x = n.x - W / 2;
  const y = n.y - H / 2;

  const fill = isSrc ? '#FFFFFF' : (isFb ? '#FFFFFF' : '#FFFFFF');
  const stroke = isSink ? '#1B2A4A' : (isFb ? '#cdc4f0' : '#0E1320');
  const strokeWidth = isSink ? 1.8 : 1.4;

  return (
    <g className={`dfdag-node ${pulse ? 'is-pulse' : ''}`}>
      {/* pulse halo */}
      {pulse && (
        <rect x={x - 6} y={y - 6} width={W + 12} height={H + 12} rx="14"
              fill="none" stroke="#1B2A4A" strokeWidth="2" opacity="0.35"
              className="dfdag-halo" />
      )}
      <rect x={x} y={y} width={W} height={H} rx="10"
            fill={fill} stroke={stroke} strokeWidth={strokeWidth}
            strokeDasharray={isFb ? '4 4' : '0'} />
      <NodeIcon kind={n.icon} cx={x + 24} cy={n.y} />
      <text x={x + 44} y={n.y - 2} fontFamily="DM Sans, sans-serif" fontSize="14" fontWeight="600" fill="#0E1320">
        {n.label}
      </text>
      <text x={x + 44} y={n.y + 14} fontFamily="DM Mono, monospace" fontSize="9.5" fill="#828B87" letterSpacing="0.04em">
        {n.sub}
      </text>
    </g>
  );
}

function NodeIcon({ kind, cx, cy }) {
  const color = '#1B2A4A';
  const s = 8; // half-size
  switch (kind) {
    case 'mic':
      return (
        <g transform={`translate(${cx} ${cy})`}>
          <rect x="-3" y="-7" width="6" height="9" rx="3" fill={color}/>
          <path d="M-5 1 a5 5 0 0 0 10 0" stroke={color} strokeWidth="1.4" fill="none" strokeLinecap="round"/>
          <line x1="0" y1="3" x2="0" y2="7" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
        </g>
      );
    case 'text':
      return (
        <g transform={`translate(${cx - 7} ${cy - 7})`}>
          <line x1="0" y1="3" x2="14" y2="3" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
          <line x1="0" y1="7" x2="11" y2="7" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
          <line x1="0" y1="11" x2="13" y2="11" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
        </g>
      );
    case 'file':
      return (
        <g transform={`translate(${cx - 7} ${cy - 9})`}>
          <path d="M2 0 h8 l4 4 v12 a2 2 0 0 1 -2 2 H2 a2 2 0 0 1 -2 -2 V2 a2 2 0 0 1 2 -2 z"
                fill="none" stroke={color} strokeWidth="1.4" strokeLinejoin="round"/>
          <path d="M10 0 v4 h4" fill="none" stroke={color} strokeWidth="1.4"/>
        </g>
      );
    case 'plus':
      return (
        <g transform={`translate(${cx} ${cy})`}>
          <circle r={s} fill="none" stroke={color} strokeWidth="1.4"/>
          <line x1={-s/2} y1="0" x2={s/2} y2="0" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
          <line x1="0" y1={-s/2} x2="0" y2={s/2} stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
        </g>
      );
    case 'wand':
      return (
        <g transform={`translate(${cx - 7} ${cy - 7})`}>
          <line x1="0" y1="14" x2="14" y2="0" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
          <circle cx="14" cy="0" r="2" fill={color}/>
          <line x1="11" y1="4" x2="13" y2="2" stroke="#fff" strokeWidth="1"/>
        </g>
      );
    case 'graph':
      return (
        <g transform={`translate(${cx} ${cy})`}>
          <circle cx="-5" cy="-4" r="2" fill={color}/>
          <circle cx="5"  cy="-4" r="2" fill={color}/>
          <circle cx="0"  cy="5"  r="2" fill={color}/>
          <line x1="-5" y1="-4" x2="0" y2="5" stroke={color} strokeWidth="1.1"/>
          <line x1="5"  y1="-4" x2="0" y2="5" stroke={color} strokeWidth="1.1"/>
          <line x1="-5" y1="-4" x2="5" y2="-4" stroke={color} strokeWidth="1.1"/>
        </g>
      );
    case 'bars':
      return (
        <g transform={`translate(${cx - 6} ${cy + 6})`}>
          <rect x="0" y="-4"  width="3" height="4"  fill={color}/>
          <rect x="5" y="-8"  width="3" height="8"  fill={color}/>
          <rect x="10" y="-12" width="3" height="12" fill={color}/>
        </g>
      );
    case 'star':
      return (
        <g transform={`translate(${cx} ${cy})`}>
          <path d="M0 -8 L2 -2 L8 -1 L3 3 L5 9 L0 6 L-5 9 L-3 3 L-8 -1 L-2 -2 Z" fill={color}/>
        </g>
      );
    case 'loop':
      return (
        <g transform={`translate(${cx} ${cy})`}>
          <path d="M -6 0 a 6 6 0 1 0 6 -6" fill="none" stroke={color} strokeWidth="1.4" strokeLinecap="round"/>
          <path d="M -1 -7 l 3 1 -1 3" fill="none" stroke={color} strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
        </g>
      );
    default: return null;
  }
}

window.DataFlowDAG = DataFlowDAG;
