/**
 * Build-time Route Trie Construction
 *
 * Builds a serializable trie from the route manifest for O(path_length)
 * route matching at runtime. Each trie leaf embeds the route's ancestry
 * shortCodes for layout pruning.
 */
import { parsePattern } from "../router/pattern-matching.js";
/**
 * Build a route trie from build-time manifest data.
 *
 * @param routeManifest - Map of route name to full URL pattern
 * @param routeAncestry - Map of route name to ancestry shortCodes
 * @param routeToStaticPrefix - Map of route name to its entry's staticPrefix
 * @param routeTrailingSlash - Optional map of route name to trailing slash mode
 */
export function buildRouteTrie(routeManifest, routeAncestry, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes) {
    const root = {};
    for (const [routeName, pattern] of Object.entries(routeManifest)) {
        const ancestry = routeAncestry[routeName] || [];
        const staticPrefix = routeToStaticPrefix[routeName] || "";
        const trailingSlash = routeTrailingSlash?.[routeName];
        const responseType = responseTypeRoutes?.[routeName];
        // Detect and strip trailing slash from pattern for parsing
        const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
        const normalizedPattern = hasTrailingSlash ? pattern.slice(0, -1) : pattern;
        const segments = parsePattern(normalizedPattern);
        insertRoute(root, segments, 0, {
            n: routeName,
            sp: staticPrefix,
            a: ancestry,
            ...(trailingSlash ? { ts: trailingSlash } : {}),
            ...(prerenderRouteNames?.has(routeName) ? { pr: true } : {}),
            ...(passthroughRouteNames?.has(routeName) ? { pt: true } : {}),
            ...(responseType ? { rt: responseType } : {}),
        });
    }
    return root;
}
/**
 * Insert a route into the trie, handling optional params by forking
 * the insertion path (one terminal without the param, one with).
 */
function insertRoute(node, segments, index, leaf) {
    // Collect param names, optional param names, and constraints across all segments
    const paramNames = [];
    const optionalParams = [];
    const constraints = {};
    for (const seg of segments) {
        if (seg.type === "param") {
            paramNames.push(seg.value);
            if (seg.optional) {
                optionalParams.push(seg.value);
            }
            if (seg.constraint) {
                constraints[seg.value] = seg.constraint;
            }
        }
    }
    const fullLeaf = {
        ...leaf,
        ...(paramNames.length > 0 ? { pa: paramNames } : {}),
        ...(optionalParams.length > 0 ? { op: optionalParams } : {}),
        ...(Object.keys(constraints).length > 0 ? { cv: constraints } : {}),
    };
    insertSegments(node, segments, index, fullLeaf);
}
/**
 * Recursively insert segments into the trie.
 * For optional params, we add a terminal at the current node (param absent)
 * AND continue inserting into the param child (param present).
 */
/**
 * Extract ancestry map from a built trie by visiting all leaf nodes.
 * Returns { routeName: ancestryShortCodes[] } for every route in the trie.
 */
export function extractAncestryFromTrie(root) {
    const result = {};
    function visit(node) {
        if (node.r) {
            result[node.r.n] = node.r.a;
        }
        if (node.w) {
            result[node.w.n] = node.w.a;
        }
        if (node.s) {
            for (const child of Object.values(node.s)) {
                visit(child);
            }
        }
        if (node.p) {
            visit(node.p.c);
        }
    }
    visit(root);
    return result;
}
/**
 * Merge a new leaf with an existing leaf, handling content negotiation.
 * When an RSC route and response-type routes share the same URL pattern,
 * the RSC route becomes the primary leaf and response-type routes are
 * appended to the nv (negotiate variants) array.
 * Multiple response types on the same path are supported (json + text + xml).
 */
function mergeLeaves(existing, leaf) {
    if (!existing)
        return leaf;
    if (existing.rt && leaf.rt) {
        // Both are response-type: preserve old as variant
        const merged = leaf;
        merged.nv = existing.nv || [];
        merged.nv.push({ routeKey: existing.n, responseType: existing.rt });
        return merged;
    }
    if (leaf.rt && !existing.rt) {
        // RSC primary exists, new leaf is response-type: append variant
        // RSC was defined first (it was already the existing leaf)
        if (!existing.nv) {
            existing.nv = [];
            existing.rf = true;
        }
        existing.nv.push({ routeKey: leaf.n, responseType: leaf.rt });
        return existing;
    }
    if (!leaf.rt && existing.rt) {
        // Response-type was primary, new leaf is RSC: swap and move old to variants
        // RSC was defined second (response-type was already the existing leaf)
        if (!leaf.nv)
            leaf.nv = [];
        if (existing.nv)
            leaf.nv.push(...existing.nv);
        leaf.nv.push({ routeKey: existing.n, responseType: existing.rt });
        // rf intentionally not set — RSC came after response-type variants
        return leaf;
    }
    // Both RSC (last wins): overwrite
    return leaf;
}
function mergeLeaf(node, leaf) {
    node.r = mergeLeaves(node.r, leaf);
}
function insertSegments(node, segments, index, leaf) {
    // Base case: all segments consumed, add terminal
    if (index >= segments.length) {
        mergeLeaf(node, leaf);
        return;
    }
    const segment = segments[index];
    if (segment.type === "static") {
        if (!node.s)
            node.s = {};
        if (!node.s[segment.value])
            node.s[segment.value] = {};
        insertSegments(node.s[segment.value], segments, index + 1, leaf);
    }
    else if (segment.type === "param") {
        if (segment.optional) {
            // Optional param: add terminal at current node (param absent)
            mergeLeaf(node, leaf);
            // AND continue with param child (param present)
        }
        if (!node.p) {
            node.p = { n: segment.value, c: {} };
        }
        insertSegments(node.p.c, segments, index + 1, leaf);
    }
    else if (segment.type === "wildcard") {
        // Wildcard consumes all remaining segments
        const wildLeaf = { ...leaf, pn: "*" };
        const existing = node.w ? { ...node.w } : undefined;
        const merged = mergeLeaves(existing, wildLeaf);
        node.w = merged;
    }
}
//# sourceMappingURL=route-trie.js.map