// BranchingPipeline.jsx — Workflow DAG with fan-out/fan-in + approval gate.
// Sequence: Research → Plan → [Approve plan?] → Execute Plan 1 → (Plan 2 ‖ Plan 3) → Review → Open PR
// Click any node to expand a detail card with artifacts + a View Details button.
// The Approval node is a human-input gate — its detail card shows Approve / Request changes / Reject.

const BP_NODES = [
  { id: 'research',  label: 'Research',         col: 0, row: 0, kind: 'main',     duration: '4m 12s' },
  { id: 'plan',      label: 'Plan',             col: 1, row: 0, kind: 'main',     duration: '8m 47s' },
  { id: 'approval',  label: 'Approve Plan?',    col: 2, row: 0, kind: 'approval', duration: '~2m wait' },
  { id: 'exec1',     label: 'Execute Plan 1',   col: 3, row: 0, kind: 'main',     duration: '19m 04s' },
  { id: 'exec2',     label: 'Execute Plan 2',   col: 4, row: -1, kind: 'parallel', duration: '21m 33s' },
  { id: 'exec3',     label: 'Execute Plan 3',   col: 4, row:  1, kind: 'parallel', duration: '17m 58s' },
  { id: 'review',    label: 'Review',           col: 5, row: 0, kind: 'main',     duration: '3m 21s' },
  { id: 'pr',        label: 'Open PR',          col: 6, row: 0, kind: 'main',     duration: '0m 41s' },
];

const BP_EDGES = [
  ['research', 'plan'],
  ['plan',     'approval'],
  ['approval', 'exec1'],
  ['exec1',    'exec2'],
  ['exec1',    'exec3'],
  ['exec2',    'review'],
  ['exec3',    'review'],
  ['review',   'pr'],
];

// Phases — note approval *blocks* progress (longer pause + amber state)
const BP_PHASES = [
  { live: ['research'], blocked: false },
  { live: ['plan'],     blocked: false },
  { live: ['approval'], blocked: true },   // waiting on human review
  { live: ['approval'], blocked: true },   // ↳ hold
  { live: ['exec1'],    blocked: false },
  { live: ['exec2', 'exec3'], blocked: false },
  { live: ['exec2', 'exec3'], blocked: false },
  { live: ['review'],   blocked: false },
  { live: ['pr'],       blocked: false },
];

const BP_ARTIFACTS = {
  research: {
    summary: 'Indexed 47 files across 3 packages. 12 dependencies traced. 4 prior commits referenced.',
    files: [
      { name: 'context-graph.json',       size: '184 KB', kind: 'graph' },
      { name: 'reference-snippets.md',    size: '24 KB',  kind: 'doc' },
      { name: 'dependency-trace.log',     size: '8.2 KB', kind: 'log' },
    ],
  },
  plan: {
    summary: 'Decomposed into 3 plans by failure-domain isolation. Each plan is independently mergeable.',
    files: [
      { name: 'plan-001-oauth-config.md',  size: '6.1 KB', kind: 'doc' },
      { name: 'plan-002-token-refresh.md', size: '4.8 KB', kind: 'doc' },
      { name: 'plan-003-error-paths.md',   size: '3.4 KB', kind: 'doc' },
      { name: 'acceptance-criteria.yaml',  size: '1.9 KB', kind: 'yaml' },
    ],
  },
  approval: {
    kind: 'gate',
    summary: 'Plan ready for review. Requesting approval before execution begins.',
    requestedBy: 'sourceweaver-bot',
    requestedAt: '2 min ago',
    files: [
      { name: 'plan-001-oauth-config.md',  size: '6.1 KB', kind: 'doc' },
      { name: 'plan-002-token-refresh.md', size: '4.8 KB', kind: 'doc' },
      { name: 'plan-003-error-paths.md',   size: '3.4 KB', kind: 'doc' },
    ],
  },
  exec1: {
    summary: 'Wrote OAuth config primitives + provider registry. 6 files touched, 184 lines added.',
    files: [
      { name: 'src/auth/oauth/config.ts',  size: '+92 lines',  kind: 'code' },
      { name: 'src/auth/oauth/registry.ts', size: '+58 lines', kind: 'code' },
      { name: 'tests/oauth.config.spec.ts', size: '+34 lines', kind: 'test' },
    ],
  },
  exec2: {
    summary: 'Built token refresh + retry path. Diff isolated from Plan 1 by interface boundary.',
    files: [
      { name: 'src/auth/oauth/refresh.ts', size: '+71 lines',  kind: 'code' },
      { name: 'tests/oauth.refresh.spec.ts', size: '+42 lines', kind: 'test' },
    ],
  },
  exec3: {
    summary: 'Error-path handling and structured logs. Fixtures match Plan 1 contract.',
    files: [
      { name: 'src/auth/oauth/errors.ts',  size: '+38 lines',  kind: 'code' },
      { name: 'src/auth/oauth/logging.ts', size: '+22 lines',  kind: 'code' },
      { name: 'tests/oauth.errors.spec.ts', size: '+19 lines', kind: 'test' },
    ],
  },
  review: {
    summary: '8 of 8 acceptance checks. Type-check clean. Lint 0 warnings. Coverage +2.4%.',
    files: [
      { name: 'review-report.md',          size: '11 KB',  kind: 'doc' },
      { name: 'coverage-delta.json',       size: '4.2 KB', kind: 'graph' },
    ],
  },
  pr: {
    summary: 'Pull request opened against main. Awaiting human review.',
    files: [
      { name: 'PR #1142 description',     size: '—',      kind: 'doc' },
      { name: 'CHANGELOG.md',              size: '+1 line', kind: 'doc' },
    ],
  },
};

function BranchingPipeline() {
  const [phase, setPhase] = React.useState(0);
  const [tick, setTick] = React.useState(0);
  // Default-open the approval gate so users see the most distinctive node first
  const [openId, setOpenId] = React.useState('approval');
  // Per-node animated elapsed seconds, keyed by node id.
  const [elapsed, setElapsed] = React.useState({});

  React.useEffect(() => {
    const id = setInterval(() => {
      setPhase((p) => (p + 1) % BP_PHASES.length);
      setTick((t) => t + 1);
    }, 850);
    return () => clearInterval(id);
  }, []);

  // Parse "8m 47s", "0m 41s", "~2m wait" → seconds.
  const parseDur = (s) => {
    if (!s) return 0;
    const m = s.match(/(\d+)\s*m/);
    const sec = s.match(/(\d+)\s*s/);
    return (m ? +m[1] * 60 : 0) + (sec ? +sec[1] : 0);
  };
  const fmtDur = (totalSec) => {
    const m = Math.floor(totalSec / 60);
    const s = Math.floor(totalSec % 60);
    return `${m}m ${String(s).padStart(2, '0')}s`;
  };

  // Animate elapsed counter for any live, non-blocked, non-gate node.
  React.useEffect(() => {
    const currentPhase = BP_PHASES[phase];
    if (currentPhase.blocked) return;
    const liveNodes = currentPhase.live
      .map((id) => BP_NODES.find((n) => n.id === id))
      .filter((n) => n && n.kind !== 'approval');
    if (liveNodes.length === 0) return;
    // Each phase is 850ms; animate elapsed from 0 → target across ~700ms (16fps).
    const FRAME_MS = 40;
    const FRAMES = 18;
    let frame = 0;
    setElapsed((e) => {
      const next = { ...e };
      liveNodes.forEach((n) => { next[n.id] = 0; });
      return next;
    });
    const id = setInterval(() => {
      frame++;
      const t = Math.min(1, frame / FRAMES);
      // ease-out-cubic for a snappy fast-then-settle feel
      const eased = 1 - Math.pow(1 - t, 3);
      setElapsed((e) => {
        const next = { ...e };
        liveNodes.forEach((n) => {
          next[n.id] = Math.round(parseDur(n.duration) * eased);
        });
        return next;
      });
      if (frame >= FRAMES) clearInterval(id);
    }, FRAME_MS);
    return () => clearInterval(id);
  }, [phase]);

  const COLS = 7;
  const W = 720;
  const H = 180;
  const colX = (c) => 56 + c * ((W - 112) / (COLS - 1));
  const rowY = (r) => H / 2 + r * 50;

  const nodeById = Object.fromEntries(BP_NODES.map((n) => [n.id, n]));
  const currentPhase = BP_PHASES[phase];
  const liveSet = new Set(currentPhase.live);
  const completedSet = new Set();
  for (let i = 0; i < phase; i++) BP_PHASES[i].live.forEach((id) => completedSet.add(id));
  const isBlocked = currentPhase.blocked;

  const isEdgeLive = (a, b) => liveSet.has(b) && !isBlocked;
  const isEdgeDone = (a, b) => completedSet.has(b);

  const stageStatus = (id) => {
    if (liveSet.has(id)) return isBlocked ? 'blocked' : 'live';
    if (completedSet.has(id)) return 'done';
    return 'pending';
  };

  // Display duration: animate up from 0 while live, settle to target when done.
  const nodeDuration = (n) => {
    const st = stageStatus(n.id);
    if (n.kind === 'approval') {
      if (st === 'blocked') return 'awaiting…';
      if (st === 'done') return n.duration;
      return n.duration;
    }
    if (st === 'live') {
      const e = elapsed[n.id] ?? 0;
      return fmtDur(e);
    }
    if (st === 'done') return n.duration;
    return n.duration;
  };

  const activeLabels = currentPhase.live.map((id) => nodeById[id].label).join(' · ');
  const openNode = openId ? nodeById[openId] : null;
  const openArtifacts = openId ? BP_ARTIFACTS[openId] : null;
  const openStatus = openId ? stageStatus(openId) : null;

  return (
    <div className="bp">
      <div className="bp-head">
        <div className="bp-meta">
          <span className="bp-id">FEAT-2247</span>
          <span className="bp-title">Add OAuth fallback for self-hosted SSO</span>
        </div>
        <div className="bp-status">
          <span className={`status-dot ${isBlocked ? 'is-blocked' : ''}`} />
          <span className={`bp-active ${isBlocked ? 'is-blocked' : ''}`}>
            {isBlocked ? 'AWAITING APPROVAL · ' : ''}{activeLabels}
          </span>
          <span className="bp-elapsed">00:{String(12 + (tick % 48)).padStart(2,'0')}:34</span>
        </div>
      </div>

      <div className={`bp-stage ${openNode ? 'has-detail' : ''}`}>
        <div className="bp-canvas-wrap">
          <svg className="bp-canvas" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
            <g className="bp-edges">
              {BP_EDGES.map(([a, b], i) => {
                const na = nodeById[a], nb = nodeById[b];
                const x1 = colX(na.col) + (na.kind === 'approval' ? 28 : 42);
                const y1 = rowY(na.row);
                const x2 = colX(nb.col) - (nb.kind === 'approval' ? 28 : 42);
                const y2 = rowY(nb.row);
                const mx = (x1 + x2) / 2;
                const d = `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
                const live = isEdgeLive(a, b);
                const done = isEdgeDone(a, b);
                return (
                  <g key={i}>
                    <path d={d} className={`bp-edge ${live ? 'is-live' : ''} ${done ? 'is-done' : ''}`} />
                    {live && <path d={d} className="bp-edge-pulse" />}
                  </g>
                );
              })}
            </g>

            {/* Fan-out halo removed per design feedback */}

            <g className="bp-nodes">
              {BP_NODES.map((n) => {
                const st = stageStatus(n.id);
                const x = colX(n.col);
                const y = rowY(n.row);
                const isOpen = openId === n.id;
                const isApproval = n.kind === 'approval';

                if (isApproval) {
                  // Diamond gate
                  const s = 22;
                  return (
                    <g
                      key={n.id}
                      className={`bp-node bp-gate bp-gate-${st} ${isOpen ? 'is-open' : ''}`}
                      transform={`translate(${x}, ${y})`}
                      onClick={() => setOpenId(isOpen ? null : n.id)}
                      style={{ cursor: 'pointer' }}
                    >
                      {isOpen && <rect x={-s-4} y={-s-4} width={s*2+8} height={s*2+8} rx="6" className="bp-node-ring" transform="rotate(45)" />}
                      <rect x={-s} y={-s} width={s*2} height={s*2} className="bp-gate-shape" transform="rotate(45)" />
                      <text y="2" className="bp-gate-glyph">?</text>
                      <text y={s + 18} className="bp-gate-label">APPROVE PLAN</text>
                      {n.duration ? (
                        <text y={s + 32} className="bp-node-duration">{nodeDuration(n)}</text>
                      ) : null}
                    </g>
                  );
                }

                const w = 84, h = 28;
                return (
                  <g
                    key={n.id}
                    className={`bp-node bp-node-${st} bp-node-kind-${n.kind} ${isOpen ? 'is-open' : ''}`}
                    transform={`translate(${x - w/2}, ${y - h/2})`}
                    onClick={() => setOpenId(isOpen ? null : n.id)}
                    style={{ cursor: 'pointer' }}
                  >
                    {isOpen && <rect x="-4" y="-4" width={w+8} height={h+8} rx="5" className="bp-node-ring" />}
                    <rect x="0" y="0" width={w} height={h} rx="3" />
                    <text x={w/2} y={h/2 + 1} className="bp-node-label">{n.label.toUpperCase()}</text>
                    {n.duration ? (
                      <text x={w/2} y={h + 14} className="bp-node-duration">{nodeDuration(n)}</text>
                    ) : null}
                  </g>
                );
              })}
            </g>
          </svg>
        </div>

        {/* Detail card — gate variant has Approve/Reject/Request changes actions */}
        {openNode && openArtifacts && (
          openArtifacts.kind === 'gate' ? (
            <div className="bp-detail bp-detail-gate">
              <div className="bp-detail-head">
                <div className="bp-detail-title-row">
                  <span className="bp-detail-status bp-detail-status-blocked">⏸ awaiting approval</span>
                  <span className="bp-detail-title">{openNode.label}</span>
                </div>
                <button className="bp-detail-close" onClick={() => setOpenId(null)} aria-label="close">×</button>
              </div>
              <div className="bp-detail-summary">{openArtifacts.summary}</div>
              <div className="bp-gate-meta">
                <div><span className="bp-gate-meta-k">Requested by</span><span className="bp-gate-meta-v">{openArtifacts.requestedBy}</span></div>
                <div><span className="bp-gate-meta-k">Submitted</span><span className="bp-gate-meta-v">{openArtifacts.requestedAt}</span></div>
              </div>
              <div className="bp-detail-files">
                <div className="bp-detail-files-head">PROPOSED PLANS · {openArtifacts.files.length}</div>
                <ul className="bp-detail-file-list">
                  {openArtifacts.files.map((f) => (
                    <li key={f.name} className="bp-detail-file">
                      <span className="bp-detail-file-kind">MD</span>
                      <span className="bp-detail-file-name">{f.name}</span>
                      <span className="bp-detail-file-size">{f.size}</span>
                    </li>
                  ))}
                </ul>
              </div>
              <div className="bp-detail-foot bp-gate-actions">
                <button className="bp-gate-btn bp-gate-btn-reject">Reject</button>
                <button className="bp-gate-btn bp-gate-btn-changes">Request changes</button>
                <button className="bp-gate-btn bp-gate-btn-approve">Approve & execute →</button>
              </div>
            </div>
          ) : (
            <div className="bp-detail">
              <div className="bp-detail-head">
                <div className="bp-detail-title-row">
                  <span className={`bp-detail-status bp-detail-status-${openStatus}`}>
                    {openStatus === 'live' ? '● running' : openStatus === 'done' ? '✓ complete' : '○ pending'}
                  </span>
                  <span className="bp-detail-title">{openNode.label}</span>
                  <span className="bp-detail-pin">node://{openNode.id}</span>
                </div>
                <button className="bp-detail-close" onClick={() => setOpenId(null)} aria-label="close">×</button>
              </div>
              <div className="bp-detail-summary">{openArtifacts.summary}</div>
              <div className="bp-detail-files">
                <div className="bp-detail-files-head">ARTIFACTS · {openArtifacts.files.length}</div>
                <ul className="bp-detail-file-list">
                  {openArtifacts.files.map((f) => (
                    <li key={f.name} className="bp-detail-file">
                      <span className={`bp-detail-file-kind bp-detail-file-kind-${f.kind}`}>
                        {f.kind === 'code' || f.kind === 'test' ? 'TS' :
                         f.kind === 'doc' ? 'MD' :
                         f.kind === 'yaml' ? 'YML' :
                         f.kind === 'graph' ? 'JSON' :
                         f.kind === 'log' ? 'LOG' : 'FIL'}
                      </span>
                      <span className="bp-detail-file-name">{f.name}</span>
                      <span className="bp-detail-file-size">{f.size}</span>
                    </li>
                  ))}
                </ul>
              </div>
              <div className="bp-detail-foot">
                <button className="bp-detail-btn-primary">View details →</button>
              </div>
            </div>
          )
        )}
      </div>

      <div className="bp-foot">
        <div className="bp-legend">
          <span className="bp-legend-item bp-legend-done">▮ done</span>
          <span className="bp-legend-item bp-legend-live">▮ running</span>
          <span className="bp-legend-item bp-legend-blocked">▮ awaiting approval</span>
          <span className="bp-legend-item bp-legend-pending">▮ pending</span>
          <span className="bp-legend-spacer" />
          <span className="bp-legend-file">.sourceweaver/feature.yml</span>
          <span className="bp-legend-sep">@</span>
          <a className="bp-legend-sha" href="#" title="View commit a3f9c12">a3f9c12</a>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { BranchingPipeline });
