// FSM graph — hand-laid node positions for the 17-state solver FSM.
// Happy path runs along the spine; refund branches splay below.

const FSM_LAYOUT = {
  // x, y in a 720x420 canvas; we'll viewBox-scale.
  created:                    { x:  60, y: 110, branch: "happy" },
  keys_generated_alice:       { x: 130, y: 110, branch: "happy" },
  keys_generated_bob:         { x: 200, y: 110, branch: "happy" },
  alice_partially_initiated:  { x: 270, y: 110, branch: "happy" },
  alice_initiated:            { x: 340, y: 110, branch: "happy" },
  bob_partially_initiated:    { x: 410, y: 110, branch: "happy" },
  bob_initiated:              { x: 480, y: 110, branch: "happy" },
  alice_secret_shared:        { x: 550, y: 110, branch: "happy" },
  bob_redeemed:               { x: 620, y: 110, branch: "happy" },
  alice_redeemed:             { x: 680, y: 60,  branch: "terminal-ok" },

  lock_timelock_expired:      { x: 340, y: 200, branch: "refund" },
  refund_submitted:           { x: 420, y: 240, branch: "refund" },
  alice_refunded:             { x: 510, y: 280, branch: "terminal-info" },
  bob_refunded:               { x: 600, y: 320, branch: "terminal-info" },

  refund_timelock_expired:    { x: 510, y: 360, branch: "claim" },
  bob_claimed_from_refund:    { x: 600, y: 380, branch: "terminal-info" },
  alice_claimed_after_refund: { x: 680, y: 360, branch: "terminal-info" },
};

const FSM_EDGES = [
  ["created", "keys_generated_alice", "match_accepted"],
  ["keys_generated_alice", "keys_generated_bob", "counterparty_keys"],
  ["keys_generated_bob", "alice_partially_initiated", "phase1_step1"],
  ["alice_partially_initiated", "alice_initiated", "btc_lock_broadcast"],
  ["alice_initiated", "bob_partially_initiated", "phase1_step2"],
  ["bob_partially_initiated", "bob_initiated", "zec_deposit_confirmed"],
  ["bob_initiated", "alice_secret_shared", "phase2_secret"],
  ["alice_secret_shared", "bob_redeemed", "btc_lock_spent"],
  ["bob_redeemed", "alice_redeemed", "orchard_claim"],

  ["alice_initiated", "lock_timelock_expired", "refund_csv_elapsed"],
  ["bob_partially_initiated", "lock_timelock_expired", "refund_csv_elapsed"],
  ["bob_initiated", "lock_timelock_expired", "refund_csv_elapsed"],
  ["lock_timelock_expired", "refund_submitted", "refund_tx_broadcast"],
  ["refund_submitted", "alice_refunded", "refund_tx_confirmed"],
  ["alice_refunded", "bob_refunded", "swap_hash_revealed"],

  ["alice_refunded", "refund_timelock_expired", "refund_csv2_elapsed"],
  ["refund_timelock_expired", "bob_claimed_from_refund", "bob_csv_claim"],
  ["refund_timelock_expired", "alice_claimed_after_refund", "alice_csv_claim"],
];

function pathProgress(currentState) {
  // Returns set of states already visited (inclusive of current).
  const visited = new Set();
  const idx = ZW.HAPPY_PATH.indexOf(currentState);
  if (idx >= 0) {
    for (let i = 0; i <= idx; i++) visited.add(ZW.HAPPY_PATH[i]);
    return visited;
  }
  // Refund branches: assume happy path up to alice_initiated then branch
  ZW.HAPPY_PATH.slice(0, 5).forEach((s) => visited.add(s));
  const ri = ZW.REFUND_BRANCH.indexOf(currentState);
  if (ri >= 0) {
    for (let i = 0; i <= ri; i++) visited.add(ZW.REFUND_BRANCH[i]);
    return visited;
  }
  ZW.REFUND_BRANCH.slice(0, 3).forEach((s) => visited.add(s));
  const ci = ZW.CLAIM_BRANCH.indexOf(currentState);
  if (ci >= 0) {
    for (let i = 0; i <= ci; i++) visited.add(ZW.CLAIM_BRANCH[i]);
  }
  return visited;
}

function FSMGraph({ currentState, compact = false, height = 360 }) {
  const W = 740, H = 420;
  const visited = pathProgress(currentState);

  function nodeStyle(state) {
    const isCurrent = state === currentState;
    const isVisited = visited.has(state);
    const isTerminal = ZW.TERMINAL.has(state);
    const isHappyTerm = state === "alice_redeemed";
    const layout = FSM_LAYOUT[state];

    let fill = "transparent";
    let stroke = "var(--line-strong)";
    let textColor = "var(--fg-3)";

    if (isVisited) {
      stroke = layout.branch === "refund" || layout.branch === "claim"
        ? "var(--warn)" : "var(--alice)";
      fill = layout.branch === "refund" || layout.branch === "claim"
        ? "color-mix(in oklch, var(--warn) 18%, var(--bg-1))"
        : "color-mix(in oklch, var(--alice) 18%, var(--bg-1))";
      textColor = "var(--fg-0)";
    }
    if (isTerminal && isVisited) {
      stroke = isHappyTerm ? "var(--ok)" : "var(--info)";
      fill = isHappyTerm
        ? "color-mix(in oklch, var(--ok) 22%, var(--bg-1))"
        : "color-mix(in oklch, var(--info) 22%, var(--bg-1))";
    }
    if (isCurrent) {
      stroke = "var(--alice)";
      if (layout.branch === "refund" || layout.branch === "claim") stroke = "var(--warn)";
      if (state === "alice_redeemed") stroke = "var(--ok)";
      textColor = "var(--fg-0)";
    }
    return { fill, stroke, textColor, isCurrent };
  }

  function edgeColor(from, to) {
    if (visited.has(from) && visited.has(to)) {
      const fromBr = FSM_LAYOUT[from].branch;
      const toBr = FSM_LAYOUT[to].branch;
      if (fromBr.startsWith("terminal") || toBr.startsWith("terminal")) return "var(--ok)";
      if (fromBr === "refund" || toBr === "refund" || fromBr === "claim" || toBr === "claim")
        return "var(--warn)";
      return "var(--alice)";
    }
    return "var(--line)";
  }

  return (
    <div style={{ width: "100%", height, position: "relative" }}>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet"
        style={{ width: "100%", height: "100%", display: "block" }}>
        <defs>
          <marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
            <path d="M0,0 L6,3 L0,6 z" fill="var(--fg-3)" />
          </marker>
          <marker id="arrow-active" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
            <path d="M0,0 L6,3 L0,6 z" fill="var(--alice)" />
          </marker>
          <marker id="arrow-warn" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
            <path d="M0,0 L6,3 L0,6 z" fill="var(--warn)" />
          </marker>
          <marker id="arrow-ok" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
            <path d="M0,0 L6,3 L0,6 z" fill="var(--ok)" />
          </marker>
        </defs>

        {/* Branch labels */}
        <text x={20} y={28} fill="var(--fg-3)" fontSize={10} fontFamily="var(--mono)">HAPPY PATH</text>
        <text x={20} y={232} fill="var(--fg-3)" fontSize={10} fontFamily="var(--mono)">REFUND BRANCH (timelock0)</text>
        <text x={20} y={372} fill="var(--fg-3)" fontSize={10} fontFamily="var(--mono)">CLAIM-AFTER-REFUND (timelock1)</text>

        {/* Edges */}
        {FSM_EDGES.map(([from, to, evt], i) => {
          const a = FSM_LAYOUT[from], b = FSM_LAYOUT[to];
          const color = edgeColor(from, to);
          const active = visited.has(from) && visited.has(to);
          const dx = b.x - a.x, dy = b.y - a.y;
          const len = Math.hypot(dx, dy);
          const ux = dx / len, uy = dy / len;
          const r1 = 22, r2 = 22;
          const x1 = a.x + ux * r1, y1 = a.y + uy * r1;
          const x2 = b.x - ux * r2, y2 = b.y - uy * r2;
          const isWarn = (FSM_LAYOUT[from].branch === "refund" || FSM_LAYOUT[from].branch === "claim" ||
                          FSM_LAYOUT[to].branch === "refund" || FSM_LAYOUT[to].branch === "claim");
          const isOk = to === "alice_redeemed" && active;
          const marker = !active ? "url(#arrow)" :
            isOk ? "url(#arrow-ok)" : isWarn ? "url(#arrow-warn)" : "url(#arrow-active)";
          return (
            <line key={i}
              x1={x1} y1={y1} x2={x2} y2={y2}
              stroke={color}
              strokeWidth={active ? 1.5 : 1}
              strokeDasharray={active ? "0" : "3 3"}
              markerEnd={marker}
              opacity={active ? 1 : 0.5}
            />
          );
        })}

        {/* Nodes */}
        {Object.entries(FSM_LAYOUT).map(([state, pos]) => {
          const { fill, stroke, textColor, isCurrent } = nodeStyle(state);
          const isTerm = ZW.TERMINAL.has(state);
          const r = isTerm ? 18 : 16;
          const label = ZW.STATE_LABELS[state];
          return (
            <g key={state} transform={`translate(${pos.x}, ${pos.y})`}>
              {isCurrent && (
                <circle r={r + 6}
                  fill="none"
                  stroke={stroke}
                  strokeWidth={1}
                  opacity={0.6}>
                  <animate attributeName="r" values={`${r+2};${r+10};${r+2}`} dur="2s" repeatCount="indefinite" />
                  <animate attributeName="opacity" values="0.6;0;0.6" dur="2s" repeatCount="indefinite" />
                </circle>
              )}
              <circle r={r} fill={fill} stroke={stroke} strokeWidth={isCurrent ? 2 : 1.2} />
              {isTerm && (
                <circle r={r - 4} fill="none" stroke={stroke} strokeWidth={0.8} opacity={0.5} />
              )}
              <text y={r + 14} textAnchor="middle"
                fontSize={10} fontFamily="var(--sans)" fontWeight={isCurrent ? 600 : 500}
                fill={textColor}>
                {label}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

// Compact horizontal "stepper" version for table rows.
function FSMStepperMini({ state }) {
  const visited = pathProgress(state);
  const path = ZW.HAPPY_PATH;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 2 }}>
      {path.map((s, i) => {
        const v = visited.has(s);
        const cur = s === state;
        const branched = !ZW.HAPPY_PATH.includes(state);
        return (
          <span key={s} style={{
            width: cur ? 10 : 7,
            height: 7,
            borderRadius: 999,
            background: cur ? "var(--alice)"
                       : v ? "color-mix(in oklch, var(--alice) 60%, transparent)"
                            : "var(--bg-3)",
            border: cur ? "none" : "1px solid var(--line)",
            transition: "all 200ms",
          }} />
        );
      })}
      {!ZW.HAPPY_PATH.includes(state) && (
        <span style={{
          marginLeft: 4, fontSize: 10, color: "var(--warn)",
          fontFamily: "var(--mono)",
        }}>↘ {ZW.STATE_LABELS[state]}</span>
      )}
    </div>
  );
}

Object.assign(window, { FSMGraph, FSMStepperMini, FSM_LAYOUT, FSM_EDGES, pathProgress });
