/**
 * Bridge: page-builder tree (Model B, `data._layout`) → canonical flat
 * sections (Model A, `data.sections`).
 *
 * ADR 0001 makes the flat `PageSection[]` array the single public content
 * contract and reframes the recursive page-builder `BlockNode` tree as an
 * optional, admin-only authoring layer. Documents that were authored only in
 * the tree must still render through the canonical `<Sections>` renderer, so
 * this module performs a best-effort, **never-throwing** conversion. No
 * page-builder content is stranded (roadmap WS-A5).
 *
 * Fidelity notes:
 *   - The tree's nesting (page → section → container/row/column → block) is
 *     flattened to an ordered list of leaf blocks in document order. Each block
 *     becomes one canonical section.
 *   - Each block's `settings.blockType` is mapped to the closest canonical
 *     section type. Unknown/complex block types degrade to a `text` section
 *     that surfaces whatever string content the block carried, so content is
 *     shown rather than silently dropped.
 *   - The enclosing builder section's `htmlId` (anchor) and hidden visibility
 *     are propagated. Free-form CSS settings (arbitrary background colors,
 *     pixel paddings) are intentionally NOT mapped — they don't translate to
 *     the canonical token-based settings and would produce wrong styling.
 *   - `savedSectionRef` nodes reference a separate saved-section document and
 *     can't be resolved without a DB lookup; they are skipped here. Callers
 *     that need them must expand refs before calling this function.
 *
 * This is intentionally DB-free and pure so it runs at read time (the resolve
 * endpoint), in a one-off migration script, or in tests identically.
 */
import { generateSectionId } from './helpers.js';
import { SECTION_TYPES } from './registry.js';
// ─── Defensive readers (the tree arrives as untrusted JSON) ──────────
function isObj(v) {
    return !!v && typeof v === 'object' && !Array.isArray(v);
}
function str(v) {
    return typeof v === 'string' ? v : '';
}
/** Extract a URL from a media value that may be a string, `{ url }`, or an id. */
function mediaUrl(v) {
    if (typeof v === 'string')
        return v;
    if (isObj(v) && typeof v.url === 'string')
        return v.url;
    return '';
}
/** Drop empty-string / null / undefined values so defaults aren't clobbered with blanks. */
function compact(obj) {
    const out = {};
    for (const [k, val] of Object.entries(obj)) {
        if (val === '' || val === null || val === undefined)
            continue;
        out[k] = val;
    }
    return out;
}
/**
 * Generic fallback: surface any readable text the block carried as a `text`
 * section so content is never lost when a block type has no clean canonical
 * equivalent (cards, faq, video, form, code, custom blocks…).
 */
function fallbackTextSection(blockType, data) {
    const parts = [];
    const visit = (value) => {
        if (typeof value === 'string') {
            const t = value.trim();
            if (t)
                parts.push(t);
        }
        else if (Array.isArray(value)) {
            for (const item of value)
                visit(item);
        }
        else if (isObj(value)) {
            for (const v of Object.values(value))
                visit(v);
        }
    };
    visit(data);
    return {
        type: 'text',
        name: SECTION_TYPES.text.name,
        content: compact({ heading: '', body: parts.join('\n\n') }),
    };
}
const BLOCK_MAPPERS = {
    hero: (d) => ({
        type: 'hero',
        content: compact({
            heading: str(d.title),
            body: str(d.subtitle),
            primaryButtonLabel: str(d.ctaText),
            primaryButtonUrl: str(d.ctaLink),
        }),
    }),
    text: (d) => ({
        type: 'text',
        content: compact({ heading: str(d.heading), body: str(d.body) }),
    }),
    cta: (d) => ({
        type: 'cta',
        content: compact({
            heading: str(d.heading),
            body: str(d.body),
            buttonLabel: str(d.buttonText),
            buttonUrl: str(d.buttonLink),
        }),
    }),
    image: (d) => ({
        type: 'feature',
        name: 'Image',
        content: compact({
            imageUrl: mediaUrl(d.src),
            imageAlt: str(d.alt),
            body: str(d.caption),
        }),
    }),
    gallery: (d) => ({
        type: 'gallery',
        content: compact({
            images: Array.isArray(d.images)
                ? d.images
                    .filter(isObj)
                    .map((img) => compact({ url: mediaUrl(img.src), alt: str(img.alt), caption: str(img.caption) }))
                : [],
        }),
    }),
};
function mapBlock(blockType, data) {
    const mapper = BLOCK_MAPPERS[blockType];
    return mapper ? mapper(data) : fallbackTextSection(blockType, data);
}
/** Read the anchor + visibility a builder `section` node imposes on its blocks. */
function sectionContext(node) {
    const settings = isObj(node.settings) ? node.settings : {};
    const anchorId = str(settings.htmlId) || undefined;
    const visible = settings.visibility !== 'hidden';
    return { anchorId, visible };
}
function childrenOf(node) {
    if (isObj(node) && Array.isArray(node.children))
        return node.children;
    return [];
}
function makeSection(mapped, ctx) {
    const def = SECTION_TYPES[mapped.type];
    return {
        id: generateSectionId(),
        sectionType: mapped.type,
        name: mapped.name && mapped.name.trim() ? mapped.name : def.name,
        visible: ctx.visible,
        content: mapped.content,
        settings: {
            ...structuredClone(def.defaultSettings),
            ...(ctx.anchorId ? { anchorId: ctx.anchorId } : {}),
        },
    };
}
/** Depth-first collect every leaf `block` node under `node`, in document order. */
function collectBlocks(node, out, ctx) {
    for (const child of childrenOf(node)) {
        if (!isObj(child))
            continue;
        if (child.type === 'block') {
            const settings = isObj(child.settings) ? child.settings : {};
            const blockType = str(settings.blockType);
            const data = isObj(child.data) ? child.data : {};
            out.push(makeSection(mapBlock(blockType, data), ctx));
        }
        else if (child.type === 'savedSectionRef') {
            // Can't resolve a saved-section reference without a DB lookup — skip.
            continue;
        }
        else {
            collectBlocks(child, out, ctx);
        }
    }
}
/**
 * Convert a page-builder `_layout` tree (or any partial/untrusted shape) into a
 * best-effort canonical `PageSection[]`. Returns `[]` for anything it can't
 * read. Never throws.
 */
export function layoutTreeToSections(layout) {
    if (!isObj(layout))
        return [];
    const sections = [];
    for (const node of childrenOf(layout)) {
        if (!isObj(node))
            continue;
        if (node.type === 'section') {
            collectBlocks(node, sections, sectionContext(node));
        }
        else if (node.type === 'block') {
            // Tolerate trees where a block sits directly under the page.
            const settings = isObj(node.settings) ? node.settings : {};
            sections.push(makeSection(mapBlock(str(settings.blockType), isObj(node.data) ? node.data : {}), {
                visible: true,
            }));
        }
        else if (node.type === 'savedSectionRef') {
            continue;
        }
        else {
            collectBlocks(node, sections, { visible: true });
        }
    }
    return sections;
}
//# sourceMappingURL=bridge.js.map