/* portfolio-super-editor.jsx — One tab per project.
 *
 * The new primary CMS surface: pick a project from the left list, see EVERY
 * section that mentions it on the right (card · stack · metrics · case study
 * · frameworks linked to it · learning episodes about it · downloads about
 * it), and edit it all top-to-bottom in one flow. The old per-section flat
 * tabs are still in the codebase for fallback / advanced editing.
 *
 * Also exposes:
 *  • "Add project from folder path" — drop a path, get an empty draft to fill
 *  • "Add project from URL"          — same, prefilled with the URL as a link
 *  • "Re-scan with AI"               — (UI-visible; runs the ingestion tool in
 *                                       Slice 3, returns a diff for review)
 */

const { useState: useStateSE, useMemo: useMemoSE } = React;

// ── tiny atoms shared with the rest of the admin ────────────────────────────
const SEField = ({ label, value, onChange, type = "text", hint, placeholder, multiline, rows = 3 }) => (
  <div className="form-row">
    <label className="lab">{label}</label>
    {multiline
      ? <textarea value={value || ""} onChange={(e) => onChange(e.target.value)} rows={rows} placeholder={placeholder} />
      : <input type={type} value={value || ""} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} />}
    {hint && <div className="hint">{hint}</div>}
  </div>
);
const SEList = ({ label, value, onChange, hint, rows = 4 }) => (
  <div className="form-row">
    <label className="lab">{label}</label>
    <textarea rows={rows} value={(value || []).join("\n")} onChange={(e) => onChange(e.target.value.split("\n").filter(Boolean))} />
    {hint && <div className="hint">{hint}</div>}
  </div>
);

// Section wrapper with collapsible header — keeps the long super-editor scannable.
const SEBlock = ({ title, hint, defaultOpen = true, action, children }) => (
  <details className="admin-item se-block" open={defaultOpen}
    style={{ marginBottom: 14, padding: "10px 14px", border: "1px solid var(--border)", borderRadius: 10 }}>
    <summary style={{ cursor: "pointer", listStyle: "none", padding: "4px 0", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
      <span>
        <strong style={{ fontSize: 13 }}>{title}</strong>
        {hint && <span style={{ color: "var(--text-3)", fontWeight: 400, fontSize: 11, marginLeft: 8 }}>· {hint}</span>}
      </span>
      {action}
    </summary>
    <div style={{ marginTop: 12 }}>{children}</div>
  </details>
);

// ── helpers ─────────────────────────────────────────────────────────────────
function makeProject(seed = {}) {
  return {
    id: uid("prod"),
    name: seed.name || "New project",
    kicker: "",
    tagline: "",
    status: "active-development",
    publishStatus: "draft",
    domain: "",
    logo: (seed.name || "NP").slice(0, 2).toUpperCase(),
    color: "#2563eb",
    screenshotSlot: "",
    hero: "",
    businessProblem: "",
    operationalWorkflow: "",
    engineeringGoals: [],
    stack: [],
    metrics: [],
    roadmap: [],
    links: { github: seed.url || "", demo: "", pdf: "" },
    frameworks: [],
    featured: false,
    sourcePath: seed.path || "",       // local folder path (for AI re-scan)
    sourceUrl: seed.url || "",          // remote URL (for AI re-scan)
    caseStudy: { problem: "", workflow: "", architecture: "", implementation: "", impact: [], lessons: [] },
    episodes: [],                       // per-project learning episodes (0..N)
    workflowSteps: [],                  // ordered [{label, sub}] for the flow diagram
    architectureSteps: [],              // ordered [{label, sub}] — architecture pipeline
    implementationSteps: [],            // ordered [{label, sub}] — implementation steps
    downloads: [],                      // per-project downloads
  };
}

// What sections in `data` mention this project (by id or fuzzy name match)?
// Surfaced as related editors so you don't have to hunt across tabs.
function relatedAcrossData(data, p) {
  const pn = (p.name || "").toLowerCase();
  const matches = (s) => s && pn && String(s).toLowerCase().includes(pn);
  return {
    caseStudies: (data.projects || []).filter((cs) => matches(cs.name) || cs.id === p.id),
    frameworks: (data.frameworks?.items || []).filter((f) => matches(f.linkedTo)),
    episodes: (data.learning?.episodes || []).filter((e) => matches(e.title) || matches(e.summary)),
    downloads: (data.downloads?.items || []).filter((d) => matches(d.name) || matches(d.description)),
  };
}

// ── the super editor ────────────────────────────────────────────────────────
const ProjectSuperEditor = ({ data, update }) => {
  const { show } = React.useContext(ToastContext);
  const products = data.products || { items: [], statuses: {} };
  const items = products.items || [];

  const [pickIdx, setPickIdx] = useStateSE(0);
  const [adding, setAdding] = useStateSE(false);
  const [addMode, setAddMode] = useStateSE("path"); // "path" | "url"
  const [addValue, setAddValue] = useStateSE("");

  const current = items[pickIdx] || null;
  const statuses = products.statuses || {};
  const publishStatuses = ["draft", "review", "published", "archived"];

  const setProduct = (i, patch) => {
    const next = items.map((x, j) => j === i ? { ...x, ...patch } : x);
    update({ ...data, products: { ...products, items: next } });
  };
  const setLinks = (i, k, v) => setProduct(i, { links: { ...(items[i].links || {}), [k]: v } });
  const setMetric = (i, mi, k, v) =>
    setProduct(i, { metrics: (items[i].metrics || []).map((m, j) => j === mi ? { ...m, [k]: v } : m) });
  const addMetric = (i) => setProduct(i, { metrics: [...(items[i].metrics || []), { num: "", lab: "" }] });
  const rmMetric = (i, mi) => setProduct(i, { metrics: (items[i].metrics || []).filter((_, j) => j !== mi) });
  const setCS = (i, k, v) => setProduct(i, { caseStudy: { ...(items[i].caseStudy || {}), [k]: v } });
  // Episodes: one project can have many learning episodes (intro, deep-dive, postmortem…).
  const setEpisode = (i, ei, k, v) =>
    setProduct(i, { episodes: (items[i].episodes || []).map((e, j) => j === ei ? { ...e, [k]: v } : e) });
  const addEpisode = (i) => setProduct(i, { episodes: [...(items[i].episodes || []), { title: "", summary: "", tags: [], duration: "" }] });
  const rmEpisode = (i, ei) => setProduct(i, { episodes: (items[i].episodes || []).filter((_, j) => j !== ei) });
  // Workflow / architecture / implementation: ordered step arrays that render as
  // a real flow diagram (boxes + arrows). Same shape as the Showcase workflow.
  const setStep = (i, field, si, k, v) =>
    setProduct(i, { [field]: (items[i][field] || []).map((s, j) => j === si ? { ...s, [k]: v } : s) });
  const addStep = (i, field) => setProduct(i, { [field]: [...(items[i][field] || []), { label: "Step", sub: "" }] });
  const rmStep = (i, field, si) => setProduct(i, { [field]: (items[i][field] || []).filter((_, j) => j !== si) });

  // Case-study impact rows ({num, lab}) — drives the big metric row inside the
  // case study modal on the public site.
  const setImpact = (i, ii, k, v) =>
    setProduct(i, { caseStudy: { ...(items[i].caseStudy || {}), impact: ((items[i].caseStudy?.impact) || []).map((m, j) => j === ii ? { ...m, [k]: v } : m) } });
  const addImpact = (i) => setProduct(i, { caseStudy: { ...(items[i].caseStudy || {}), impact: [...((items[i].caseStudy?.impact) || []), { num: "", lab: "" }] } });
  const rmImpact = (i, ii) => setProduct(i, { caseStudy: { ...(items[i].caseStudy || {}), impact: ((items[i].caseStudy?.impact) || []).filter((_, j) => j !== ii) } });

  // Roadmap ({phase, item}) — was in the product schema but had no editor UI.
  const setRoadmap = (i, ri, k, v) =>
    setProduct(i, { roadmap: (items[i].roadmap || []).map((r, j) => j === ri ? { ...r, [k]: v } : r) });
  const addRoadmap = (i) => setProduct(i, { roadmap: [...(items[i].roadmap || []), { phase: "Next", item: "" }] });
  const rmRoadmap = (i, ri) => setProduct(i, { roadmap: (items[i].roadmap || []).filter((_, j) => j !== ri) });

  // Per-project downloads ({name, type, size, description, file}).
  const setDownload = (i, di, k, v) =>
    setProduct(i, { downloads: (items[i].downloads || []).map((d, j) => j === di ? { ...d, [k]: v } : d) });
  const addDownload = (i) => setProduct(i, { downloads: [...(items[i].downloads || []), { id: uid("dl"), name: "", type: "PDF", size: "", description: "", file: "", tags: [] }] });
  const rmDownload = (i, di) => setProduct(i, { downloads: (items[i].downloads || []).filter((_, j) => j !== di) });

  // Per-project framework links ({name, summary, pillars[], poster, posterCaption}).
  // Stored on the product so editing one project edits everything for it.
  const setFw = (i, fi, k, v) =>
    setProduct(i, { frameworks: (items[i].frameworks || []).map((f, j) => j === fi ? (typeof f === "string" ? { name: f, [k]: v } : { ...f, [k]: v }) : f) });
  const addFw = (i) => setProduct(i, { frameworks: [...(items[i].frameworks || []), { name: "", summary: "", pillars: [], poster: "", posterCaption: "" }] });
  const rmFw = (i, fi) => setProduct(i, { frameworks: (items[i].frameworks || []).filter((_, j) => j !== fi) });

  const addProject = () => {
    const seed = addMode === "path" ? { path: addValue, name: addValue.split(/[\\\/]/).pop() } : { url: addValue };
    if (!addValue.trim()) { show("Paste a folder path or a URL first"); return; }
    const fresh = makeProject(seed);
    const next = [...items, fresh];
    update({ ...data, products: { ...products, items: next } });
    setPickIdx(next.length - 1);
    setAdding(false); setAddValue("");
    show("Project draft added — fill in the rest, then Publish");
  };

  const removeProject = (i) => {
    if (!confirm(`Delete "${items[i].name}"? This removes the project from the CMS only — your code folder isn't touched.`)) return;
    const next = items.filter((_, j) => j !== i);
    update({ ...data, products: { ...products, items: next } });
    setPickIdx(Math.max(0, Math.min(i, next.length - 1)));
  };

  const moveProject = (i, dir) => {
    const j = i + dir; if (j < 0 || j >= items.length) return;
    const next = [...items]; [next[i], next[j]] = [next[j], next[i]];
    update({ ...data, products: { ...products, items: next } });
    setPickIdx(j);
  };

  // Re-scan: post the source path to the local dev server, which runs the
  // ingestion engine and returns a fresh draft. We then show a diff modal so
  // the user approves/skips per field — never blindly overwriting.
  const [diff, setDiff] = useStateSE(null);    // single-project diff: { draft, log }
  const [rescanning, setRescanning] = useStateSE(false);
  const [bulk, setBulk] = useStateSE(null);    // bulk-scan result: { root, items }
  const [bulkScanning, setBulkScanning] = useStateSE(false);
  const [scanRoot, setScanRoot] = useStateSE("");
  const rescan = async () => {
    if (!current) return;
    const path = current.sourcePath;
    if (!path) {
      if (current.sourceUrl) { show("URL re-scan is coming next — for now, clone the URL to a local folder and set the Folder path."); return; }
      show("Add a folder path on this project first (under 'Source')"); return;
    }
    setRescanning(true);
    try {
      const r = await fetch("/__ingest", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path, name: current.name }) });
      const j = await r.json();
      if (!j.ok) { show("Re-scan failed: " + (j.error || "unknown")); console.warn(j); return; }
      setDiff({ draft: j.draft, log: j.log || "" });
    } catch (e) {
      show("Re-scan needs the local dev server (python .claude/serve.py). On the live site, run it on your laptop.");
    } finally { setRescanning(false); }
  };

  // The fields the engine produces and we can diff against the current product.
  // Everything else (status pill, color, logo, etc.) is human-curated, so we
  // never let AI touch it.
  const DIFF_FIELDS = [
    "kicker", "tagline", "domain", "hero",
    "businessProblem", "operationalWorkflow",
    "engineeringGoals", "stack", "metrics",
    "workflowSteps", "architectureSteps", "implementationSteps",
    "roadmap", "frameworks", "downloads", "episodes",   // ← gap-fix: AI can now propose updates to all per-project surfaces
  ];

  const applyDiff = (picks) => {
    if (!diff) return;
    const patch = {};
    for (const f of DIFF_FIELDS) if (picks[f]) patch[f] = diff.draft[f];
    setProduct(pickIdx, patch);
    setDiff(null);
    show(`Applied ${Object.keys(picks).filter(k => picks[k]).length} field(s) — review and Publish to go live.`);
  };

  // Bulk: scan a root folder (e.g. C:/Projects), surface every subfolder as
  // either a NEW project to add, or an UPDATE diff against an existing record.
  // Heavy operation — needs the local dev server (it runs the ingestion engine
  // per subfolder). On the live site we surface a friendly nudge.
  const runScanRoot = async () => {
    if (!scanRoot.trim()) { show("Type a root folder path first (e.g. C:/Projects)"); return; }
    setBulkScanning(true);
    try {
      const existing = items.map(p => ({ id: p.id, name: p.name, sourcePath: p.sourcePath || "" }));
      const r = await fetch("/__scan-root", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: scanRoot, existing }) });
      const j = await r.json();
      if (!j.ok) { show("Scan failed: " + (j.error || "unknown")); console.warn(j); return; }
      setBulk(j);
    } catch (e) {
      show("Scan needs the local dev server (python .claude/serve.py). Run it on your laptop with the code.");
    } finally { setBulkScanning(false); }
  };

  // Apply the user's bulk picks: add new projects, patch existing ones by id.
  const applyBulk = (picks) => {
    if (!bulk) return;
    let added = 0, updated = 0;
    const next = [...items];
    for (const it of bulk.items) {
      const pick = picks[it.folder]; if (!pick || !pick.apply || !it.draft) continue;
      if (it.matched) {
        const idx = next.findIndex(p => p.id === it.matched.id);
        if (idx >= 0) {
          const patch = {};
          for (const f of DIFF_FIELDS) if (pick.fields[f]) patch[f] = it.draft[f];
          next[idx] = { ...next[idx], ...patch, sourcePath: next[idx].sourcePath || it.fullPath };
          updated++;
        }
      } else {
        next.push(makeProject({ name: it.folder, path: it.fullPath }));
        const justAdded = next[next.length - 1];
        for (const f of DIFF_FIELDS) if (pick.fields[f]) justAdded[f] = it.draft[f];
        // also copy resume bullets + tags if present
        if (it.draft.metrics) justAdded.metrics = it.draft.metrics;
        if (it.draft.stack) justAdded.stack = it.draft.stack;
        added++;
      }
    }
    update({ ...data, products: { ...products, items: next } });
    setBulk(null);
    show(`Bulk imported: ${added} new · ${updated} updated. Review each in the picker and Publish when ready.`);
  };

  const related = current ? relatedAcrossData(data, current) : { caseStudies: [], frameworks: [], episodes: [], downloads: [] };

  return (
    <div className="se-root">
      {/* ── header: counts + add buttons ─────────────────────────────── */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, gap: 10, flexWrap: "wrap" }}>
        <div>
          <strong style={{ fontSize: 14 }}>Projects</strong>
          <span style={{ color: "var(--text-3)", fontSize: 12, marginLeft: 8 }}>{items.length} in CMS</span>
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          <button className="btn btn-sm" onClick={() => { setAddMode("path"); setAdding(true); }}><Icon name="plus" size={11}/> One folder</button>
          <button className="btn btn-sm" onClick={() => { setAddMode("url"); setAdding(true); }}><Icon name="plus" size={11}/> URL</button>
        </div>
      </div>

      {/* Scan a root: the one-click bulk import + bulk re-scan. Updates
          existing projects (matched by name) and surfaces brand-new folders. */}
      <div className="se-scan-root">
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
          <Icon name="spark" size={13}/>
          <strong style={{ fontSize: 13 }}>Scan a root folder</strong>
          <span style={{ fontSize: 11.5, color: "var(--text-3)" }}>
            One pass over every subfolder · new ones become drafts · existing ones get a per-field diff
          </span>
        </div>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          <input style={{ flex: 1, minWidth: 200 }} value={scanRoot} onChange={(e) => setScanRoot(e.target.value)} placeholder="e.g. C:/Projects" />
          <button className="btn btn-accent btn-sm" onClick={runScanRoot} disabled={bulkScanning}>
            <Icon name="spark" size={11}/> {bulkScanning ? "Scanning…" : "Scan + review"}
          </button>
        </div>
        <div className="hint" style={{ marginTop: 6 }}>
          Runs locally on your laptop (needs <code>python .claude/serve.py</code>). Use this whenever you've added or updated projects on disk.
        </div>
      </div>

      {adding && (
        <div style={{ padding: 12, marginBottom: 12, border: "1px dashed var(--accent)", background: "var(--accent-soft)", borderRadius: 10 }}>
          <label className="lab">{addMode === "path" ? "Project folder path (on this machine)" : "Project URL"}</label>
          <input value={addValue} onChange={(e) => setAddValue(e.target.value)}
            placeholder={addMode === "path" ? "C:/Projects/FinWise" : "https://github.com/you/finwise"} />
          <div style={{ marginTop: 8, display: "flex", gap: 6 }}>
            <button className="btn btn-accent btn-sm" onClick={addProject}><Icon name="check" size={11}/> Add as draft</button>
            <button className="btn btn-sm btn-ghost" onClick={() => { setAdding(false); setAddValue(""); }}>Cancel</button>
          </div>
          <div className="hint" style={{ marginTop: 6 }}>This creates a draft project record. In Slice 3, the same "Source" field powers the AI re-scan flow.</div>
        </div>
      )}

      {/* Bulk modal lives at root so it opens even when no projects exist yet
          (e.g. you're starting from scratch and scanning a root to populate). */}
      {bulk && <BulkReview bulk={bulk} products={items} fields={DIFF_FIELDS} onApply={applyBulk} onClose={() => setBulk(null)} />}

      {/* ── two-col layout: picker | editor ──────────────────────────── */}
      {items.length === 0 ? (
        <div style={{ padding: 22, textAlign: "center", color: "var(--text-3)", border: "1px dashed var(--border)", borderRadius: 10 }}>
          No projects yet — use <strong>Scan a root folder</strong> above (best), <strong>One folder</strong>, or <strong>URL</strong>.
        </div>
      ) : (
        <>{diff && current && <RescanDiff current={current} draft={diff.draft} log={diff.log} fields={DIFF_FIELDS} onApply={applyDiff} onClose={() => setDiff(null)} />}
        <div className="se-layout">
          {/* picker */}
          <aside className="se-picker">
            {items.map((p, i) => (
              <button key={p.id} className={"se-pick-row" + (i === pickIdx ? " active" : "")} onClick={() => setPickIdx(i)}>
                <span className="se-pick-logo" style={{ background: p.color || "var(--accent)" }}>{p.logo || (p.name || "?").slice(0,2).toUpperCase()}</span>
                <span className="se-pick-body">
                  <span className="se-pick-name">{p.name || "(unnamed)"}</span>
                  <span className="se-pick-meta">
                    <span className="se-pub" data-state={p.publishStatus || "published"}>{p.publishStatus || "published"}</span>
                    {p.domain && <span style={{ marginLeft: 6 }}>· {p.domain}</span>}
                  </span>
                </span>
              </button>
            ))}
          </aside>

          {/* editor */}
          <div className="se-editor">
            {current && (
              <>
                {/* sticky action bar */}
                <div className="se-bar">
                  <div style={{ display: "flex", gap: 6 }}>
                    <button className="btn btn-sm" onClick={() => moveProject(pickIdx, -1)} title="Move up"><Icon name="chevron-up" size={12}/></button>
                    <button className="btn btn-sm" onClick={() => moveProject(pickIdx, 1)} title="Move down"><Icon name="chevron-down" size={12}/></button>
                    <button className="btn btn-sm btn-ghost" onClick={rescan} disabled={rescanning} title="Re-scan source folder + draft updates">
                      <Icon name="spark" size={12}/> {rescanning ? "Re-scanning…" : "Re-scan with AI"}
                    </button>
                  </div>
                  <button className="btn btn-sm btn-ghost" style={{ color: "var(--danger)" }} onClick={() => removeProject(pickIdx)}><Icon name="trash" size={11}/> Delete</button>
                </div>

                {/* 1 · Identity */}
                <SEBlock title="Identity" hint="how the card and modal read" defaultOpen>
                  <div className="form-grid-2">
                    <SEField label="Name" value={current.name} onChange={(v) => setProduct(pickIdx, { name: v })} />
                    <SEField label="Kicker (small label)" value={current.kicker} onChange={(v) => setProduct(pickIdx, { kicker: v })} />
                  </div>
                  <SEField label="Tagline (one-liner)" value={current.tagline} onChange={(v) => setProduct(pickIdx, { tagline: v })} />
                  <div className="form-grid-3">
                    <SEField label="Domain" value={current.domain} onChange={(v) => setProduct(pickIdx, { domain: v })} placeholder="Fintech · Mobile" />
                    <div className="form-row" style={{ margin: 0 }}>
                      <label className="lab">Logo (2–3 chars)</label>
                      <input value={current.logo || ""} maxLength={3} onChange={(e) => setProduct(pickIdx, { logo: e.target.value })} />
                    </div>
                    <div className="form-row" style={{ margin: 0 }}>
                      <label className="lab">Color</label>
                      <input type="color" value={current.color || "#2563eb"} onChange={(e) => setProduct(pickIdx, { color: e.target.value })} />
                    </div>
                  </div>
                </SEBlock>

                {/* 2 · Lifecycle & visibility */}
                <SEBlock title="Lifecycle &amp; visibility" hint="status pill + who sees it">
                  <div className="form-grid-2">
                    <div className="form-row" style={{ margin: 0 }}>
                      <label className="lab">Status (the pill)</label>
                      <select value={current.status || ""} onChange={(e) => setProduct(pickIdx, { status: e.target.value })}>
                        {Object.entries(statuses).map(([key, def]) => <option key={key} value={key}>{def.label}</option>)}
                      </select>
                    </div>
                    <div className="form-row" style={{ margin: 0 }}>
                      <label className="lab">Publish state</label>
                      <select value={current.publishStatus || "published"} onChange={(e) => setProduct(pickIdx, { publishStatus: e.target.value })}>
                        {publishStatuses.map(o => <option key={o} value={o}>{o}</option>)}
                      </select>
                      <div className="hint">Visitors only see <em>published</em>. Drafts are private to admin.</div>
                    </div>
                  </div>
                </SEBlock>

                {/* 3 · Source (powers AI re-scan in Slice 3) */}
                <SEBlock title="Source" hint="folder path or URL — used by the AI re-scan">
                  <SEField label="Folder path (on this laptop)" value={current.sourcePath} onChange={(v) => setProduct(pickIdx, { sourcePath: v })} placeholder="C:/Projects/FinWise" />
                  <SEField label="Or URL (repo / live)" value={current.sourceUrl} onChange={(v) => setProduct(pickIdx, { sourceUrl: v })} placeholder="https://github.com/you/finwise" />
                  <div className="hint">When you click <strong>Re-scan with AI</strong> above, the engine reads this source, drafts updates, and shows a diff for you to approve. (Slice 3.)</div>
                </SEBlock>

                {/* 4 · Narrative */}
                <SEBlock title="Narrative" hint="hero · problem · workflow">
                  <SEField label="Hero (2–4 sentences)" value={current.hero} onChange={(v) => setProduct(pickIdx, { hero: v })} multiline rows={3} />
                  <SEField label="Business problem" value={current.businessProblem} onChange={(v) => setProduct(pickIdx, { businessProblem: v })} multiline rows={3} />
                  <SEField label="Operational workflow" value={current.operationalWorkflow} onChange={(v) => setProduct(pickIdx, { operationalWorkflow: v })} multiline rows={2} hint="Use → between steps." />
                </SEBlock>

                {/* 5 · Engineering goals + stack */}
                <SEBlock title="Engineering goals &amp; stack" hint="decisions + tools">
                  <SEList label="Engineering goals (one per line)" value={current.engineeringGoals} onChange={(v) => setProduct(pickIdx, { engineeringGoals: v })} />
                  <div className="form-row">
                    <label className="lab">Stack (comma-separated)</label>
                    <input value={(current.stack || []).join(", ")} onChange={(e) => setProduct(pickIdx, { stack: e.target.value.split(",").map(s => s.trim()).filter(Boolean) })} />
                  </div>
                </SEBlock>

                {/* 6 · Metrics */}
                <SEBlock title="Metrics" hint={`${(current.metrics || []).length} shown · keep to 3 max`}>
                  {(current.metrics || []).map((m, mi) => (
                    <div className="form-grid-3" key={mi} style={{ alignItems: "end", marginBottom: 8 }}>
                      <SEField label="Number" value={m.num} onChange={(v) => setMetric(pickIdx, mi, "num", v)} />
                      <SEField label="Label" value={m.lab} onChange={(v) => setMetric(pickIdx, mi, "lab", v)} />
                      <button className="btn btn-sm btn-ghost" onClick={() => rmMetric(pickIdx, mi)}><Icon name="trash" size={11}/></button>
                    </div>
                  ))}
                  <button className="btn btn-sm" onClick={() => addMetric(pickIdx)}><Icon name="plus" size={11}/> Add metric</button>
                </SEBlock>

                {/* 7 · Links */}
                <SEBlock title="Links" hint="github · demo · pdf">
                  <SEField label="GitHub URL" value={current.links?.github} onChange={(v) => setLinks(pickIdx, "github", v)} />
                  <SEField label="Demo URL" value={current.links?.demo} onChange={(v) => setLinks(pickIdx, "demo", v)} />
                  <SEField label="Architecture PDF URL" value={current.links?.pdf} onChange={(v) => setLinks(pickIdx, "pdf", v)} />
                </SEBlock>

                {/* 8 · Case study */}
                <SEBlock title="Case study" hint="long-form for the modal" defaultOpen={false}>
                  <SEField label="Problem" value={current.caseStudy?.problem} onChange={(v) => setCS(pickIdx, "problem", v)} multiline rows={2}/>
                  <SEField label="Workflow" value={current.caseStudy?.workflow} onChange={(v) => setCS(pickIdx, "workflow", v)} multiline rows={2}/>
                  <SEField label="Architecture" value={current.caseStudy?.architecture} onChange={(v) => setCS(pickIdx, "architecture", v)} multiline rows={3}/>
                  <SEField label="Implementation" value={current.caseStudy?.implementation} onChange={(v) => setCS(pickIdx, "implementation", v)} multiline rows={3}/>
                  <SEList label="Lessons (one per line)" value={current.caseStudy?.lessons} onChange={(v) => setCS(pickIdx, "lessons", v)}/>
                </SEBlock>

                {/* 9 · Workflow / Architecture / Implementation as REAL DIAGRAMS */}
                <SEBlock title="Diagrams" hint="workflow · architecture · implementation — boxes &amp; arrows">
                  <div className="hint" style={{ marginBottom: 8 }}>
                    Each row becomes a node in a left-to-right flow diagram (like the Showcase workflow). Empty = paragraph text from above.
                  </div>
                  {[
                    { key: "workflowSteps",       label: "Workflow steps" },
                    { key: "architectureSteps",   label: "Architecture steps" },
                    { key: "implementationSteps", label: "Implementation steps" },
                  ].map(({ key, label }) => (
                    <details key={key} className="admin-item" style={{ marginBottom: 10 }} open={(current[key] || []).length > 0}>
                      <summary style={{ cursor: "pointer", listStyle: "none", padding: "4px 0", fontSize: 12.5, fontWeight: 600 }}>
                        {label} <span style={{ color: "var(--text-3)", fontWeight: 400 }}>· {(current[key] || []).length} step{(current[key] || []).length === 1 ? "" : "s"}</span>
                      </summary>
                      <div style={{ marginTop: 8 }}>
                        {(current[key] || []).map((s, si) => (
                          <div key={si} className="form-grid-3" style={{ alignItems: "end", marginBottom: 6 }}>
                            <SEField label={`Step ${si + 1} label`} value={s.label} onChange={(v) => setStep(pickIdx, key, si, "label", v)} />
                            <SEField label="Sub-label" value={s.sub} onChange={(v) => setStep(pickIdx, key, si, "sub", v)} />
                            <button className="btn btn-sm btn-ghost" onClick={() => rmStep(pickIdx, key, si)}><Icon name="trash" size={11}/></button>
                          </div>
                        ))}
                        <button className="btn btn-sm" onClick={() => addStep(pickIdx, key)}><Icon name="plus" size={11}/> Add step</button>
                      </div>
                    </details>
                  ))}
                </SEBlock>

                {/* 10 · Per-project learning episodes — full parity with the
                    public Learning Portal cards (number, framework, thumb/video). */}
                <SEBlock title="Learning episodes" hint={`${(current.episodes || []).length} episode${(current.episodes || []).length === 1 ? "" : "s"} · matches the Learning Portal cards`} defaultOpen={false}>
                  {(current.episodes || []).map((e, ei) => (
                    <div key={ei} style={{ padding: "10px 12px", marginBottom: 10, border: "1px solid var(--border)", borderRadius: 8, background: "var(--bg-2)" }}>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                        <strong style={{ fontSize: 12.5 }}>Episode {ei + 1}</strong>
                        <button className="btn btn-sm btn-ghost" onClick={() => rmEpisode(pickIdx, ei)}><Icon name="trash" size={11}/></button>
                      </div>
                      <div className="form-grid-3">
                        <SEField label="Number (e.g. 01)" value={e.number} onChange={(v) => setEpisode(pickIdx, ei, "number", v)} placeholder="01" />
                        <SEField label="Duration" value={e.duration} onChange={(v) => setEpisode(pickIdx, ei, "duration", v)} placeholder="10 min read" />
                        <SEField label="Framework" value={e.framework} onChange={(v) => setEpisode(pickIdx, ei, "framework", v)} placeholder="e.g. Config-Driven Architecture" />
                      </div>
                      <SEField label="Title" value={e.title} onChange={(v) => setEpisode(pickIdx, ei, "title", v)} />
                      <SEField label="Summary" value={e.summary} onChange={(v) => setEpisode(pickIdx, ei, "summary", v)} multiline rows={3} />
                      <div className="form-row">
                        <label className="lab">Tags (comma-sep)</label>
                        <input value={(e.tags || []).join(", ")} onChange={(ev) => setEpisode(pickIdx, ei, "tags", ev.target.value.split(",").map(s => s.trim()).filter(Boolean))} placeholder="VBA, Architecture" />
                      </div>
                      <MediaPicker label="Thumbnail / video (image or video link)" value={e.thumb} onChange={(v) => setEpisode(pickIdx, ei, "thumb", v)} />
                    </div>
                  ))}
                  <button className="btn btn-sm" onClick={() => addEpisode(pickIdx)}><Icon name="plus" size={11}/> Add episode</button>
                </SEBlock>

                {/* 11 · Case study IMPACT row (was missing — the big metric row
                    that renders inside the case-study modal on the public site). */}
                <SEBlock title="Case-study impact" hint={`${(current.caseStudy?.impact || []).length} metrics — the big numbers row`} defaultOpen={false}>
                  {(current.caseStudy?.impact || []).map((m, ii) => (
                    <div className="form-grid-3" key={ii} style={{ alignItems: "end", marginBottom: 6 }}>
                      <SEField label="Number" value={m.num} onChange={(v) => setImpact(pickIdx, ii, "num", v)} placeholder="70%" />
                      <SEField label="Label" value={m.lab} onChange={(v) => setImpact(pickIdx, ii, "lab", v)} placeholder="Touchless invoices" />
                      <button className="btn btn-sm btn-ghost" onClick={() => rmImpact(pickIdx, ii)}><Icon name="trash" size={11}/></button>
                    </div>
                  ))}
                  <button className="btn btn-sm" onClick={() => addImpact(pickIdx)}><Icon name="plus" size={11}/> Add impact metric</button>
                </SEBlock>

                {/* 12 · Roadmap (was in schema, missing from editor). */}
                <SEBlock title="Roadmap" hint={`${(current.roadmap || []).length} phases — Now / Next / Later`} defaultOpen={false}>
                  {(current.roadmap || []).map((r, ri) => (
                    <div className="form-grid-3" key={ri} style={{ alignItems: "end", marginBottom: 6 }}>
                      <SEField label="Phase" value={r.phase} onChange={(v) => setRoadmap(pickIdx, ri, "phase", v)} placeholder="Now / Next / Later" />
                      <SEField label="Item" value={r.item} onChange={(v) => setRoadmap(pickIdx, ri, "item", v)} placeholder="What ships in this phase" />
                      <button className="btn btn-sm btn-ghost" onClick={() => rmRoadmap(pickIdx, ri)}><Icon name="trash" size={11}/></button>
                    </div>
                  ))}
                  <button className="btn btn-sm" onClick={() => addRoadmap(pickIdx)}><Icon name="plus" size={11}/> Add roadmap phase</button>
                </SEBlock>

                {/* 13 · Frameworks linked to this project (now editable rich-shape). */}
                <SEBlock title="Frameworks" hint={`${(current.frameworks || []).length} linked · powers the Frameworks section`} defaultOpen={false}>
                  {(current.frameworks || []).map((f, fi) => {
                    const fObj = typeof f === "string" ? { name: f } : (f || {});
                    return (
                      <div key={fi} style={{ padding: "10px 12px", marginBottom: 10, border: "1px solid var(--border)", borderRadius: 8, background: "var(--bg-2)" }}>
                        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                          <strong style={{ fontSize: 12.5 }}>Framework {fi + 1}</strong>
                          <button className="btn btn-sm btn-ghost" onClick={() => rmFw(pickIdx, fi)}><Icon name="trash" size={11}/></button>
                        </div>
                        <SEField label="Name" value={fObj.name} onChange={(v) => setFw(pickIdx, fi, "name", v)} />
                        <SEField label="Summary" value={fObj.summary} onChange={(v) => setFw(pickIdx, fi, "summary", v)} multiline rows={2} />
                        <div className="form-row">
                          <label className="lab">Pillars (one per line)</label>
                          <textarea rows={3} value={(fObj.pillars || []).join("\n")} onChange={(ev) => setFw(pickIdx, fi, "pillars", ev.target.value.split("\n").filter(Boolean))} />
                        </div>
                        <MediaPicker label="Poster (optional)" value={fObj.poster} onChange={(v) => setFw(pickIdx, fi, "poster", v)} />
                        <SEField label="Poster caption" value={fObj.posterCaption} onChange={(v) => setFw(pickIdx, fi, "posterCaption", v)} />
                      </div>
                    );
                  })}
                  <button className="btn btn-sm" onClick={() => addFw(pickIdx)}><Icon name="plus" size={11}/> Add framework</button>
                </SEBlock>

                {/* 14 · Per-project downloads. */}
                <SEBlock title="Downloads" hint={`${(current.downloads || []).length} assets · PDF / Code / Markdown / etc.`} defaultOpen={false}>
                  {(current.downloads || []).map((d, di) => (
                    <div key={di} style={{ padding: "10px 12px", marginBottom: 10, border: "1px solid var(--border)", borderRadius: 8, background: "var(--bg-2)" }}>
                      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                        <strong style={{ fontSize: 12.5 }}>Download {di + 1}</strong>
                        <button className="btn btn-sm btn-ghost" onClick={() => rmDownload(pickIdx, di)}><Icon name="trash" size={11}/></button>
                      </div>
                      <div className="form-grid-3">
                        <SEField label="Name" value={d.name} onChange={(v) => setDownload(pickIdx, di, "name", v)} />
                        <SEField label="Type" value={d.type} onChange={(v) => setDownload(pickIdx, di, "type", v)} placeholder="PDF / Code / Markdown" />
                        <SEField label="Size" value={d.size} onChange={(v) => setDownload(pickIdx, di, "size", v)} placeholder="640 KB" />
                      </div>
                      <SEField label="Description" value={d.description} onChange={(v) => setDownload(pickIdx, di, "description", v)} multiline rows={2} />
                      <SEField label="File URL or path" value={d.file} onChange={(v) => setDownload(pickIdx, di, "file", v)} placeholder="assets/your-file.pdf or https://..." />
                    </div>
                  ))}
                  <button className="btn btn-sm" onClick={() => addDownload(pickIdx)}><Icon name="plus" size={11}/> Add download</button>
                </SEBlock>

                {/* 15 · Screenshot / image (was missing — products use screenshotSlot
                    but had no upload affordance in the super-editor itself). */}
                <SEBlock title="Screenshot" hint="shown on the product card and modal" defaultOpen={false}>
                  <MediaPicker label="Product screenshot (≤1.5MB image, or paste a link)" value={current.image} onChange={(v) => setProduct(pickIdx, { image: v })} />
                  <SEField label="Image-slot ID (advanced)" value={current.screenshotSlot} onChange={(v) => setProduct(pickIdx, { screenshotSlot: v })} hint="Leave blank to auto-use the project slug." />
                </SEBlock>

                {/* 10 · Related across the site */}
                <SEBlock title="Related on the site" hint="things that already mention this project" defaultOpen={false}>
                  <div className="hint" style={{ marginBottom: 8 }}>
                    These are read-only summaries so you can see at a glance what else references this project; edit them in their own sections.
                  </div>
                  <div className="se-related">
                    <div><strong>Case studies:</strong> {related.caseStudies.map(c => c.name).join(", ") || <em>none</em>}</div>
                    <div><strong>Frameworks linked:</strong> {related.frameworks.map(f => f.name).join(", ") || <em>none</em>}</div>
                    <div><strong>Learning episodes:</strong> {related.episodes.map(e => e.title).join(", ") || <em>none</em>}</div>
                    <div><strong>Downloads:</strong> {related.downloads.map(d => d.name).join(", ") || <em>none</em>}</div>
                  </div>
                </SEBlock>
              </>
            )}
          </div>
        </div>
        </>
      )}
    </div>
  );
};

// ── Diff modal — per-field approve/skip after a re-scan ─────────────────────
const RescanDiff = ({ current, draft, log, fields, onApply, onClose }) => {
  const initial = {}; for (const f of fields) initial[f] = false;
  const [picks, setPicks] = useStateSE(initial);
  const allOn = () => { const p = {}; for (const f of fields) p[f] = changed(f); setPicks(p); };
  const changed = (f) => JSON.stringify(current[f] ?? null) !== JSON.stringify(draft[f] ?? null);
  const fmt = (v) => Array.isArray(v) ? (v.length ? "• " + v.join("\n• ") : "—") : (v && typeof v === "object" ? JSON.stringify(v, null, 2) : (v || "—"));

  const rows = fields.map(f => ({ f, on: changed(f) }));
  const changedCount = rows.filter(r => r.on).length;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 900, width: "94%" }}>
        <div style={{ padding: "18px 22px", borderBottom: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <div>
            <h3 style={{ margin: 0, fontSize: 16 }}>AI re-scan — review changes</h3>
            <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>
              {changedCount} field{changedCount === 1 ? "" : "s"} differ. Tick what to apply — your other edits stay untouched.
            </div>
          </div>
          <div style={{ display: "flex", gap: 6 }}>
            <button className="btn btn-sm" onClick={allOn}>Select all changed</button>
            <button className="btn btn-icon btn-ghost" onClick={onClose}><Icon name="close" size={14}/></button>
          </div>
        </div>
        <div style={{ padding: "10px 22px 22px", maxHeight: "70vh", overflowY: "auto" }}>
          {rows.map(({ f, on }) => (
            <div key={f} className="diff-row" data-changed={on ? "1" : "0"}>
              <label style={{ display: "flex", gap: 8, alignItems: "center", cursor: on ? "pointer" : "default", opacity: on ? 1 : 0.55 }}>
                <input type="checkbox" disabled={!on} checked={!!picks[f]} onChange={(e) => setPicks(p => ({ ...p, [f]: e.target.checked }))} />
                <strong style={{ fontSize: 13, textTransform: "capitalize" }}>{f.replace(/([A-Z])/g, " $1")}</strong>
                <span style={{ fontSize: 11, color: on ? "var(--accent)" : "var(--text-3)", fontFamily: "var(--mono)" }}>
                  {on ? "changed" : "no change"}
                </span>
              </label>
              {on && (
                <div className="diff-cols">
                  <div className="diff-col">
                    <div className="diff-tag">current</div>
                    <pre>{fmt(current[f])}</pre>
                  </div>
                  <div className="diff-col">
                    <div className="diff-tag diff-tag-new">AI draft</div>
                    <pre>{fmt(draft[f])}</pre>
                  </div>
                </div>
              )}
            </div>
          ))}
          {log && <details style={{ marginTop: 14 }}><summary style={{ fontSize: 11, color: "var(--text-3)", cursor: "pointer" }}>Scan log</summary><pre style={{ fontSize: 11, color: "var(--text-3)", whiteSpace: "pre-wrap" }}>{log}</pre></details>}
        </div>
        <div style={{ padding: "14px 22px", borderTop: "1px solid var(--border)", display: "flex", justifyContent: "flex-end", gap: 8 }}>
          <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
          <button className="btn btn-accent" onClick={() => onApply(picks)} disabled={Object.values(picks).every(v => !v)}>
            <Icon name="check" size={12}/> Apply selected
          </button>
        </div>
      </div>
    </div>
  );
};

// ── Bulk review — one screen for all NEW and UPDATE rows from a root scan ───
const BulkReview = ({ bulk, products, fields, onApply, onClose }) => {
  // picks: { [folder]: { apply: bool, fields: {field:bool} } }
  const initial = {};
  for (const it of bulk.items) {
    const has = !!it.draft;
    initial[it.folder] = { apply: has, fields: {} };
    if (has) for (const f of fields) {
      const cur = it.matched ? (products.find(p => p.id === it.matched.id) || {})[f] : undefined;
      const changed = JSON.stringify(cur ?? null) !== JSON.stringify(it.draft[f] ?? null);
      initial[it.folder].fields[f] = !it.matched ? true : changed; // NEW → take all; UPDATE → take only changed
    }
  }
  const [picks, setPicks] = useStateSE(initial);
  const togRow = (folder, on) => setPicks(p => ({ ...p, [folder]: { ...p[folder], apply: on } }));
  const togField = (folder, f, on) => setPicks(p => ({ ...p, [folder]: { ...p[folder], fields: { ...p[folder].fields, [f]: on } } }));
  const newCount = bulk.items.filter(it => it.draft && !it.matched).length;
  const updCount = bulk.items.filter(it => it.draft && it.matched).length;
  const errCount = bulk.items.filter(it => !it.draft).length;

  const fmt = (v) => Array.isArray(v) ? (v.length ? "• " + v.join("\n• ") : "—") : (v && typeof v === "object" ? JSON.stringify(v) : (v || "—"));

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 1000, width: "96%" }}>
        <div style={{ padding: "16px 22px", borderBottom: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <div>
            <h3 style={{ margin: 0, fontSize: 16 }}>Scan results — review &amp; import</h3>
            <div style={{ fontSize: 12, color: "var(--text-3)", marginTop: 3 }}>
              <strong>{newCount}</strong> new · <strong>{updCount}</strong> with updates · {errCount} skipped · root <code>{bulk.root}</code>
            </div>
          </div>
          <button className="btn btn-icon btn-ghost" onClick={onClose}><Icon name="close" size={14}/></button>
        </div>

        <div style={{ padding: "8px 22px 22px", maxHeight: "72vh", overflowY: "auto" }}>
          {bulk.items.map((it, i) => {
            const cur = it.matched ? (products.find(p => p.id === it.matched.id) || {}) : {};
            const status = !it.draft ? "skipped" : it.matched ? "update" : "new";
            const p = picks[it.folder] || { apply: false, fields: {} };
            return (
              <div key={i} className="bulk-row" data-status={status}>
                <div className="bulk-row-head">
                  <label style={{ display: "flex", alignItems: "center", gap: 8, cursor: it.draft ? "pointer" : "default" }}>
                    <input type="checkbox" disabled={!it.draft} checked={p.apply} onChange={(e) => togRow(it.folder, e.target.checked)} />
                    <span className={"bulk-tag bulk-tag-" + status}>{status === "new" ? "NEW" : status === "update" ? "UPDATE" : "SKIP"}</span>
                    <strong style={{ fontSize: 13 }}>{it.folder}</strong>
                    {it.matched && <span style={{ color: "var(--text-3)", fontSize: 12 }}>→ {it.matched.name}</span>}
                  </label>
                  {it.error && <span style={{ fontSize: 11, color: "var(--danger)" }}>{it.error.slice(0, 80)}</span>}
                </div>
                {p.apply && it.draft && (
                  <div className="bulk-row-body">
                    {fields.map(f => {
                      const changed = !it.matched || JSON.stringify(cur[f] ?? null) !== JSON.stringify(it.draft[f] ?? null);
                      if (it.matched && !changed) return null;   // UPDATE: only show changed fields
                      return (
                        <div key={f} className="diff-row" data-changed="1">
                          <label style={{ display: "flex", gap: 8, alignItems: "center", cursor: "pointer" }}>
                            <input type="checkbox" checked={!!p.fields[f]} onChange={(e) => togField(it.folder, f, e.target.checked)} />
                            <strong style={{ fontSize: 12, textTransform: "capitalize" }}>{f.replace(/([A-Z])/g, " $1")}</strong>
                            {!it.matched && <span style={{ fontSize: 10, color: "var(--accent)", fontFamily: "var(--mono)" }}>new</span>}
                          </label>
                          <div className="diff-cols">
                            {it.matched && (
                              <div className="diff-col">
                                <div className="diff-tag">current</div>
                                <pre>{fmt(cur[f])}</pre>
                              </div>
                            )}
                            <div className="diff-col">
                              <div className="diff-tag diff-tag-new">AI draft</div>
                              <pre>{fmt(it.draft[f])}</pre>
                            </div>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}
        </div>

        <div style={{ padding: "14px 22px", borderTop: "1px solid var(--border)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
          <div style={{ fontSize: 12, color: "var(--text-3)" }}>
            Nothing publishes yet — these become drafts/edits you can review in the picker before clicking Publish.
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
            <button className="btn btn-accent" onClick={() => onApply(picks)}>
              <Icon name="check" size={12}/> Import selected
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { ProjectSuperEditor, RescanDiff, BulkReview });
