/* @jsxRuntime classic */
/* @jsx React.createElement */
// Orchestra — Context Map (Bench Phase-3, task 86banqh9k)
// Read-only visualization: Knowledge Base entry → affected Wright → category (the Bench),
// with freshness/dirty state. "Reconcile" on a dirty Wright jumps to The Bench (Operating Context).
// Loaded by index.html as a separate text/babel script; exposes window.ContextMapPage.

(function () {
  const { useState, useEffect, useMemo } = React;

  // ── API helper (mirrors app.jsx's api()) ──────────────────────────────────
  function toCamel(s) { return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); }
  function deepCamel(obj) {
    if (Array.isArray(obj)) return obj.map(deepCamel);
    if (obj && typeof obj === 'object') {
      const out = {};
      for (const k of Object.keys(obj)) out[toCamel(k)] = deepCamel(obj[k]);
      return out;
    }
    return obj;
  }
  async function api(method, path) {
    const res = await fetch(path, { method });
    if (!res.ok) { const t = await res.text().catch(() => ''); throw new Error(`${res.status}: ${t}`); }
    return deepCamel(await res.json());
  }

  // Category slugs come back from the Bench summary as camelCased dict keys
  // (e.g. "data_model" → "dataModel" after deepCamel). Humanize for display.
  function camelToLabel(s) {
    if (!s) return 'General';
    const spaced = s.replace(/([A-Z])/g, ' $1');
    return (spaced.charAt(0).toUpperCase() + spaced.slice(1)).trim();
  }

  function trunc(s, n) { return s && s.length > n ? s.slice(0, n - 1) + '…' : (s || ''); }

  function bezier(x1, y1, x2, y2) {
    const cx = (x1 + x2) / 2;
    return `M${x1},${y1} C${cx},${y1} ${cx},${y2} ${x2},${y2}`;
  }

  function ContextMapPage({ agents, navigateTo }) {
    const [summaries, setSummaries] = useState(null); // null = loading
    const [dirty, setDirty] = useState([]);
    const [kbEntries, setKbEntries] = useState([]);

    useEffect(() => {
      injectStyles();
      Promise.all([
        api('GET', '/api/bench').catch(() => ({ summaries: [] })),
        api('GET', '/api/bench/dirty').catch(() => ({ dirty: [] })),
        api('GET', '/api/org-context/entries').catch(() => []),
      ]).then(([benchRes, dirtyRes, entries]) => {
        setSummaries(benchRes.summaries || []);
        setDirty(dirtyRes.dirty || []);
        setKbEntries(entries || []);
      });
    }, []);

    const agentRoles = {};
    (agents || []).forEach(a => { agentRoles[a.id] = a.role || a.id; });

    const dirtySet = useMemo(() => {
      const s = new Set();
      dirty.forEach(d => s.add(`${d.agentId}::${toCamel(d.category || '')}`));
      return s;
    }, [dirty]);
    const dirtyAgentIds = useMemo(() => new Set(dirty.map(d => d.agentId)), [dirty]);

    const kbById = {};
    kbEntries.forEach(e => { kbById[e.id] = e; });
    const dirtyEntryIds = useMemo(() => {
      const s = new Set();
      dirty.forEach(d => { if (d.sourceEntryId) s.add(d.sourceEntryId); });
      return s;
    }, [dirty]);
    const liveKbNodes = Array.from(dirtyEntryIds)
      .map(id => kbById[id])
      .filter(e => e && e.kind !== 'group');

    const activeAgents = (summaries || []).filter(s => s.totalCount > 0);
    const wrightIds = activeAgents.map(s => s.agentId);

    const categorySet = {};
    activeAgents.forEach(s => {
      Object.entries(s.categories || {}).forEach(([cat, count]) => {
        categorySet[cat] = (categorySet[cat] || 0) + count;
      });
    });
    const categoryNames = Object.keys(categorySet).sort();

    if (summaries === null) {
      return (
        <div className="cm-root">
          <div className="cm-title">Context Map</div>
          <div className="cm-loading">Loading…</div>
        </div>
      );
    }

    if (wrightIds.length === 0) {
      return (
        <div className="cm-root">
          <div className="cm-title">Context Map</div>
          <div className="cm-sub">
            Visualizes Knowledge Base → Wright → category edges feeding the Bench.
          </div>
          <div className="cm-empty">
            No Bench activity yet. Items appear here once Builds complete review gates,
            or the Knowledge Base propagates entries into a Wright's Bench.
          </div>
        </div>
      );
    }

    // ── SVG layout: 3 columns — pending KB entries | Wrights | categories ──
    const NODE_W = 178, NODE_H = 34, ROW_GAP = 10, PAD_Y = 24, PAD_X = 20, COL_GAP = 140;
    const COL1_X = PAD_X;
    const COL2_X = COL1_X + NODE_W + COL_GAP;
    const COL3_X = COL2_X + NODE_W + COL_GAP;
    const SVG_W = COL3_X + NODE_W + PAD_X;

    const kbIds = liveKbNodes.map(e => e.id);
    const blockH = (arr) => (arr.length ? arr.length * (NODE_H + ROW_GAP) - ROW_GAP : 0);
    const SVG_H = Math.max(blockH(kbIds), blockH(wrightIds), blockH(categoryNames), NODE_H) + PAD_Y * 2;

    function colYs(ids) {
      const ys = {};
      const startY = (SVG_H - blockH(ids)) / 2;
      ids.forEach((id, i) => { ys[id] = startY + i * (NODE_H + ROW_GAP) + NODE_H / 2; });
      return ys;
    }
    const kbYs = colYs(kbIds);
    const wrightYs = colYs(wrightIds);
    const catYs = colYs(categoryNames);

    const kbEdges = [];
    dirty.forEach(d => {
      if (d.sourceEntryId && kbById[d.sourceEntryId] && kbYs[d.sourceEntryId] !== undefined
          && wrightYs[d.agentId] !== undefined) {
        kbEdges.push({ kbId: d.sourceEntryId, agentId: d.agentId });
      }
    });

    const catEdges = [];
    activeAgents.forEach(s => {
      Object.keys(s.categories || {}).forEach(cat => {
        if (catYs[cat] !== undefined) {
          catEdges.push({ agentId: s.agentId, cat, dirty: dirtySet.has(`${s.agentId}::${cat}`) });
        }
      });
    });

    return (
      <div className="cm-root">
        <div className="cm-head-row">
          <div>
            <div className="cm-title">Context Map</div>
            <div className="cm-sub">
              Knowledge Base → Wright → category edges feeding the Bench. Read-only —
              amber dashed edges are dirty (pending reconcile); gold edges are fresh.
            </div>
          </div>
          {dirtyAgentIds.size > 0 && (
            <button className="btn btn-outline btn-sm" onClick={() => navigateTo && navigateTo('bench')}>
              ⟳ Reconcile in The Bench
            </button>
          )}
        </div>

        <div style={{ overflowX: 'auto' }}>
          <svg width={SVG_W} height={SVG_H} style={{ display: 'block' }}>
            {kbEdges.map(({ kbId, agentId }) => (
              <path key={`kb-${kbId}-${agentId}`}
                d={bezier(COL1_X + NODE_W, kbYs[kbId], COL2_X, wrightYs[agentId])}
                fill="none" stroke="#e0a050" strokeWidth={1.5} strokeOpacity={0.65} strokeDasharray="4,3" />
            ))}
            {catEdges.map(({ agentId, cat, dirty: isDirty }) => (
              <path key={`cat-${agentId}-${cat}`}
                d={bezier(COL2_X + NODE_W, wrightYs[agentId], COL3_X, catYs[cat])}
                fill="none" stroke={isDirty ? '#e0a050' : '#c8a84a'} strokeWidth={1.5}
                strokeOpacity={isDirty ? 0.75 : 0.4}
                strokeDasharray={isDirty ? '4,3' : undefined} />
            ))}

            {liveKbNodes.map(e => (
              <g key={`kbn-${e.id}`} transform={`translate(${COL1_X},${kbYs[e.id] - NODE_H / 2})`}>
                <rect width={NODE_W} height={NODE_H} rx={6} fill="#1c2133" stroke="#e0a050" strokeWidth={1.5} />
                <text x={10} y={NODE_H / 2 + 1} dominantBaseline="middle" fill="#e0e6f0" fontSize={11.5}>
                  {trunc(e.title || `Entry #${e.id}`, 22)}
                </text>
              </g>
            ))}

            {wrightIds.map(id => {
              const isDirty = dirtyAgentIds.has(id);
              return (
                <g key={`wn-${id}`} transform={`translate(${COL2_X},${wrightYs[id] - NODE_H / 2})`}
                  style={{ cursor: isDirty ? 'pointer' : 'default' }}
                  onClick={() => { if (isDirty && navigateTo) navigateTo('bench'); }}>
                  <rect width={NODE_W} height={NODE_H} rx={6} fill="#1c2133"
                    stroke={isDirty ? '#e0a050' : '#3a4560'} strokeWidth={isDirty ? 1.5 : 1} />
                  <text x={10} y={NODE_H / 2 + 1} dominantBaseline="middle" fill="#e0e6f0" fontSize={12}>
                    {trunc(agentRoles[id] || id, isDirty ? 16 : 22)}
                  </text>
                  {isDirty && (
                    <text x={NODE_W - 8} y={NODE_H / 2 + 1} textAnchor="end" dominantBaseline="middle"
                      fill="#e0a050" fontSize={11}>⟳</text>
                  )}
                </g>
              );
            })}

            {categoryNames.map(cat => (
              <g key={`catn-${cat}`} transform={`translate(${COL3_X},${catYs[cat] - NODE_H / 2})`}>
                <rect width={NODE_W} height={NODE_H} rx={6} fill="#1c2133" stroke="#3a4560" strokeWidth={1} />
                <text x={10} y={NODE_H / 2 + 1} dominantBaseline="middle" fill="#e0e6f0" fontSize={11.5}>
                  {trunc(camelToLabel(cat), 20)}
                </text>
                <text x={NODE_W - 8} y={NODE_H / 2 + 1} textAnchor="end" dominantBaseline="middle"
                  fill="#c8a84a" fontSize={11}>{categorySet[cat]}</text>
              </g>
            ))}
          </svg>
        </div>

        <div className="cm-caption">
          Left: Knowledge Base entries pending propagation ({liveKbNodes.length}) · Middle: Wrights
          ({wrightIds.length}) · Right: categories — the Bench ({categoryNames.length}) · Click a
          dirty (⟳) Wright to reconcile.
        </div>

        {liveKbNodes.length === 0 && (
          <div className="cm-note">
            No Knowledge Base entries are currently pending propagation — every Wright's Bench
            reflects the latest Knowledge Base state. Wright→category edges below show existing
            Bench contents.
          </div>
        )}
      </div>
    );
  }

  function injectStyles() {
    if (document.getElementById('cm-styles')) return;
    const el = document.createElement('style');
    el.id = 'cm-styles';
    el.textContent = `
.cm-root { height:100%; overflow-y:auto; overflow-x:hidden; padding:24px; }
.cm-head-row { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; flex-wrap:wrap; margin-bottom:16px; }
.cm-title { font-weight:600; font-size:1.05rem; }
.cm-sub { font-size:0.82rem; color:#748090; margin-top:4px; line-height:1.55; max-width:620px; }
.cm-loading { color:#748090; font-size:0.85rem; margin-top:8px; }
.cm-empty { background:#0f1525; border:1px solid #1b2640; border-radius:10px; padding:24px;
  color:#748090; font-size:0.85rem; line-height:1.6; max-width:520px; margin-top:16px; }
.cm-caption { font-size:0.75rem; color:#748090; margin-top:10px; }
.cm-note { font-size:0.78rem; color:#748090; margin-top:14px; max-width:600px; line-height:1.55; }
`;
    document.head.appendChild(el);
  }

  window.ContextMapPage = ContextMapPage;
})();
