/**
 * Document → clean Markdown serialization for the AI/agent layer.
 *
 * Powers per-page `.md` output and `/llms-full.txt`. The goal is a faithful,
 * readable rendering of the *content* (so an agent can fetch the page body, not
 * just the link) — never the chrome, settings, ids, or URLs that live alongside
 * it in the document model.
 *
 * HTML/rich-text slots are converted with Turndown; structured section content
 * is walked generically (allow-listed prose/heading keys only) so new section
 * types render acceptably without per-type code. Output is always `noindex`-only
 * — callers must serve it with `X-Robots-Tag: noindex` and keep it out of the
 * sitemap (the HTML page stays canonical).
 */
import TurndownService from 'turndown';
import { getSectionType } from '../sections/registry.js';
let _td = null;
function turndown() {
    if (!_td) {
        _td = new TurndownService({
            headingStyle: 'atx',
            codeBlockStyle: 'fenced',
            bulletListMarker: '-',
            emDelimiter: '_',
        });
    }
    return _td;
}
const HTML_LIKE = /<[a-z!/][\s\S]*>/i;
/** Convert an HTML/rich-text fragment to Markdown. Plain text passes through. */
export function htmlToMarkdown(html) {
    if (!html)
        return '';
    const s = String(html);
    if (!HTML_LIKE.test(s))
        return s.replace(/\s+/g, ' ').trim();
    try {
        return turndown().turndown(s).trim();
    }
    catch {
        // Defensive: never throw from a public serializer — degrade to stripped text.
        return s
            .replace(/<[^>]+>/g, ' ')
            .replace(/\s+/g, ' ')
            .trim();
    }
}
// Keys whose string values are prose paragraphs.
const PROSE_KEYS = new Set([
    'body',
    'content',
    'html',
    'text',
    'copy',
    'description',
    'answer',
    'quote',
    'excerpt',
]);
// Keys whose string values are headings/sub-headings within a section.
const HEADING_KEYS = new Set([
    'heading',
    'title',
    'subtitle',
    'subheading',
    'eyebrow',
    'question',
    'label',
]);
const MAX_DEPTH = 8;
/**
 * Render a published document to clean Markdown:
 *   `# Title`, an optional one-line takeaway, then the body.
 */
export function documentToMarkdown(doc) {
    const data = (doc.data ?? {});
    const out = [];
    const title = firstString(data.metaTitle, doc.title, data.title) ?? 'Untitled';
    out.push(`# ${oneLine(title)}`, '');
    const intro = firstString(data.keyTakeaway, data.metaDescription, data.seoDescription);
    if (intro) {
        out.push(oneLine(intro), '');
    }
    const body = buildBody(data);
    if (body) {
        out.push(body);
    }
    else if (doc.plainText) {
        out.push(String(doc.plainText).trim());
    }
    return collapseBlankLines(out.join('\n')).trim() + '\n';
}
function buildBody(data) {
    const parts = [];
    const sections = Array.isArray(data.sections) ? data.sections : null;
    if (sections) {
        for (const s of sections) {
            collectFromSection(s, parts);
        }
    }
    if (parts.length === 0 && data._layout && typeof data._layout === 'object') {
        collectFromTree(data._layout, parts, 0);
    }
    if (parts.length === 0) {
        const html = firstString(data.body, data.content, data.html);
        if (html)
            parts.push(htmlToMarkdown(html));
    }
    return parts
        .map((p) => p.trim())
        .filter(Boolean)
        .join('\n\n')
        .trim();
}
function collectFromSection(section, parts) {
    if (!isRecord(section))
        return;
    if (section.visible === false)
        return;
    const content = section.content;
    if (!isRecord(content))
        return;
    // Resolve which slots hold rich text (HTML) from the section-type registry,
    // so we convert those with Turndown rather than treating them as plain text.
    const richKeys = new Set();
    const typeId = typeof section.sectionType === 'string' ? section.sectionType : '';
    const def = typeId ? getSectionType(typeId) : undefined;
    for (const field of def?.fields ?? []) {
        if (field?.type === 'richText' && typeof field.key === 'string')
            richKeys.add(field.key);
    }
    walkContent(content, richKeys, parts, 0);
}
function collectFromTree(node, parts, depth) {
    if (depth > MAX_DEPTH || !isRecord(node))
        return;
    if (isRecord(node.data)) {
        // Block data carries author content (richText body, headings, etc.).
        walkContent(node.data, new Set(), parts, 0);
    }
    if (Array.isArray(node.children)) {
        for (const child of node.children)
            collectFromTree(child, parts, depth + 1);
    }
}
function walkContent(obj, richKeys, parts, depth) {
    if (depth > MAX_DEPTH)
        return;
    for (const [key, value] of Object.entries(obj)) {
        if (typeof value === 'string') {
            const trimmed = value.trim();
            if (!trimmed)
                continue;
            if (richKeys.has(key) || HTML_LIKE.test(trimmed)) {
                const md = htmlToMarkdown(trimmed);
                if (md)
                    parts.push(md);
            }
            else if (HEADING_KEYS.has(key)) {
                parts.push(`## ${oneLine(trimmed)}`);
            }
            else if (PROSE_KEYS.has(key)) {
                parts.push(oneLine(trimmed));
            }
        }
        else if (Array.isArray(value)) {
            for (const item of value) {
                if (isRecord(item))
                    walkContent(item, richKeys, parts, depth + 1);
            }
        }
        else if (isRecord(value)) {
            walkContent(value, richKeys, parts, depth + 1);
        }
    }
}
function isRecord(v) {
    return typeof v === 'object' && v !== null && !Array.isArray(v);
}
function firstString(...candidates) {
    for (const c of candidates) {
        if (typeof c === 'string' && c.trim())
            return c;
    }
    return undefined;
}
function oneLine(s) {
    return s.replace(/\s+/g, ' ').trim();
}
function collapseBlankLines(s) {
    return s.replace(/\n{3,}/g, '\n\n');
}
//# sourceMappingURL=content-markdown.js.map