/**
 * Router Manifest Loading
 *
 * Handles lazy loading and validation of route manifests.
 */
import { invariant, RouteNotFoundError } from "../errors";
import { createRouteHelpers } from "../route-definition";
import { getContext, runWithPrefixes } from "../server/context";
import MapRootLayout from "../server/root-layout";
import { VERSION } from "@rangojs/router:version";
// Module-level manifest cache: avoids re-executing DSL handler on every request.
// Handler execution is deterministic (components, loaders, middleware are module-level
// stable references), so the resulting EntryData tree can be safely cached and reused
// across requests within the same isolate.
//
// Cache is keyed by (VERSION, mountIndex, routeKey, isSSR). VERSION comes from the
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
// When VERSION changes, this module re-evaluates and the cache is recreated empty.
// Including VERSION in the key is additional defense against stale entries.
const manifestModuleCache = new Map();
/**
 * Load manifest from route entry with AsyncLocalStorage context
 * Handles lazy imports, unwrapping, and validation
 *
 * Results are cached at module level after first execution. Subsequent calls
 * for the same (routeKey, isSSR) within the same isolate return cached data
 * without re-executing the DSL handler.
 */
/**
 * Clear the module-level manifest cache.
 * Called on HMR to ensure stale handler references are discarded.
 */
export function clearManifestCache() {
    manifestModuleCache.clear();
}
export async function loadManifest(entry, routeKey, path, metricsStore, isSSR) {
    // Helper to push a metric entry
    const pushMetric = metricsStore
        ? (label, start) => {
            metricsStore.metrics.push({
                label,
                duration: performance.now() - start,
                startTime: start - metricsStore.requestStart,
            });
        }
        : undefined;
    const mountIndex = entry.mountIndex;
    // Check module-level cache (persists across requests within same isolate)
    const cacheKey = `${VERSION}:${mountIndex ?? ''}:${routeKey}:${isSSR ? 1 : 0}`;
    const cached = manifestModuleCache.get(cacheKey);
    if (cached) {
        const cacheStart = performance.now();
        // Set up Store for downstream consumers (segment resolution reads Store.manifest)
        const Store = getContext().getOrCreateStore(routeKey);
        Store.mountIndex = mountIndex;
        Store.isSSR = isSSR;
        if (metricsStore)
            Store.metrics = metricsStore;
        // Restore cached manifest into Store
        for (const [k, v] of cached) {
            Store.manifest.set(k, v);
        }
        pushMetric?.("manifest:cache-hit", cacheStart);
        return cached.get(routeKey);
    }
    const storeSetupStart = performance.now();
    const Store = getContext().getOrCreateStore(routeKey);
    // Set mount index in store for unique shortCode prefixes
    Store.mountIndex = mountIndex;
    // Set isSSR flag so loading() can check if we're in SSR
    Store.isSSR = isSSR;
    // Attach metrics store to context if provided
    if (metricsStore) {
        Store.metrics = metricsStore;
    }
    pushMetric?.("manifest:store-setup", storeSetupStart);
    // Clear manifest before rebuilding to prevent stale entry mutations
    const clearStart = performance.now();
    Store.manifest.clear();
    pushMetric?.("manifest:clear", clearStart);
    try {
        // Include mountIndex in namespace to ensure unique cache keys per mount
        const namespaceWithMount = mountIndex !== undefined
            ? `#router.M${mountIndex}`
            : "#router";
        // For lazy entries, use the captured parent from include() context
        // This ensures routes are registered under the correct layout hierarchy
        const lazyContext = entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
        const parentForContext = lazyContext?.parent ?? Store.parent;
        // For lazy entries, merge captured counters from include() so the
        // handler's entries get shortCode indices after sibling entries that
        // were created during pattern extraction.  This prevents shortCode
        // collisions between lazy and non-lazy entries under the same parent
        // (e.g., ArticlesLayout and BlogLayout both under NavLayout).
        if (lazyContext && lazyContext.counters) {
            const captured = lazyContext.counters;
            for (const [key, value] of Object.entries(captured)) {
                Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
            }
        }
        const handlerExecStart = performance.now();
        const useItems = await getContext().runWithStore(Store, Store.namespace || namespaceWithMount, parentForContext, async () => {
            // Create helpers for lazy-loaded handlers that need them
            const helpers = createRouteHelpers();
            // For lazy entries, use lazyPatterns.handler() with proper prefixes.
            // Do NOT wrap in MapRootLayout here: the captured parent chain from
            // pattern extraction already includes the synthetic MapRootLayout
            // parent, so adding another would create an extra level that does
            // not exist in the non-lazy (root handler) path and would produce
            // mismatched shortCodes.
            if (entry.lazy && entry.lazyPatterns) {
                const lazyPatterns = entry.lazyPatterns;
                const includePrefix = entry._lazyPrefix || "";
                const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
                if (fullPrefix || lazyContext?.namePrefix) {
                    return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => lazyPatterns.handler());
                }
                return lazyPatterns.handler();
            }
            // Wrap handler execution in root layout so routes get correct parent
            // This ensures all routes are registered with the layout as their parent
            let promiseResult = null;
            const wrappedItems = helpers.layout(MapRootLayout, () => {
                const result = entry.handler();
                if (result instanceof Promise) {
                    // Lazy handler detected - capture promise for async handling
                    promiseResult = result;
                    return []; // Return empty, we'll discard this wrapped result
                }
                return result;
            });
            // Handle lazy (Promise-based) handlers
            if (promiseResult !== null) {
                const load = await promiseResult;
                if (load &&
                    load !== null &&
                    typeof load === "object" &&
                    "default" in load) {
                    // Promise<{ default: () => Array }> - e.g., dynamic import
                    // Lazy-loaded handlers may need helpers (passed as optional arg)
                    return load.default(helpers);
                }
                if (typeof load === "function") {
                    // Promise<() => Array>
                    return load(helpers);
                }
                // Promise<Array> - direct array from async handler
                return load;
            }
            // Inline handler - routes were registered with correct parent inside layout
            return [wrappedItems].flat(3);
        });
        pushMetric?.("manifest:handler-exec", handlerExecStart);
        const validationStart = performance.now();
        invariant(useItems && useItems.length > 0, "Did not receive any handler from router.map()");
        // For non-lazy entries the root handler is wrapped in MapRootLayout,
        // so the result always contains a layout item.  Lazy entries run the
        // included patterns handler directly (no MapRootLayout wrapper) so
        // we skip this check -- the layout is in the captured parent chain.
        if (!lazyContext) {
            invariant(useItems.some((item) => item.type === "layout"), "Top-level handler must be a layout");
        }
        invariant(Store.manifest.has(routeKey), `Route must be registered for ${routeKey}`);
        pushMetric?.("manifest:validation", validationStart);
        // Cache manifest for future requests in this isolate
        manifestModuleCache.set(cacheKey, new Map(Store.manifest));
        return Store.manifest.get(routeKey);
    }
    catch (e) {
        throw new RouteNotFoundError(`Failed to load route handlers for ${path}: ${e.message}`, {
            cause: {
                error: e,
                state: {
                    path,
                    routeKey,
                },
            },
        });
    }
}
//# sourceMappingURL=manifest.js.map