// Model-only-for-naming port for design-to-code emission (Epic #750, Issue #755).
//
// The deterministic emitter (emissionPlan.ts) already produces a complete, usable plan with
// structural default names and NO model. This module is the strictly-additive seam by which a
// capability-routed model may supply BETTER semantic display names — and nothing else. The override
// is enforced STRUCTURALLY: `applyNaming` only ever replaces an element's `displayName`; it cannot
// add, remove, reorder, or re-parent an element, change a role, change text, or change navigation.
// There is no code path by which a model name reaches structure. An absent provider, an unknown id,
// or an empty/whitespace name leaves the structural default in place — so naming degrades gracefully
// to the deterministic baseline. The provider is injected (a port), so no model id is hard-coded
// here; the server tier resolves the model by capability (#810) and backs this port.
function collectRequestElements(element, out) {
    out.push({ id: element.id, role: element.role, structuralName: element.displayName });
    for (const child of element.children)
        collectRequestElements(child, out);
}
function buildRequest(plan) {
    const elements = [];
    for (const screen of plan.screens)
        collectRequestElements(screen.root, elements);
    return { elements };
}
// Project the provider result to id/value pairs, accepting either a Map or a plain record.
function namingEntries(result) {
    if (result instanceof Map) {
        const map = result;
        return [...map.entries()];
    }
    return Object.entries(result);
}
// Normalise the provider result to a lookup. A non-string or empty/whitespace value is dropped so a
// garbage model output never overrides a structural default — it simply contributes nothing.
function toNameLookup(result) {
    const lookup = new Map();
    for (const [id, value] of namingEntries(result)) {
        if (typeof value !== "string")
            continue;
        const trimmed = value.trim();
        if (trimmed.length > 0)
            lookup.set(id, trimmed);
    }
    return lookup;
}
function renameElement(element, names) {
    const override = names.get(element.id);
    return {
        ...element,
        ...(override !== undefined ? { displayName: override } : {}),
        children: element.children.map((child) => renameElement(child, names)),
    };
}
function renameScreen(screen, names) {
    return { ...screen, root: renameElement(screen.root, names) };
}
/**
 * Apply a semantic-naming provider to a plan, overriding ONLY element display names. The element
 * tree, roles, text, tokens, and navigation are preserved byte-for-byte; only `displayName` fields
 * may change, and only for elements the provider names with a non-empty string. With no usable name
 * for an element (or a missing provider entry), the structural default stands. Deterministic and
 * side-effect-free.
 */
export function applyNaming(plan, provider) {
    const names = toNameLookup(provider(buildRequest(plan)));
    if (names.size === 0)
        return plan;
    return { ...plan, screens: plan.screens.map((screen) => renameScreen(screen, names)) };
}
