/* Force-directed and Embedding (UMAP) layouts for the prerequisite graph.
   Same node set as PrerequisiteGraph hierarchical for comparability. */

const _GRAPH_NODES = [
  { id:"real_numbers",  label:"ℝ",                kind:"axiom",      mastery:"mastered" },
  { id:"set",           label:"Set",              kind:"definition", mastery:"mastered" },
  { id:"function",      label:"Function",         kind:"definition", mastery:"mastered" },
  { id:"interval",      label:"Open interval",    kind:"definition", mastery:"mastered" },
  { id:"sequence",      label:"Sequence",         kind:"definition", mastery:"mastered" },
  { id:"slope_line",    label:"Slope of a line",  kind:"definition", mastery:"learning" },
  { id:"absolute_val",  label:"Absolute value",   kind:"definition", mastery:"mastered" },
  { id:"composition",   label:"Composition",      kind:"definition", mastery:"mastered" },
  { id:"limit_seq",     label:"Limit · seq",      kind:"definition", mastery:"mastered" },
  { id:"limit_func",    label:"Limit · func",     kind:"definition", mastery:"mastered" },
  { id:"epsilon_delta", label:"ε–δ",              kind:"definition", mastery:"learning" },
  { id:"limit_sum",     label:"Limit sum",        kind:"lemma",      mastery:"mastered" },
  { id:"limit_product", label:"Limit product",    kind:"lemma",      mastery:"learning" },
  { id:"limit_compose", label:"Limit compose",    kind:"lemma",      mastery:"learning" },
  { id:"continuity",    label:"Continuity",       kind:"definition", mastery:"learning" },
  { id:"secant",        label:"Secant line",      kind:"definition", mastery:"frontier" },
  { id:"diff_quot",     label:"Diff. quotient",   kind:"definition", mastery:"frontier" },
  { id:"derivative",    label:"Derivative",       kind:"definition", mastery:"frontier", focus:true },
  { id:"diff_implies_cont", label:"Diff ⇒ cont",  kind:"theorem",    mastery:"mastered" },
  { id:"sum_rule",      label:"Sum rule",         kind:"theorem",    mastery:"mastered" },
  { id:"product_rule",  label:"Product rule",     kind:"theorem",    mastery:"learning" },
  { id:"chain_rule",    label:"Chain rule",       kind:"theorem",    mastery:"frontier" },
  { id:"mvt",           label:"MVT",              kind:"theorem",    mastery:"locked" },
  { id:"l_hopital",     label:"L'Hôpital",        kind:"theorem",    mastery:"locked" },
  { id:"taylor",        label:"Taylor",           kind:"theorem",    mastery:"locked" },
  { id:"newtons_method",label:"Newton's method",  kind:"procedure",  mastery:"locked" },
];
const _GRAPH_EDGES = [
  ["function","derivative","USES"],["limit_func","derivative","USES"],
  ["diff_quot","derivative","DEFINES"],["secant","derivative","USES"],
  ["continuity","derivative","USES"],["interval","derivative","USES"],
  ["real_numbers","limit_seq","USES"],["set","function","USES"],
  ["function","limit_func","USES"],["function","composition","USES"],
  ["limit_seq","limit_func","GENERALIZES"],["limit_func","epsilon_delta","DEFINES"],
  ["limit_func","limit_sum","USES"],["limit_func","limit_product","USES"],
  ["limit_func","limit_compose","USES"],["limit_func","continuity","DEFINES"],
  ["slope_line","secant","USES"],["slope_line","diff_quot","USES"],
  ["function","secant","USES"],["absolute_val","continuity","USES"],
  ["derivative","diff_implies_cont","PROVES"],["derivative","sum_rule","PROVES"],
  ["derivative","product_rule","PROVES"],["derivative","chain_rule","PROVES"],
  ["limit_sum","sum_rule","USES"],["limit_product","product_rule","USES"],
  ["limit_compose","chain_rule","USES"],["continuity","diff_implies_cont","USES"],
  ["diff_implies_cont","mvt","USES"],["derivative","mvt","USES"],
  ["derivative","l_hopital","USES"],["derivative","taylor","USES"],
  ["derivative","newtons_method","USES"],
];

const _nodeFill = (m) => m==="mastered"?"var(--ink)":m==="frontier"?"var(--accent)":"var(--paper)";
const _nodeStroke = (m) => m==="locked"?"var(--rule-2)":m==="frontier"?"var(--accent)":"var(--ink)";

const _drawShape = (n, cx, cy) => {
  const fill = _nodeFill(n.mastery), stroke = _nodeStroke(n.mastery);
  if (n.kind === "theorem" || n.kind === "lemma") {
    return <rect x={cx-6} y={cy-6} width="12" height="12" fill={fill} stroke={stroke} strokeWidth={n.focus?2:1.25}/>;
  }
  if (n.kind === "axiom") {
    return <polygon points={`${cx},${cy-7} ${cx+7},${cy} ${cx},${cy+7} ${cx-7},${cy}`} fill={fill} stroke={stroke} strokeWidth="1.25"/>;
  }
  if (n.kind === "procedure") {
    return <polygon points={`${cx-6},${cy-4} ${cx+6},${cy-4} ${cx+4},${cy+4} ${cx-4},${cy+4}`} fill={fill} stroke={stroke} strokeWidth="1.25"/>;
  }
  return <circle cx={cx} cy={cy} r={n.focus?7:5} fill={fill} stroke={stroke} strokeWidth={n.focus?2:1.25}/>;
};

// ── Force layout ────────────────────────────────────────────
const ForceLayout = () => {
  // Hand-laid coordinates that approximate force-directed equilibrium.
  // Derivative is a hub; theorems pull outward; mastered foundations cluster.
  const positions = {
    real_numbers:[0.18,0.72], set:[0.10,0.58], function:[0.22,0.50],
    interval:[0.32,0.70], sequence:[0.06,0.80], slope_line:[0.28,0.88],
    absolute_val:[0.22,0.30], composition:[0.78,0.20],
    limit_seq:[0.26,0.62], limit_func:[0.40,0.42], epsilon_delta:[0.50,0.20],
    limit_sum:[0.62,0.30], limit_product:[0.74,0.34], limit_compose:[0.82,0.42],
    continuity:[0.34,0.30], secant:[0.42,0.66], diff_quot:[0.50,0.62],
    derivative:[0.54,0.50],
    diff_implies_cont:[0.42,0.16], sum_rule:[0.62,0.62],
    product_rule:[0.72,0.62], chain_rule:[0.78,0.54],
    mvt:[0.66,0.86], l_hopital:[0.78,0.80], taylor:[0.90,0.62], newtons_method:[0.92,0.30],
  };

  const W = 1180, H = 720, PAD = 60;
  const xToPx = (x) => PAD + x*(W-2*PAD), yToPx = (y) => PAD + y*(H-2*PAD);
  const byId = Object.fromEntries(_GRAPH_NODES.map(n => [n.id, n]));

  const edgeColor = (t) => t==="DEFINES"?"var(--accent)":t==="PROVES"?"var(--ink)":"var(--ink-3)";

  return (
    <svg viewBox={`0 0 ${W} ${H}`} className="pg__svg" preserveAspectRatio="xMidYMid meet">
      {/* faint org cells around clusters */}
      <ellipse cx={xToPx(0.18)} cy={yToPx(0.66)} rx="120" ry="90" fill="var(--paper-2)" opacity="0.6"/>
      <ellipse cx={xToPx(0.50)} cy={yToPx(0.50)} rx="180" ry="130" fill="var(--paper-2)" opacity="0.4"/>
      <ellipse cx={xToPx(0.78)} cy={yToPx(0.62)} rx="140" ry="100" fill="var(--paper-2)" opacity="0.4"/>
      <text x={xToPx(0.18)} y={yToPx(0.88)} fontFamily="var(--font-mono)" fontSize="9" fill="var(--ink-4)" textAnchor="middle" letterSpacing="1">FOUNDATIONS</text>
      <text x={xToPx(0.50)} y={yToPx(0.10)} fontFamily="var(--font-mono)" fontSize="9" fill="var(--ink-4)" textAnchor="middle" letterSpacing="1">LIMITS · CONTINUITY</text>
      <text x={xToPx(0.78)} y={yToPx(0.92)} fontFamily="var(--font-mono)" fontSize="9" fill="var(--ink-4)" textAnchor="middle" letterSpacing="1">THEOREMS</text>

      {_GRAPH_EDGES.map(([a,b,t], i) => {
        const pa = positions[a], pb = positions[b];
        if (!pa || !pb) return null;
        return <line key={i} x1={xToPx(pa[0])} y1={yToPx(pa[1])} x2={xToPx(pb[0])} y2={yToPx(pb[1])}
          stroke={edgeColor(t)} strokeWidth={t==="PROVES"?1.2:0.8} opacity={t==="USES"?0.4:0.7}/>;
      })}
      {_GRAPH_NODES.map(n => {
        const p = positions[n.id]; if (!p) return null;
        const cx = xToPx(p[0]), cy = yToPx(p[1]);
        return (
          <g key={n.id}>
            {_drawShape(n, cx, cy)}
            {n.focus && <circle cx={cx} cy={cy} r="14" fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="2 2"/>}
            <text x={cx+10} y={cy+4} fontFamily="var(--font-ui)" fontSize="10.5" fontWeight={n.focus?600:500}
                  fill={n.mastery==="locked"?"var(--ink-4)":"var(--ink)"}>{n.label}</text>
          </g>
        );
      })}
    </svg>
  );
};

// ── Embedding layout ────────────────────────────────────────
const EmbeddingLayout = () => {
  // qwen3 1024-dim → UMAP 2D. Hand-placed to put semantic neighbors close.
  // Note: edges still drawn but visually quieter — the projection isn't about prereqs.
  const positions = {
    // limit cluster
    limit_func:[0.30,0.32], limit_seq:[0.24,0.40], epsilon_delta:[0.38,0.26],
    limit_sum:[0.20,0.30], limit_product:[0.16,0.36], limit_compose:[0.22,0.20],
    continuity:[0.32,0.46],
    // derivative + neighbors (dense semantic cluster)
    derivative:[0.58,0.50], diff_quot:[0.54,0.56], secant:[0.50,0.60],
    slope_line:[0.46,0.66], chain_rule:[0.66,0.46], product_rule:[0.66,0.56],
    sum_rule:[0.62,0.60], diff_implies_cont:[0.46,0.42],
    // function/structure cluster
    function:[0.74,0.78], composition:[0.80,0.74], set:[0.86,0.82], real_numbers:[0.78,0.86],
    interval:[0.84,0.88], sequence:[0.70,0.84], absolute_val:[0.92,0.74],
    // theorem cluster (downstream)
    mvt:[0.78,0.30], l_hopital:[0.84,0.36], taylor:[0.88,0.26], newtons_method:[0.92,0.46],
  };

  const W = 1180, H = 720, PAD = 60;
  const xToPx = (x) => PAD + x*(W-2*PAD), yToPx = (y) => PAD + y*(H-2*PAD);

  // semantic clusters
  const clusters = [
    { cx:0.26, cy:0.32, rx:140, ry:120, label:"LIMITS", color:"var(--rule-3)" },
    { cx:0.56, cy:0.54, rx:160, ry:130, label:"DERIVATIVE NEIGHBORHOOD", color:"var(--accent)", emph:true },
    { cx:0.80, cy:0.80, rx:160, ry:100, label:"FUNCTION · STRUCTURE", color:"var(--rule-3)" },
    { cx:0.84, cy:0.34, rx:140, ry:100, label:"DOWNSTREAM THEOREMS", color:"var(--rule-3)" },
  ];

  return (
    <svg viewBox={`0 0 ${W} ${H}`} className="pg__svg" preserveAspectRatio="xMidYMid meet">
      <defs>
        <pattern id="emb-grid" width="40" height="40" patternUnits="userSpaceOnUse">
          <path d="M40 0 L0 0 0 40" fill="none" stroke="var(--rule-3)" strokeWidth="0.4" strokeDasharray="2 4"/>
        </pattern>
      </defs>
      <rect x={PAD} y={PAD} width={W-2*PAD} height={H-2*PAD} fill="url(#emb-grid)"/>
      <rect x={PAD} y={PAD} width={W-2*PAD} height={H-2*PAD} fill="none" stroke="var(--rule-2)" strokeWidth="1"/>

      {/* cluster envelopes */}
      {clusters.map((c, i) => (
        <g key={i}>
          <ellipse cx={xToPx(c.cx)} cy={yToPx(c.cy)} rx={c.rx} ry={c.ry}
                   fill={c.emph?"var(--accent)":"var(--ink)"} fillOpacity={c.emph?0.04:0.02}
                   stroke={c.color} strokeWidth={c.emph?1:0.5} strokeDasharray={c.emph?"4 3":"2 4"}/>
          <text x={xToPx(c.cx)} y={yToPx(c.cy)-c.ry-6} fontFamily="var(--font-mono)" fontSize="9"
                fill={c.emph?"var(--accent)":"var(--ink-4)"} textAnchor="middle" letterSpacing="1">{c.label}</text>
        </g>
      ))}

      {/* faint prereq edges (de-emphasized) */}
      {_GRAPH_EDGES.map(([a,b,t], i) => {
        const pa = positions[a], pb = positions[b]; if (!pa || !pb) return null;
        return <line key={i} x1={xToPx(pa[0])} y1={yToPx(pa[1])} x2={xToPx(pb[0])} y2={yToPx(pb[1])}
          stroke="var(--rule-3)" strokeWidth="0.4" opacity="0.5"/>;
      })}

      {/* iso-distance rings around derivative */}
      {[60, 130, 220].map((r, i) => (
        <circle key={i} cx={xToPx(0.58)} cy={yToPx(0.50)} r={r}
                fill="none" stroke="var(--accent)" strokeWidth="0.5" strokeDasharray="2 4" opacity="0.4"/>
      ))}

      {_GRAPH_NODES.map(n => {
        const p = positions[n.id]; if (!p) return null;
        const cx = xToPx(p[0]), cy = yToPx(p[1]);
        // distance to derivative for label decoration
        const dp = positions.derivative;
        const d = Math.hypot(p[0]-dp[0], p[1]-dp[1]);
        return (
          <g key={n.id}>
            {_drawShape(n, cx, cy)}
            {n.focus && <circle cx={cx} cy={cy} r="14" fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="2 2"/>}
            <text x={cx+10} y={cy+4} fontFamily="var(--font-ui)" fontSize="10.5" fontWeight={n.focus?600:500}
                  fill={n.mastery==="locked"?"var(--ink-4)":"var(--ink)"}>{n.label}</text>
            {n.id !== "derivative" && d < 0.20 && (
              <text x={cx+10} y={cy+15} fontFamily="var(--font-mono)" fontSize="8"
                    fill="var(--ink-4)">d={d.toFixed(2)}</text>
            )}
          </g>
        );
      })}

      {/* axes annotations */}
      <text x={W-PAD-4} y={H-PAD+18} fontFamily="var(--font-mono)" fontSize="9" fill="var(--ink-4)" textAnchor="end">UMAP 2D · qwen3 1024-dim · cosine</text>
      <text x={PAD} y={PAD-10} fontFamily="var(--font-mono)" fontSize="9" fill="var(--ink-4)">semantic projection · 26 / 14,082 nodes</text>
    </svg>
  );
};

window.ForceLayout = ForceLayout;
window.EmbeddingLayout = EmbeddingLayout;
