/**
 * Render bundled docs for the install server.
 *
 * Supports three output modes:
 *
 * - `text/html`  — full standalone page with site-wide noindex meta and a
 *   canonical link back to the marketing SPA. The HTML shell, base tokenised
 *   styles, and palette are sourced from
 *   {@link ../bundled_pages/html_shell.ts | html_shell.ts} and
 *   {@link ../bundled_pages/tokens.ts | tokens.ts}, the same modules
 *   `src/services/root_landing/html_template.ts` consumes — code coupling, not
 *   comment coupling.
 * - `text/markdown` — raw body (no front-matter, no decoration). Lets agents
 *   pipe a doc straight into another tool.
 * - `application/json` — `{ frontmatter, body, slug, canonical }` for
 *   programmatic consumers.
 *
 * Pages flagged `interactive: true` in front-matter render an
 * "open the canonical page" notice instead of trying to render MDX-only
 * components from the install runtime.
 */
import { renderMarkdownBodyToHtml } from "../docs_bundle/render_html.js";
import { BUNDLED_PAGE_PALETTE as P, BUNDLED_PAGE_PROSE as PROSE, } from "../bundled_pages/tokens.js";
import { renderBundledBanner, renderBundledBannerStyles, renderBundledPageShell, } from "../bundled_pages/html_shell.js";
function escapeHtml(value) {
    return value
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
}
function buildCanonicalUrl(doc, ctx) {
    if (doc.frontmatter.canonical_override) {
        return doc.frontmatter.canonical_override;
    }
    const base = ctx.publicDocsUrl.replace(/\/+$/, "");
    return `${base}/${doc.slug}`;
}
/**
 * Surface-specific extra `<head>` content (description, keywords, canonical
 * link, alternate-format link). The shell already emits charset, viewport,
 * title, robots, and favicon — see
 * {@link ../bundled_pages/html_shell.ts | renderBundledPageShell}.
 */
function buildExtraHead(doc, ctx) {
    const canonical = buildCanonicalUrl(doc, ctx);
    const keywords = doc.frontmatter.keywords.join(", ");
    return [
        `<meta name="description" content="${escapeHtml(doc.frontmatter.description)}">`,
        keywords ? `<meta name="keywords" content="${escapeHtml(keywords)}">` : "",
        `<link rel="canonical" href="${escapeHtml(canonical)}">`,
        `<link rel="alternate" type="text/markdown" href="?format=md">`,
    ]
        .filter(Boolean)
        .join("\n    ");
}
/**
 * Surface-specific styles for the docs page. Color literals are sourced from
 * {@link ../bundled_pages/tokens.ts | BUNDLED_PAGE_PALETTE} (aliased as `P`)
 * so the install docs and root landing share one canonical palette. The base
 * style block (CSS variables, body typography, default link color) is emitted
 * by {@link ../bundled_pages/html_shell.ts | renderBundledPageShell}.
 */
function buildExtraStyles() {
    const H = PROSE.heading;
    const LIST = PROSE.list;
    const CODE = PROSE.code;
    const QUOTE = PROSE.blockquote;
    return `
:root {
  --bg: ${P.background.light.hex};
  --fg: ${P.foreground.light.hex};
  --muted: ${P.muted_foreground.light.hex};
  --border: ${P.border.light.hex};
  --code-bg: ${P.muted.light.hex};
  --link: ${P.primary.light.hex};
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: ${P.background.dark.hex};
    --fg: ${P.foreground.dark.hex};
    --muted: ${P.muted_foreground.dark.hex};
    --border: ${P.border.dark.hex};
    --code-bg: ${P.muted.dark.hex};
    --link: ${P.primary.dark.hex};
  }
}
body {
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.55 ui-sans-serif, -apple-system, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
}
.doc-wrap {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 24px 96px 24px;
}
.doc-breadcrumb {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 24px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.doc-breadcrumb a {
  color: var(--muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--muted);
}
.doc-breadcrumb a:hover { color: var(--fg); }
.doc-wrap > .banner { margin-bottom: 24px; }
${renderBundledBannerStyles()}
h1 { font-size: ${H.h1.fontSize}; margin: 0 0 ${H.h1.marginBottom} 0; line-height: ${H.h1.lineHeight}; }
h2 { font-size: ${H.h2.fontSize}; margin: ${H.h2.marginTop} 0 ${H.h2.marginBottom} 0; line-height: ${H.h2.lineHeight}; }
h3 { font-size: ${H.h3.fontSize}; margin: ${H.h3.marginTop} 0 ${H.h3.marginBottom} 0; line-height: ${H.h3.lineHeight}; }
h4 { font-size: ${H.h4.fontSize}; margin: ${H.h4.marginTop} 0 ${H.h4.marginBottom} 0; line-height: ${H.h4.lineHeight}; }
p, ul, ol, blockquote, table { margin: 0 0 ${PROSE.paragraph.marginBottom} 0; }
ul, ol { padding-left: ${LIST.paddingLeft}; }
li + li { margin-top: ${LIST.itemGap}; }
a { color: var(--link); }
code {
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: var(--code-bg);
  padding: ${CODE.inlinePadding};
  border-radius: ${CODE.inlineRadius};
}
pre {
  background: var(--code-bg);
  border: 1px solid var(--border);
  border-radius: ${CODE.blockRadius};
  padding: ${CODE.blockPadding};
  overflow-x: auto;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}
pre code { background: transparent; padding: 0; border-radius: 0; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }
th { background: var(--code-bg); }
blockquote {
  border-left: ${QUOTE.borderLeftWidth} solid var(--border);
  padding-left: ${QUOTE.paddingLeft};
  color: var(--muted);
}
hr { border: 0; border-top: 1px solid var(--border); margin: ${PROSE.hr.margin}; }
.doc-anchor {
  text-decoration: none;
  color: var(--muted);
  margin-right: 4px;
  opacity: 0;
  transition: opacity 120ms;
}
h1:hover .doc-anchor, h2:hover .doc-anchor, h3:hover .doc-anchor,
h4:hover .doc-anchor, h5:hover .doc-anchor, h6:hover .doc-anchor { opacity: 0.6; }
.doc-meta {
  color: var(--muted);
  font-size: 13px;
  margin-bottom: 28px;
}
`;
}
/**
 * MDX bodies can contain JSX tags from the SPA-side allow-list (see
 * `frontend/src/components/docs/mdx_components.tsx`). The install renderer
 * does not evaluate MDX (the install bundle does not ship React). We strip
 * the JSX tags before passing the body to `marked` so the prose renders
 * cleanly. Pages that depend on dynamic components must be marked
 * `interactive: true` in front-matter — the install then emits the
 * canonical-link stub and the (stripped) prose as a static fallback.
 *
 * The set of recognised tags is the SPA allow-list. Unknown tags are also
 * stripped (we err on the side of dropping rather than rendering broken
 * HTML); the front-matter test suite enforces that authored MDX uses only
 * components from the allow-list.
 */
function stripMdxJsxFromBody(body) {
    // Replace <Tag .../> (self-closing) and <Tag>...</Tag> (paired) with the
    // contents (paired) or empty string (self-closing). Components from the
    // allow-list start with an uppercase ASCII letter — the JSX rule.
    // Non-greedy paired-tag match handles nested children; we run two passes
    // to catch nested same-name tags.
    let result = body;
    for (let pass = 0; pass < 3; pass += 1) {
        const next = result
            .replace(/<([A-Z][A-Za-z0-9]*)\b[^>]*\/>/g, "")
            .replace(/<([A-Z][A-Za-z0-9]*)\b[^>]*>([\s\S]*?)<\/\1>/g, "$2");
        if (next === result)
            break;
        result = next;
    }
    return result;
}
function renderInteractiveFallback(_doc, canonical) {
    return renderBundledBanner({
        variant: "warning",
        title: "This page contains interactive components",
        body: `that the bundled install renderer does not support. Open the canonical page on
  <a href="${escapeHtml(canonical)}">${escapeHtml(canonical)}</a> for the full
  experience, or request <code>?format=md</code> to read the markdown source
  directly.`,
    });
}
/**
 * Render a `DocSource` as a complete HTML document suitable for serving from
 * the install at `/docs/<slug>`.
 */
export function renderDocAsHtml(doc, ctx) {
    const canonical = buildCanonicalUrl(doc, ctx);
    const inspectorLink = ctx.inspectorUrl
        ? `<a href="${escapeHtml(ctx.inspectorUrl)}">Inspector</a>`
        : "";
    const breadcrumb = `<nav class="doc-breadcrumb" aria-label="breadcrumb">
  <a href="/">Neotoma</a> /
  <a href="/docs">Docs</a> /
  <span>${escapeHtml(doc.frontmatter.title)}</span>
  ${inspectorLink ? `· ${inspectorLink}` : ""}
</nav>`;
    const noIndexNotice = renderBundledBanner({
        variant: "info",
        body: `Bundled with this Neotoma install. Canonical version:
  <a href="${escapeHtml(canonical)}">${escapeHtml(canonical)}</a>.`,
    });
    const safeBody = doc.extension === "mdx" ? stripMdxJsxFromBody(doc.body) : doc.body;
    const body = doc.frontmatter.interactive && doc.extension === "mdx"
        ? renderInteractiveFallback(doc, canonical) + renderMarkdownBodyToHtml(safeBody)
        : renderMarkdownBodyToHtml(safeBody);
    const meta = `<p class="doc-meta">Last updated ${escapeHtml(doc.frontmatter.last_updated)} · category: ${escapeHtml(doc.frontmatter.nav_category)}</p>`;
    const main = `<main class="doc-wrap">
    ${breadcrumb}
    ${noIndexNotice}
    ${meta}
    ${body}
  </main>`;
    return renderBundledPageShell({
        title: `${doc.frontmatter.title} · Neotoma docs`,
        robots: "noindex,nofollow",
        extraHead: buildExtraHead(doc, ctx),
        extraStyles: buildExtraStyles(),
        tokensToExpose: [],
    }, main);
}
/**
 * Render an index page listing every bundled doc, grouped by `nav_category`.
 */
export function renderDocIndexHtml(docs, ctx) {
    const grouped = new Map();
    for (const doc of docs) {
        const key = doc.frontmatter.nav_category;
        if (!grouped.has(key))
            grouped.set(key, []);
        grouped.get(key).push(doc);
    }
    const sections = [];
    for (const [category, group] of grouped) {
        const items = group
            .map((d) => {
            const canonical = buildCanonicalUrl(d, ctx);
            return `<li>
        <a href="/docs/${escapeHtml(d.slug)}">${escapeHtml(d.frontmatter.title)}</a>
        <span class="doc-meta">— ${escapeHtml(d.frontmatter.description)}
          (canonical: <a href="${escapeHtml(canonical)}">${escapeHtml(canonical)}</a>)</span>
      </li>`;
        })
            .join("\n      ");
        sections.push(`<section>
      <h2>${escapeHtml(category)}</h2>
      <ul>
      ${items}
      </ul>
    </section>`);
    }
    const extraHead = [
        `<meta name="description" content="Bundled product documentation served by this Neotoma install.">`,
        `<link rel="canonical" href="${escapeHtml(ctx.publicDocsUrl.replace(/\/+$/, ""))}/docs">`,
    ].join("\n    ");
    const main = `<main class="doc-wrap">
    <nav class="doc-breadcrumb" aria-label="breadcrumb">
      <a href="/">Neotoma</a> / <span>Docs</span>
    </nav>
    ${renderBundledBanner({
        variant: "info",
        body: `These docs are bundled with this install (noindex). Canonical version:
      <a href="${escapeHtml(ctx.publicDocsUrl.replace(/\/+$/, ""))}/">${escapeHtml(ctx.publicDocsUrl.replace(/\/+$/, ""))}/</a>.`,
    })}
    <h1>Bundled product docs</h1>
    <p>${docs.length} page(s) shipped with this Neotoma install.</p>
    ${sections.join("\n    ")}
  </main>`;
    return renderBundledPageShell({
        title: "Docs · Neotoma install",
        robots: "noindex,nofollow",
        extraHead,
        extraStyles: buildExtraStyles(),
        tokensToExpose: [],
    }, main);
}
export function renderDocAsJson(doc, ctx) {
    return {
        slug: doc.slug,
        extension: doc.extension,
        frontmatter: doc.frontmatter,
        canonical_url: buildCanonicalUrl(doc, ctx),
        body: doc.body,
    };
}
export function renderDocIndexJson(docs, ctx) {
    return {
        canonical_index_url: `${ctx.publicDocsUrl.replace(/\/+$/, "")}/`,
        count: docs.length,
        items: docs.map((d) => ({
            slug: d.slug,
            title: d.frontmatter.title,
            description: d.frontmatter.description,
            nav_category: d.frontmatter.nav_category,
            last_updated: d.frontmatter.last_updated,
            canonical_url: buildCanonicalUrl(d, ctx),
            install_url: `/docs/${d.slug}`,
        })),
    };
}
//# sourceMappingURL=renderer.js.map