/**
 * Build-time manifest generation for @rangojs/router
 *
 * Extracts the prefix tree and route manifest from UrlPatterns at build time.
 * This enables:
 * - Pre-computed prefix tree for fast short-circuit checks
 * - Complete route manifest for href() without runtime evaluation
 * - Support for nested includes
 */
import { extractStaticPrefix } from "../router/pattern-matching.js";
import { RSCRouterContext, runWithPrefixes } from "../server/context.js";
import { createRouteHelpers } from "../route-definition.js";
import MapRootLayout from "../server/root-layout.js";
/**
 * Build prefix tree node by running the patterns with proper context.
 * Uses a visited set to detect circular includes and prevent infinite recursion.
 */
function buildPrefixTreeNode(urlPrefix, namePrefix, patterns, routeManifest, routeAncestry, // internal: feeds trie building, not exported
mountIndex, visited = new Set(), routeTrailingSlash, prerenderRoutes, prerenderDefs, passthroughRoutes, responseTypeRoutes, routeSearchSchemas) {
    if (visited.has(patterns)) {
        console.warn(`[@rangojs/router] Circular include detected at prefix "${urlPrefix}". Skipping.`);
        return {
            staticPrefix: extractStaticPrefix(urlPrefix),
            fullPrefix: urlPrefix,
            namePrefix: namePrefix || undefined,
            children: {},
            routes: [],
        };
    }
    visited.add(patterns);
    // Create context for running patterns with include tracking
    const manifest = new Map();
    const patternsMap = new Map();
    const patternsByPrefix = new Map();
    const trailingSlashMap = new Map();
    const searchSchemasMap = new Map();
    const trackedIncludes = [];
    RSCRouterContext.run({
        manifest,
        patterns: patternsMap,
        patternsByPrefix,
        trailingSlash: trailingSlashMap,
        searchSchemas: searchSchemasMap,
        namespace: "build",
        parent: null,
        counters: {},
        mountIndex,
        trackedIncludes, // Enable nested include tracking
    }, () => {
        const helpers = createRouteHelpers();
        // Wrap in root layout for correct parent hierarchy (matches runtime)
        helpers.layout(MapRootLayout, () => {
            if (urlPrefix || namePrefix) {
                return runWithPrefixes(urlPrefix, namePrefix, () => {
                    return patterns.handler();
                });
            }
            return patterns.handler();
        });
    });
    // Collect route names defined in this include (routes have prefixes applied)
    const routes = [];
    for (const [name, pattern] of patternsMap.entries()) {
        routes.push(name);
        routeManifest[name] = pattern;
    }
    // Collect trailing slash config
    if (routeTrailingSlash) {
        for (const [name, mode] of trailingSlashMap.entries()) {
            routeTrailingSlash[name] = mode;
        }
    }
    if (routeSearchSchemas) {
        for (const [name, schema] of searchSchemasMap.entries()) {
            routeSearchSchemas[name] = schema;
        }
    }
    // Capture ancestry from manifest entries' parent chains
    captureAncestry(manifest, routeAncestry);
    // Collect prerender route names and handler definitions from manifest entries
    if (prerenderRoutes) {
        for (const [name, entry] of manifest) {
            if (entry.type === "route" && entry.isPrerender) {
                prerenderRoutes.push(name);
                if (prerenderDefs && entry.prerenderDef) {
                    prerenderDefs[name] = entry.prerenderDef;
                }
                if (passthroughRoutes && entry.prerenderDef?.options?.passthrough === true) {
                    passthroughRoutes.push(name);
                }
            }
        }
    }
    // Collect response type routes from manifest entries
    if (responseTypeRoutes) {
        for (const [name, entry] of manifest) {
            if (entry.type === "route" && entry.responseType) {
                responseTypeRoutes[name] = entry.responseType;
            }
        }
    }
    // Build children from tracked nested includes.
    // Multiple includes can share the same fullPrefix (e.g., include("/", patternsA),
    // include("/", patternsB)). Merge their routes instead of overwriting.
    const children = {};
    for (const include of trackedIncludes) {
        const childNode = buildPrefixTreeNode(include.fullPrefix, include.namePrefix, include.patterns, routeManifest, routeAncestry, mountIndex, visited, routeTrailingSlash, prerenderRoutes, prerenderDefs, passthroughRoutes, responseTypeRoutes, routeSearchSchemas);
        const existing = children[include.fullPrefix];
        if (existing) {
            existing.routes.push(...childNode.routes);
            Object.assign(existing.children, childNode.children);
        }
        else {
            children[include.fullPrefix] = childNode;
        }
    }
    return {
        staticPrefix: extractStaticPrefix(urlPrefix),
        fullPrefix: urlPrefix,
        namePrefix: namePrefix || undefined,
        children,
        routes,
    };
}
/**
 * Walk parent chains of route entries to extract ancestry shortCodes.
 */
function captureAncestry(manifest, routeAncestry) {
    for (const [routeName, entry] of manifest) {
        if (entry.type === "route") {
            const ancestry = [];
            let current = entry;
            while (current) {
                ancestry.unshift(current.shortCode);
                current = current.parent;
            }
            routeAncestry[routeName] = ancestry;
        }
    }
}
/**
 * Generate manifest from UrlPatterns
 *
 * This runs all patterns (including lazy ones) at build time to extract:
 * - The complete prefix tree for short-circuit optimization
 * - The complete route manifest for href()
 *
 * @example
 * ```typescript
 * import { generateManifest } from "@rangojs/router/build";
 * import { urlpatterns } from "./urls";
 *
 * const manifest = generateManifest(urlpatterns);
 * // Write to file for runtime use
 * fs.writeFileSync(
 *   "src/generated/route-manifest.json",
 *   JSON.stringify(manifest, null, 2)
 * );
 * ```
 */
export function generateManifest(urlpatterns, mountIndex = 0) {
    const routeManifest = {};
    const routeAncestry = {};
    const prefixTree = {};
    // Run the root patterns handler with tracking enabled
    const manifest = new Map();
    const patternsMap = new Map();
    const patternsByPrefix = new Map();
    const trailingSlashMap = new Map();
    const searchSchemasMap = new Map();
    const trackedIncludes = [];
    RSCRouterContext.run({
        manifest,
        patterns: patternsMap,
        patternsByPrefix,
        trailingSlash: trailingSlashMap,
        searchSchemas: searchSchemasMap,
        namespace: "build",
        parent: null,
        counters: {},
        mountIndex,
        trackedIncludes, // Enable include tracking
    }, () => {
        const helpers = createRouteHelpers();
        // Wrap in root layout for correct parent hierarchy (matches runtime)
        helpers.layout(MapRootLayout, () => {
            return urlpatterns.handler();
        });
    });
    // Collect root-level routes and trailing slash config
    const routeTrailingSlash = {};
    for (const [name, pattern] of patternsMap.entries()) {
        routeManifest[name] = pattern;
    }
    for (const [name, mode] of trailingSlashMap.entries()) {
        routeTrailingSlash[name] = mode;
    }
    const routeSearchSchemas = {};
    for (const [name, schema] of searchSchemasMap.entries()) {
        routeSearchSchemas[name] = schema;
    }
    // Capture ancestry from manifest entries' parent chains
    captureAncestry(manifest, routeAncestry);
    // Collect prerender route names and handler definitions across all levels
    const prerenderRoutes = [];
    const prerenderDefs = {};
    const passthroughRoutes = [];
    const responseTypeRoutes = {};
    for (const [name, entry] of manifest) {
        if (entry.type === "route" && entry.isPrerender) {
            prerenderRoutes.push(name);
            if (entry.prerenderDef) {
                prerenderDefs[name] = entry.prerenderDef;
            }
            if (entry.prerenderDef?.options?.passthrough === true) {
                passthroughRoutes.push(name);
            }
        }
        if (entry.type === "route" && entry.responseType) {
            responseTypeRoutes[name] = entry.responseType;
        }
    }
    // Build prefix tree from tracked includes (shared visited set for cycle detection).
    // Multiple includes can share the same fullPrefix (e.g., include("/", patternsA),
    // include("/", patternsB)). Merge their routes instead of overwriting.
    const visited = new Set();
    for (const include of trackedIncludes) {
        const node = buildPrefixTreeNode(include.fullPrefix, include.namePrefix, include.patterns, routeManifest, routeAncestry, mountIndex, visited, routeTrailingSlash, prerenderRoutes, prerenderDefs, passthroughRoutes, responseTypeRoutes, routeSearchSchemas);
        const existing = prefixTree[include.fullPrefix];
        if (existing) {
            existing.routes.push(...node.routes);
            Object.assign(existing.children, node.children);
        }
        else {
            prefixTree[include.fullPrefix] = node;
        }
    }
    return {
        prefixTree,
        routeManifest,
        routeTrailingSlash: Object.keys(routeTrailingSlash).length > 0 ? routeTrailingSlash : undefined,
        prerenderRoutes: prerenderRoutes.length > 0 ? prerenderRoutes : undefined,
        passthroughRoutes: passthroughRoutes.length > 0 ? passthroughRoutes : undefined,
        responseTypeRoutes: Object.keys(responseTypeRoutes).length > 0 ? responseTypeRoutes : undefined,
        routeSearchSchemas: Object.keys(routeSearchSchemas).length > 0 ? routeSearchSchemas : undefined,
        generatedAt: new Date().toISOString(),
        // Internal: routeAncestry is used only for trie building, not exported
        _routeAncestry: routeAncestry,
        // Internal: prerender handler definitions for build-time getParams() access
        _prerenderDefs: Object.keys(prerenderDefs).length > 0 ? prerenderDefs : undefined,
    };
}
/**
 * Generate TypeScript code for the manifest
 *
 * @example
 * ```typescript
 * const code = generateManifestCode(urlpatterns);
 * fs.writeFileSync("src/generated/route-manifest.ts", code);
 * ```
 */
export function generateManifestCode(urlpatterns) {
    const manifest = generateManifest(urlpatterns);
    return `/**
 * Auto-generated route manifest
 * Generated at: ${manifest.generatedAt}
 *
 * DO NOT EDIT - This file is generated by @rangojs/router
 */

export const routeManifest = ${JSON.stringify(manifest.routeManifest, null, 2)} as const;

export type RouteNames = keyof typeof routeManifest;
`;
}
//# sourceMappingURL=generate-manifest.js.map