// One-shot assembler that BUILT doctrine-nodes.{html,md} by inlining the five
// standalone doctrine nodes. HISTORICAL / cannot run as-is: those five source
// dirs (nodes/wishes, complaints, principles, metaphors, library-of-inspiration)
// were DELETED in the 2026-06-13 cutover — doctrine-nodes is now the source of
// truth, edited in place (add-box / ✕ / ✏️) not regenerated. Kept for provenance
// and as a recipe if the sources are ever restored from git to re-merge.
// node nodes/doctrine-nodes/assemble.mjs
import { readFile, writeFile } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const here = dirname(fileURLToPath(import.meta.url));
const root = join(here, '..', '..');

// order = tab order. label = what shows on the tab.
const PANELS = [
  { slug: 'library-of-inspiration', label: 'library' },
  { slug: 'principles',             label: 'principles' },
  { slug: 'complaints',             label: 'complaints' },
  { slug: 'wishes',                 label: 'wishes' },
  { slug: 'metaphors',              label: 'metaphors' },
];

// Pull the inner content of <main>, drop the node-header, the to-do add-box +
// its script, the footer, and the dn:add-here marker. Keep items/groups/viz/
// xblocks (and their slugs) intact so the viewer injects ✕/💬/✏️ live.
function extractBody(html) {
  let s = html;
  // main inner
  const mainOpen = s.indexOf('<main>');
  const mainClose = s.lastIndexOf('</main>');
  if (mainOpen !== -1 && mainClose !== -1) s = s.slice(mainOpen + '<main>'.length, mainClose);
  // node header
  s = s.replace(/<!--[^>]*NODE HEADER[^>]*-->/g, '');
  s = s.replace(/<header class="node-header">[\s\S]*?<\/header>/i, '');
  s = s.replace(/<!--[^>]*END NODE HEADER[^>]*-->/g, '');
  // to-do component (markered) + any stray to-do script
  s = s.replace(/<!--[^>]*to-do component[^>]*-->[\s\S]*?<!--[^>]*end to-do component[^>]*-->/gi, '');
  s = s.replace(/<div class="dn-todo"[\s\S]*?<\/div>\s*<script>[\s\S]*?<\/script>/gi, '');
  s = s.replace(/<div class="dn-todo"[\s\S]*?<\/div>\s*(?=<!--|<div|<section|<footer)/gi, '');
  // the add-here marker + the comment line above it
  s = s.replace(/<!--[^>]*add-item inserts[^>]*-->/gi, '');
  s = s.replace(/<!--\s*dn:add-here\s*-->/gi, '');
  // footer
  s = s.replace(/<footer>[\s\S]*?<\/footer>/gi, '');
  // any remaining standalone <script> (copy/post/✕) that slipped through main
  s = s.replace(/<script>[\s\S]*?<\/script>/gi, '');
  return s.trim();
}

const SHARED_STYLE = `
  /* doctrine template (scripts/templates/doctrine/) — the shared purple shell
     every non-library doctrine node already uses. The five shelves are inlined
     below as switchable panels; this node owns the chrome, the panels own the
     content. */
  :root{
    --ink:#0f0a1a;
    --bg:#fdf6e3;
    --paper:#fffaf0;
    --accent:#7c3aed;
    --dim:#6b5e7c;
    --line:#e8dcc4;
  }
  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  body{
    font-family:ui-rounded,-apple-system,system-ui,sans-serif;
    background:var(--bg);color:var(--ink);line-height:1.6;padding:48px 20px 80px;
  }
  main{max-width:760px;margin:0 auto}
  .node-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:18px}
  .pill{display:inline-block;background:var(--paper);border:2px solid var(--ink);border-radius:999px;padding:4px 12px;font-size:12px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:var(--ink)}
  .pill.tag{border-color:var(--dim);color:var(--dim);font-weight:600}
  .copybtn{font-family:inherit;cursor:pointer;border:2px solid var(--ink);background:var(--paper);border-radius:8px;padding:4px 10px;font-weight:700;font-size:12px;letter-spacing:0.04em;margin-left:auto}
  #copy-status{color:var(--accent);font-style:italic;font-size:13px}
  h1{font-size:34px;margin:0 0 6px;letter-spacing:-0.02em;line-height:1.15}
  .sub{color:var(--dim);font-size:16px;max-width:640px;margin:0 0 20px;font-style:italic}
  /* switcher */
  .dn-switch{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 22px;border-bottom:2px solid var(--line);padding-bottom:10px}
  .dn-switch button{font-family:inherit;cursor:pointer;background:var(--paper);color:var(--dim);border:2px solid var(--ink);border-radius:999px;padding:6px 16px;font-size:14px;font-weight:700;letter-spacing:0.02em}
  .dn-switch button:hover{color:var(--ink)}
  .dn-switch button[aria-selected="true"]{background:var(--accent);color:#fff;border-color:var(--accent)}
  .dn-panel[hidden]{display:none}
  .dn-panel>h1{font-size:26px;margin:6px 0 4px}
  /* per-shelf add-box (section-aware → POST /api/add-item) */
  .dn-todo{border:3px solid var(--accent);border-radius:12px;box-shadow:5px 5px 0 var(--accent);background:#faf5ff;padding:14px 18px;margin:4px 0 24px}
  .dn-todo-row{display:flex;gap:8px;margin:8px 0}
  .dn-todo-row input{flex:1;border:2px solid var(--ink);border-radius:10px;background:#fff;font-family:inherit;font-size:14px;padding:8px 12px}
  .dn-todo-row input:focus{outline:none;border-color:var(--accent)}
  .dn-todo-row button{font-family:inherit;cursor:pointer;border:2px solid var(--ink);background:var(--accent);color:#fff;border-radius:10px;padding:8px 16px;font-weight:800;font-size:14px;white-space:nowrap}
  .dn-todo-row button.dn-ghost{background:var(--paper);color:var(--ink)}
  .dn-todo-hint{font-size:11.5px;color:var(--dim);margin:6px 0 0}
  .dn-add-status{color:var(--accent);font-style:italic}
  /* doctrine content components (shared by every panel) */
  .dn-item{position:relative;background:var(--paper);border:2px solid var(--ink);border-radius:10px;padding:14px 18px;margin:12px 0;box-shadow:3px 3px 0 var(--line)}
  .dn-item h3{margin:0 0 4px;font-size:19px;letter-spacing:-0.01em}
  .dn-item .kind{font-size:11.5px;color:var(--accent);font-variant:small-caps;letter-spacing:0.1em;font-weight:700;text-transform:uppercase}
  .dn-item p{margin:8px 0 0;font-size:14.5px;line-height:1.55}
  .dn-item p:first-child{margin-top:0}
  .dn-item p code,.dn-item h3 code{font-family:'Courier New',monospace;font-size:13px;color:var(--accent)}
  .dn-item .src{font-size:13px;margin-top:10px}
  .dn-meta{font-size:11.5px;color:var(--dim);margin-top:8px}
  .dn-group{position:relative;border:2px dashed var(--accent);border-radius:12px;padding:12px 16px;margin:16px 0;background:rgba(124,58,237,0.03)}
  .dn-group>h3{margin:0 0 4px;font-size:15px;color:var(--accent);letter-spacing:0.02em}
  .dn-viz{position:relative;border:2px solid var(--ink);border-radius:12px;background:var(--paper);box-shadow:5px 5px 0 var(--ink);padding:10px;margin:16px 0}
  .dn-viz svg{display:block;width:100%;height:auto}
  [data-xcomponent]{position:relative}
  footer{margin-top:36px;color:var(--dim);font-size:12px;border-top:1px dashed var(--line);padding-top:12px}
  footer a{color:var(--accent)}
  footer code{font-family:'Courier New',monospace;font-size:11px}`;

// per-shelf add-box + its section marker, injected right after the panel's sub
// paragraph. One `<!-- dn:add-here:<slug> -->` marker per panel so the
// section-aware /api/add-item lands a typed item in the right shelf.
function addBox(slug, label) {
  return `
<div class="dn-todo" data-component="${slug}-todo">
  <div class="dn-todo-row">
    <input id="add-${slug}" type="text" placeholder="add to ${label} — raw is fine">
    <button type="button" data-add="${slug}" data-kind="item">send</button>
  </div>
  <div class="dn-todo-row">
    <input id="addgrp-${slug}" type="text" placeholder="name a group…">
    <button type="button" class="dn-ghost" data-add="${slug}" data-kind="group">make group</button>
  </div>
  <p class="dn-todo-hint">lands in this shelf immediately (and in <code>history.jsonl</code>) <span class="dn-add-status" data-status="${slug}"></span></p>
</div>
<!-- dn:add-here:${slug} -->`;
}
function injectAddBox(body, slug, label) {
  const box = addBox(slug, label);
  // after the first sub paragraph; fall back to after the first h1; else prepend.
  if (/<p class="sub">[\s\S]*?<\/p>/.test(body)) return body.replace(/(<p class="sub">[\s\S]*?<\/p>)/, `$1\n${box}\n`);
  if (/<h1>[\s\S]*?<\/h1>/.test(body)) return body.replace(/(<h1>[\s\S]*?<\/h1>)/, `$1\n${box}\n`);
  return `${box}\n${body}`;
}

const panelsHtml = [];
const mdParts = [];
for (const { slug, label } of PANELS) {
  const html = await readFile(join(root, 'nodes', slug, `${slug}.html`), 'utf8');
  const md = await readFile(join(root, 'nodes', slug, `${slug}.md`), 'utf8');
  const body = injectAddBox(extractBody(html), slug, label);
  const hidden = slug === PANELS[0].slug ? '' : ' hidden';
  panelsHtml.push(`<section class="dn-panel" id="panel-${slug}" data-slug="${slug}"${hidden}>\n${body}\n</section>`);
  mdParts.push(`\n---\n\n# ${slug}\n\n${md.trim()}\n`);
}

const tabs = PANELS.map((p, i) =>
  `  <button role="tab" data-slug="${p.slug}" aria-selected="${i === 0 ? 'true' : 'false'}">${p.label}</button>`
).join('\n');

const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>doctrine-nodes</title>
<style>${SHARED_STYLE}
</style>
</head>
<body>
<main>

<!-- ======== NODE HEADER ======== -->
<header class="node-header">
  <span class="pill" style="border-color:var(--accent);color:var(--accent)">doctrine</span>
  <span class="pill">node · 2026-06-12</span>
  <span class="pill tag">doctrine</span>
  <span class="pill tag">aggregator</span>
  <button class="copybtn" onclick="copyNodeContext()" title="copy context" aria-label="copy context">copy context</button>
  <span id="copy-status"></span>
  <script>
  const NODE_PATHS = [
    "nodes/doctrine-nodes/doctrine-nodes.html",
    "nodes/doctrine-nodes/doctrine-nodes.md"
  ];
  function copyNodeContext() {
    const status = document.getElementById("copy-status");
    navigator.clipboard.writeText(NODE_PATHS.join("\\n"));
    status.textContent = "copied paths";
    setTimeout(() => { status.textContent = ""; }, 2600);
  }
  </script>
</header>
<!-- ======== END NODE HEADER ======== -->

<h1>Doctrine Nodes</h1>
<p class="sub">The whole L3 doctrine layer behind one switcher. Each shelf is folded in here — one node, five surfaces.</p>

<nav class="dn-switch" role="tablist" aria-label="doctrine shelves">
${tabs}
</nav>

${panelsHtml.join('\n\n')}

<footer>
  <a href="./doctrine-nodes.md">doctrine-nodes.md</a> · the five doctrine shelves folded into one node · this surface replaces the five standalone doctrine nodes
</footer>

</main>

<script>
(function(){
  var tabs = Array.prototype.slice.call(document.querySelectorAll('.dn-switch button'));
  var panels = Array.prototype.slice.call(document.querySelectorAll('.dn-panel'));
  function show(slug){
    tabs.forEach(function(t){ t.setAttribute('aria-selected', t.getAttribute('data-slug') === slug ? 'true' : 'false'); });
    panels.forEach(function(p){ p.hidden = p.getAttribute('data-slug') !== slug; });
    try { sessionStorage.setItem('doctrine-nodes:slug', slug); } catch(_){}
  }
  tabs.forEach(function(t){ t.addEventListener('click', function(){ show(t.getAttribute('data-slug')); }); });
  try { var saved = sessionStorage.getItem('doctrine-nodes:slug'); if (saved && document.getElementById('panel-' + saved)) show(saved); } catch(_){}
})();

// section-aware add-boxes: every [data-add] button POSTs to /api/add-item with
// its shelf slug as the section, so the typed item lands in that panel.
(function(){
  function post(slug, kind){
    var input = document.getElementById((kind === 'group' ? 'addgrp-' : 'add-') + slug);
    if (!input) return;
    var v = input.value.trim(); if (!v) return;
    var status = document.querySelector('[data-status="' + slug + '"]');
    if (status) status.textContent = 'adding…';
    fetch('/api/add-item', {
      method: 'POST', headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ path: 'nodes/doctrine-nodes/doctrine-nodes.html', section: slug, kind: kind, text: v })
    }).then(function(r){ return r.json().then(function(j){ if (!r.ok) throw new Error(j.error || ('HTTP ' + r.status)); return j; }); })
      .then(function(j){ if (window.parent !== window) window.parent.postMessage({ type: 'sl-refresh-node', highlight: j && j.id }, '*'); else location.reload(); })
      .catch(function(e){ if (status) status.textContent = 'failed — ' + (e.message || e); });
  }
  document.querySelectorAll('[data-add]').forEach(function(b){
    b.addEventListener('click', function(){ post(b.getAttribute('data-add'), b.getAttribute('data-kind')); });
  });
  document.querySelectorAll('.dn-todo input').forEach(function(inp){
    inp.addEventListener('keydown', function(e){ if (e.key === 'Enter'){ e.preventDefault(); var btn = inp.parentNode.querySelector('[data-add]'); if (btn) btn.click(); } });
  });
})();
</script>

<!-- BLOCK-START: x-delete -->
<!-- ONE shared ✕ for every [data-xcomponent] across all five shelves; baked copy
     keeps md+html PAIRED deletion working outside the viewer (drain-verify). -->
<style>
[data-xcomponent]{position:relative}
.blk-blockx{position:absolute;top:6px;right:6px;cursor:pointer;border:none;background:none;color:var(--dim,#6b6259);font-size:13px;font-weight:700;padding:0 4px;border-radius:4px;opacity:.3;font-family:inherit}
[data-xcomponent]:hover .blk-blockx{opacity:1;color:#b42318}
</style>
<script>
(function () {
  var NODE_PATH = 'nodes/doctrine-nodes';
  function makeBtn(slug, cls, onGone) {
    var btn = document.createElement('button');
    btn.className = cls; btn.textContent = '✕';
    btn.title = 'delete ' + slug + ' from both surfaces — gone for good';
    btn.onclick = function () {
      btn.disabled = true;
      fetch('/api/row-delete', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ path: NODE_PATH, slug: slug }) })
        .then(function (r) { if (!r.ok) throw new Error('row-delete failed'); onGone(); })
        .catch(function () { btn.disabled = false; btn.textContent = '!'; btn.title = 'delete failed — is the local viewer running?'; });
    };
    return btn;
  }
  document.querySelectorAll('[data-xcomponent]').forEach(function (el) {
    el.appendChild(makeBtn(el.getAttribute('data-xcomponent'), 'blk-blockx', function () { el.remove(); }));
  });
})();
</script>
<!-- BLOCK-END: x-delete -->

</body>
</html>
`;

const md = `---
title: Doctrine Nodes
kind: aggregator
tags: [doctrine, aggregator, pace-layer-3]
created: 2026-06-12
---

# Doctrine Nodes

The whole L3 doctrine layer folded into ONE node. The html surface carries a
switcher header — \`library | principles | complaints | wishes | metaphors\` —
and the five shelves are inlined as panels (no iframes, no transclusion). This
node is meant to REPLACE the five standalone doctrine nodes.

The five shelves, in tab order, follow verbatim from each source node's md.
${mdParts.join('')}`;

await writeFile(join(here, 'doctrine-nodes.html'), html);
await writeFile(join(here, 'doctrine-nodes.md'), md);
console.log('wrote doctrine-nodes.html (%d bytes) and doctrine-nodes.md (%d bytes)', html.length, md.length);
