import { createCacheScope } from "./cache/cache-scope.js";
import { assertClientComponent } from "./component-utils.js";
import { DefaultDocument } from "./components/DefaultDocument.js";
import { sanitizeError, } from "./errors";
import { serializeManifest } from "./debug.js";
import { createReverse, } from "./reverse.js";
import { registerRouteMap, getGlobalRouteMap, getPrecomputedEntries, getRouteTrie, getRouterManifest, getRouterTrie, getRouterPrecomputedEntries, ensureRouterManifest, } from "./route-map-builder.js";
import { tryTrieMatch } from "./router/trie-matching.js";
import { createRouteHelpers, } from "./route-definition.js";
import MapRootLayout from "./server/root-layout.js";
import { getContext, RSCRouterContext, runWithPrefixes, } from "./server/context";
import { createHandleStore } from "./server/handle-store.js";
import { getRequestContext } from "./server/request-context.js";
import { runWithRequestContext, } from "./server/request-context.js";
// Extracted router utilities
import { createErrorInfo, findNearestErrorBoundary as findErrorBoundary, findNearestNotFoundBoundary as findNotFoundBoundary, invokeOnError, } from "./router/error-handling.js";
// Extracted segment resolution functions
import { resolveAllSegments as _resolveAllSegments, resolveLoadersOnly as _resolveLoadersOnly, resolveLoadersOnlyWithRevalidation as _resolveLoadersOnlyWithRevalidation, buildEntryRevalidateMap as _buildEntryRevalidateMap, resolveAllSegmentsWithRevalidation as _resolveAllSegmentsWithRevalidation, } from "./router/segment-resolution.js";
// Extracted intercept resolution functions
import { findInterceptForRoute as _findInterceptForRoute, resolveInterceptEntry as _resolveInterceptEntry, resolveInterceptLoadersOnly as _resolveInterceptLoadersOnly, } from "./router/intercept-resolution.js";
// Extracted match API functions
import { createMatchContextForFull as _createMatchContextForFull, createMatchContextForPartial as _createMatchContextForPartial, matchError as _matchError, } from "./router/match-api.js";
import { createHandlerContext } from "./router/handler-context.js";
import { setupLoaderAccess, setupLoaderAccessSilent, setupBuildUse, wrapLoaderWithErrorHandling, } from "./router/loader-resolution.js";
import { loadManifest } from "./router/manifest.js";
import { createMetricsStore, } from "./router/metrics.js";
import { collectRouteMiddleware, parsePattern, } from "./router/middleware.js";
import { extractStaticPrefix, findMatch as findRouteMatch, isLazyEvaluationNeeded, traverseBack, } from "./router/pattern-matching.js";
import { evaluateRevalidation } from "./router/revalidation.js";
import { runWithRouterContext, } from "./router/router-context.js";
import { createPipelineState, } from "./router/match-context.js";
import { createMatchPartialPipeline } from "./router/match-pipelines.js";
import { collectMatchResult } from "./router/match-result.js";
import { runWithRouterLogContext, withRouterLogScope, } from "./router/logging.js";
import { resolveThemeConfig } from "./theme/constants.js";
// Response type -> MIME type used for Accept header matching
const RESPONSE_TYPE_MIME = {
    json: "application/json",
    text: "text/plain",
    xml: "application/xml",
    html: "text/html",
    md: "text/markdown",
};
// Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
const MIME_RESPONSE_TYPE = Object.fromEntries(Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]));
/**
 * Parse an Accept header into a sorted array of MIME entries.
 * Respects q-values (default 1.0) and uses client order as tiebreaker
 * when q-values are equal (matching Express/Hono behavior).
 */
function parseAcceptTypes(accept) {
    const entries = [];
    const parts = accept.split(",");
    for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        const segments = part.split(";");
        const mime = segments[0].trim();
        if (!mime)
            continue;
        let q = 1.0;
        for (let j = 1; j < segments.length; j++) {
            const param = segments[j].trim();
            if (param.startsWith("q=")) {
                q = Math.max(0, Math.min(1, Number(param.slice(2)) || 0));
            }
        }
        entries.push({ mime, q, order: i });
    }
    // Sort: highest q first, then lowest client order first (stable)
    entries.sort((a, b) => b.q - a.q || a.order - b.order);
    return entries;
}
// Sentinel response type for RSC routes in negotiation candidates
const RSC_RESPONSE_TYPE = "__rsc__";
/**
 * Pick the best negotiate variant by walking the client's sorted Accept list.
 * For each accepted MIME type (in q-value/order priority), check if any
 * candidate serves that type. Wildcards (*\/*) match the first candidate.
 * Falls back to the first candidate if nothing matches.
 */
function pickNegotiateVariant(acceptEntries, candidates) {
    // Build a MIME -> candidate lookup for O(1) matching
    const byCandidateMime = new Map();
    for (const c of candidates) {
        const mime = c.responseType === RSC_RESPONSE_TYPE ? "text/html" : RESPONSE_TYPE_MIME[c.responseType];
        if (mime && !byCandidateMime.has(mime)) {
            byCandidateMime.set(mime, c);
        }
    }
    for (const entry of acceptEntries) {
        if (entry.q === 0)
            continue;
        // Wildcard matches first candidate
        if (entry.mime === "*/*")
            return candidates[0];
        // Type wildcard (e.g. "text/*") — match first candidate with that type
        if (entry.mime.endsWith("/*")) {
            const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
            for (const [mime, candidate] of byCandidateMime) {
                if (mime.startsWith(typePrefix + "/"))
                    return candidate;
            }
            continue;
        }
        const match = byCandidateMime.get(entry.mime);
        if (match)
            return match;
    }
    // No match — use first candidate as default
    return candidates[0];
}
/**
 * Router configuration options
 */
/**
 * Brand marker for identifying router instances at build time.
 * Used by the Vite plugin to auto-discover routers from module exports.
 */
export const RSC_ROUTER_BRAND = "__rsc_router__";
/**
 * Global registry of all router instances created via createRouter().
 * Each router is keyed by its id (auto-generated or user-provided).
 * Used by the Vite plugin at build time to discover routers and extract
 * manifests, prefix trees, and pre-render candidates.
 */
export const RouterRegistry = new Map();
let routerAutoId = 0;
/**
 * Create an RSC router with generic context type
 * Route types are accumulated automatically through the builder chain
 *
 * @example
 * ```typescript
 * interface AppContext {
 *   db: Database;
 *   user?: User;
 * }
 *
 * const router = createRouter<AppContext>({
 *   debugPerformance: true  // Enable metrics
 * });
 *
 * // Route types accumulate through the chain - no module augmentation needed!
 * // Keys stay unchanged, only URL patterns get the prefix
 * router
 *   .routes(homeRoutes)          // accumulates homeRoutes
 *   .map(() => import('./home'))
 *   .routes('/shop', shopRoutes) // accumulates shopRoutes with prefixed URLs
 *   .map(() => import('./shop'));
 *
 * // router.reverse now has type-safe autocomplete for all registered routes
 * // Given shopRoutes = { cart: "/cart" }, reverse uses original key:
 * router.reverse("cart"); // "/shop/cart"
 * ```
 */
export function createRouter(options = {}) {
    const { id: userProvidedId, $$id: injectedId, debugPerformance = false, document: documentOption, defaultErrorBoundary, defaultNotFoundBoundary, notFound, onError, cache, theme: themeOption, urls: urlsOption, $$routeNames: staticRouteNames, nonce, version, warmup: warmupOption, allowDebugManifest: allowDebugManifestOption = true, } = options;
    // Capture the source file that called createRouter() via stack trace parsing.
    // Used by the Vite plugin to write per-router named-routes.gen.ts files.
    let __sourceFile;
    try {
        const stack = new Error().stack;
        if (stack) {
            const lines = stack.split("\n");
            for (const line of lines) {
                const match = line.match(/\((.+?\.(ts|tsx|js|jsx)):\d+:\d+\)/);
                if (match && !match[1].endsWith("/router.ts") && !match[1].includes("@rangojs/router") && !match[1].includes("node_modules")) {
                    // Strip file: URL protocol prefix from Vite module runner stack traces
                    __sourceFile = match[1].startsWith("file:") ? match[1].slice(5) : match[1];
                    break;
                }
            }
        }
    }
    catch { }
    // Router ID priority: explicit id > Vite-injected $$id > counter fallback.
    // $$id is a hash of filename+line injected by the Vite transform at compile
    // time, so it's stable across build/runtime regardless of module evaluation
    // order (unlike the counter which depends on import order).
    const routerId = userProvidedId ?? injectedId ?? `router_${routerAutoId++}`;
    // Resolve warmup enabled flag (default: true)
    const warmupEnabled = warmupOption !== false;
    // Resolve theme config (null if theme not enabled)
    const resolvedThemeConfig = themeOption
        ? resolveThemeConfig(themeOption)
        : null;
    /**
     * Wrapper for invokeOnError that binds the router's onError callback.
     * Uses the shared utility from router/error-handling.ts for consistent behavior.
     */
    function callOnError(error, phase, context) {
        invokeOnError(onError, error, phase, context, "Router");
    }
    // Validate document is a client component
    if (documentOption !== undefined) {
        assertClientComponent(documentOption, "document");
    }
    // Use default document if none provided (keeps internal name as rootLayout)
    const rootLayout = documentOption ?? DefaultDocument;
    const routesEntries = [];
    let mountIndex = 0;
    // Store reference to urlpatterns for runtime manifest generation
    let storedUrlPatterns = null;
    // Global middleware storage
    const globalMiddleware = [];
    // Helper to add middleware entry
    function addMiddleware(patternOrMiddleware, middleware, mountPrefix = null) {
        let pattern = null;
        let handler;
        if (typeof patternOrMiddleware === "string") {
            // Pattern + middleware
            pattern = patternOrMiddleware;
            if (!middleware) {
                throw new Error("Middleware function required when pattern is provided");
            }
            handler = middleware;
        }
        else {
            // Just middleware (no pattern)
            handler = patternOrMiddleware;
        }
        // If mount-scoped, prepend mount prefix to pattern
        let fullPattern = pattern;
        if (mountPrefix && pattern) {
            // e.g., mountPrefix="/blog", pattern="/admin/*" → "/blog/admin/*"
            fullPattern =
                pattern === "*" ? `${mountPrefix}/*` : `${mountPrefix}${pattern}`;
        }
        else if (mountPrefix && !pattern) {
            // Mount-scoped middleware without pattern applies to all of mount
            fullPattern = `${mountPrefix}/*`;
        }
        // Parse pattern into regex
        let regex = null;
        let paramNames = [];
        if (fullPattern) {
            const parsed = parsePattern(fullPattern);
            regex = parsed.regex;
            paramNames = parsed.paramNames;
        }
        globalMiddleware.push({
            pattern: fullPattern,
            regex,
            paramNames,
            handler,
            mountPrefix,
        });
    }
    // Track all registered routes with their prefixes for reverse()
    const mergedRouteMap = {};
    // Lazy precomputed entries lookup: rebuilt when per-router data arrives.
    // In production multi-router setups, per-router data is loaded lazily via
    // ensureRouterManifest(). At createRouter() time the data isn't available yet,
    // so we defer building the Map until first use and invalidate when the
    // per-router source changes.
    let precomputedByPrefix = null;
    let precomputedSource;
    function getPrecomputedByPrefix() {
        const current = getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
        if (current !== precomputedSource) {
            precomputedSource = current;
            precomputedByPrefix = current
                ? new Map(current.map((e) => [e.staticPrefix, e.routes]))
                : null;
        }
        return precomputedByPrefix;
    }
    // Wrapper to pass debugPerformance to external createMetricsStore
    const getMetricsStore = () => createMetricsStore(debugPerformance);
    // Wrapper to pass defaults to error/notFound boundary finders
    const findNearestErrorBoundary = (entry) => findErrorBoundary(entry, defaultErrorBoundary);
    const findNearestNotFoundBoundary = (entry) => findNotFoundBoundary(entry, defaultNotFoundBoundary);
    // Helper to get handleStore from request context
    const getHandleStore = () => {
        return getRequestContext()?._handleStore;
    };
    // Track a pending handler promise (non-blocking)
    const trackHandler = (promise) => {
        const store = getHandleStore();
        return store ? store.track(promise) : promise;
    };
    // Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
    // Includes onError callback for loader error notification
    function wrapLoaderPromise(promise, entry, segmentId, pathname, errorContext) {
        return wrapLoaderWithErrorHandling(promise, entry, segmentId, pathname, findNearestErrorBoundary, createErrorInfo, 
        // Invoke onError when loader fails
        errorContext
            ? (error, ctx) => {
                callOnError(error, "loader", {
                    request: errorContext.request,
                    url: errorContext.url,
                    routeKey: errorContext.routeKey,
                    params: errorContext.params,
                    segmentId: ctx.segmentId,
                    segmentType: "loader",
                    loaderName: ctx.loaderName,
                    env: errorContext.env,
                    isPartial: errorContext.isPartial,
                    handledByBoundary: ctx.handledByBoundary,
                    requestStartTime: errorContext.requestStartTime,
                });
            }
            : undefined);
    }
    // Dependencies object for extracted segment resolution functions.
    // Captures closure-bound helpers from createRouter.
    const segmentDeps = {
        wrapLoaderPromise,
        trackHandler,
        findNearestErrorBoundary,
        findNearestNotFoundBoundary,
        callOnError,
    };
    // Match API dependencies
    const matchApiDeps = {
        findMatch: (pathname, ms) => findMatch(pathname, ms),
        getMetricsStore,
        findInterceptForRoute: (routeKey, parentEntry, selectorContext, isAction) => findInterceptForRoute(routeKey, parentEntry, selectorContext, isAction),
        callOnError,
        findNearestErrorBoundary,
        getRouteMap: () => getRouterManifest(routerId) ?? getGlobalRouteMap(),
    };
    // Thin wrappers that bind the deps to extracted functions.
    // These maintain the same signatures as the original inline functions
    // so that RouterContext and call sites don't need to change.
    function resolveAllSegments(entries, routeKey, params, context, loaderPromises, options) {
        return _resolveAllSegments(entries, routeKey, params, context, loaderPromises, segmentDeps, options);
    }
    function resolveLoadersOnly(entries, context) {
        return _resolveLoadersOnly(entries, context, segmentDeps);
    }
    function resolveLoadersOnlyWithRevalidation(entries, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, actionContext) {
        return _resolveLoadersOnlyWithRevalidation(entries, context, clientSegmentIds, prevParams, request, prevUrl, nextUrl, routeKey, segmentDeps, actionContext);
    }
    function buildEntryRevalidateMap(entries) {
        return _buildEntryRevalidateMap(entries);
    }
    function resolveAllSegmentsWithRevalidation(entries, routeKey, params, context, clientSegmentSet, prevParams, request, prevUrl, nextUrl, loaderPromises, actionContext, interceptResult, localRouteName, pathname) {
        return _resolveAllSegmentsWithRevalidation(entries, routeKey, params, context, clientSegmentSet, prevParams, request, prevUrl, nextUrl, loaderPromises, actionContext, interceptResult, localRouteName, pathname, segmentDeps);
    }
    function findInterceptForRoute(targetRouteKey, fromEntry, selectorContext = null, isAction = false) {
        return _findInterceptForRoute(targetRouteKey, fromEntry, selectorContext, isAction);
    }
    function resolveInterceptEntry(interceptEntry, parentEntry, params, context, belongsToRoute = true, revalidationContext) {
        return _resolveInterceptEntry(interceptEntry, parentEntry, params, context, belongsToRoute, segmentDeps, revalidationContext);
    }
    function resolveInterceptLoadersOnly(interceptEntry, parentEntry, params, context, belongsToRoute = true, revalidationContext) {
        return _resolveInterceptLoadersOnly(interceptEntry, parentEntry, params, context, belongsToRoute, segmentDeps, revalidationContext);
    }
    // Detect lazy includes in handler result and create placeholder entries
    // Lazy includes are IncludeItem with lazy: true and _lazyContext
    // Moved to outer scope so it can be reused by evaluateLazyEntry for nested includes
    function findLazyIncludes(items) {
        const lazyItems = [];
        for (const item of items) {
            if (!item)
                continue;
            if (item.type === "include") {
                const includeItem = item;
                if (includeItem.lazy === true && includeItem._lazyContext) {
                    lazyItems.push({
                        prefix: includeItem.prefix,
                        patterns: includeItem.patterns,
                        context: includeItem._lazyContext,
                    });
                }
            }
            // Recursively check nested items (in layouts, etc.)
            if (item.uses && Array.isArray(item.uses)) {
                lazyItems.push(...findLazyIncludes(item.uses));
            }
        }
        return lazyItems;
    }
    /**
     * Evaluate a lazy entry's patterns and populate its routes
     * This runs the lazy patterns handler and updates the entry in-place
     * Also detects nested lazy includes and registers them as new entries
     */
    function evaluateLazyEntry(entry) {
        if (!entry.lazy || entry.lazyEvaluated || !entry.lazyPatterns) {
            return;
        }
        // Check for pre-computed routes from build-time data.
        // Only leaf nodes (no nested includes) are precomputed, so entries with
        // nested lazy includes fall through to the handler below.
        // When multiple entries share the same staticPrefix (e.g., several
        // include("/", ...) calls), the precomputed data merges all their routes
        // into one entry. Assigning that merged set to the first matching entry
        // causes findMatch to pick the wrong handler for routes belonging to a
        // different include. Skip the shortcut when the prefix is shared.
        const currentPrecomputed = getPrecomputedByPrefix();
        if (currentPrecomputed) {
            const routes = currentPrecomputed.get(entry.staticPrefix);
            if (routes) {
                const prefixIsShared = routesEntries.filter((e) => e.staticPrefix === entry.staticPrefix).length > 1;
                if (!prefixIsShared) {
                    entry.lazyEvaluated = true;
                    entry.routes = routes;
                    for (const [name, pattern] of Object.entries(routes)) {
                        mergedRouteMap[name] = pattern;
                    }
                    registerRouteMap(mergedRouteMap);
                    return;
                }
            }
        }
        // Mark as evaluated immediately to prevent concurrent evaluation.
        // JS is single-threaded but handlers.handler() could theoretically yield,
        // and the while-loop in findMatch retries after evaluation.
        entry.lazyEvaluated = true;
        const lazyPatterns = entry.lazyPatterns;
        const lazyContext = entry.lazyContext;
        // Create a new context for evaluating the lazy patterns
        const manifest = new Map();
        const patterns = new Map();
        const patternsByPrefix = new Map();
        const trailingSlashMap = new Map();
        // Capture the handler result to detect nested lazy includes
        let handlerResult = [];
        // Merge captured counters from include() to maintain consistent
        // shortCode indices with sibling entries from pattern extraction
        const lazyCounters = {};
        if (lazyContext && lazyContext.counters) {
            const captured = lazyContext.counters;
            for (const [key, value] of Object.entries(captured)) {
                lazyCounters[key] = value;
            }
        }
        RSCRouterContext.run({
            manifest,
            patterns,
            patternsByPrefix,
            trailingSlash: trailingSlashMap,
            namespace: "lazy",
            parent: lazyContext?.parent ?? null,
            counters: lazyCounters,
        }, () => {
            // Run the lazy patterns handler with the original context prefixes
            // The prefix comes from the IncludeItem stored in lazyPatterns
            const includePrefix = entry._lazyPrefix || "";
            const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
            if (fullPrefix || lazyContext?.namePrefix) {
                runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () => {
                    handlerResult = lazyPatterns.handler();
                });
            }
            else {
                handlerResult = lazyPatterns.handler();
            }
        });
        // Populate the entry's routes from the patterns
        const routesObject = {};
        for (const [name, pattern] of patterns.entries()) {
            routesObject[name] = pattern;
            // Also add to merged route map for reverse() support
            const existingPattern = mergedRouteMap[name];
            if (existingPattern !== undefined && existingPattern !== pattern) {
                console.warn(`[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
                    `overwriting with "${pattern}" (from lazy include). Use unique route names to avoid this.`);
            }
            mergedRouteMap[name] = pattern;
        }
        // Update the entry in-place
        entry.routes = routesObject;
        // Note: Do NOT clear lazyPatterns/lazyContext here.
        // loadManifest() needs them on every request to re-run the handler
        // in the correct AsyncLocalStorage context (Store.manifest).
        // Update trailing slash config if available
        if (trailingSlashMap.size > 0) {
            entry.trailingSlash = Object.fromEntries(trailingSlashMap);
        }
        // Detect nested lazy includes and register them as new entries
        const nestedLazyIncludes = findLazyIncludes(handlerResult);
        for (const lazyInclude of nestedLazyIncludes) {
            // Compute the full URL prefix (combining parent prefix if any)
            const fullPrefix = lazyInclude.context.urlPrefix
                ? lazyInclude.context.urlPrefix + lazyInclude.prefix
                : lazyInclude.prefix;
            const nestedEntry = {
                prefix: "",
                staticPrefix: extractStaticPrefix(fullPrefix),
                routes: {}, // Empty until first match
                trailingSlash: entry.trailingSlash,
                handler: lazyInclude.patterns.handler,
                mountIndex: mountIndex++,
                // Lazy evaluation fields
                lazy: true,
                lazyPatterns: lazyInclude.patterns,
                lazyContext: lazyInclude.context,
                lazyEvaluated: false,
                // Store the include prefix for evaluation
                _lazyPrefix: lazyInclude.prefix,
            };
            // Insert nested lazy entry before any entry whose staticPrefix is a
            // prefix of (but shorter than) this lazy entry's staticPrefix.
            // This ensures more specific lazy includes are matched before
            // less specific eager entries (e.g., "/href/nested" before "/href/:id").
            const nestedPrefix = nestedEntry.staticPrefix;
            let insertIndex = routesEntries.length;
            if (nestedPrefix) {
                for (let i = 0; i < routesEntries.length; i++) {
                    const existing = routesEntries[i];
                    if (nestedPrefix.startsWith(existing.staticPrefix) &&
                        nestedPrefix.length > existing.staticPrefix.length) {
                        insertIndex = i;
                        break;
                    }
                }
            }
            routesEntries.splice(insertIndex, 0, nestedEntry);
        }
        // Re-register route map for runtime reverse() usage
        registerRouteMap(mergedRouteMap);
    }
    // Single-entry cache for findMatch to avoid redundant matching within the same request.
    // previewMatch and match both call findMatch with the same pathname — this ensures
    // the route matching work (which may check thousands of routes) only happens once.
    let lastFindMatchPathname = null;
    let lastFindMatchResult = null;
    // Wrapper for findMatch that uses routesEntries
    // Handles lazy evaluation by evaluating lazy entries on first match.
    // Phase 1: try O(path_length) trie match.
    // Phase 2: fall back to regex iteration.
    function findMatch(pathname, ms) {
        // Return cached result if same pathname (avoids double-match per request)
        if (lastFindMatchPathname === pathname) {
            return lastFindMatchResult;
        }
        // Helper to push sub-metrics
        const pushMetric = ms
            ? (label, start) => {
                ms.metrics.push({
                    label,
                    duration: performance.now() - start,
                    startTime: start - ms.requestStart,
                });
            }
            : undefined;
        // Phase 1: Try trie match (O(path_length))
        // Prefer per-router trie (isolated) over global trie (merged).
        const routeTrie = getRouterTrie(routerId) ?? getRouteTrie();
        if (routeTrie) {
            const trieStart = performance.now();
            const trieResult = tryTrieMatch(routeTrie, pathname);
            pushMetric?.("match:trie", trieStart);
            if (trieResult) {
                // Find the RouteEntry that contains this route.
                // Multiple entries can share the same staticPrefix (e.g., several
                // include("/", patterns) calls all produce staticPrefix=""). Evaluate
                // each candidate and pick the one whose routes include the matched key.
                const entryStart = performance.now();
                let entry;
                let fallbackEntry;
                for (const e of routesEntries) {
                    if (e.staticPrefix !== trieResult.sp)
                        continue;
                    if (!fallbackEntry)
                        fallbackEntry = e;
                    evaluateLazyEntry(e);
                    if (e.routes &&
                        trieResult.routeKey in e.routes) {
                        entry = e;
                        break;
                    }
                }
                // If no entry had the route in its routes map, use the first matching
                // entry as fallback (handles main entry with inline routes not yet
                // reflected in its routes object).
                if (!entry)
                    entry = fallbackEntry;
                // If entry not found (nested include not yet discovered), evaluate parent
                if (!entry) {
                    const parent = routesEntries.find((e) => trieResult.sp.startsWith(e.staticPrefix) &&
                        e.staticPrefix !== trieResult.sp);
                    if (parent) {
                        const lazyStart = performance.now();
                        evaluateLazyEntry(parent);
                        pushMetric?.("match:lazy-eval", lazyStart);
                    }
                    entry = routesEntries.find((e) => e.staticPrefix === trieResult.sp);
                }
                pushMetric?.("match:entry-resolve", entryStart);
                if (entry) {
                    lastFindMatchPathname = pathname;
                    lastFindMatchResult = {
                        entry,
                        routeKey: trieResult.routeKey,
                        params: trieResult.params,
                        optionalParams: new Set(trieResult.optionalParams || []),
                        redirectTo: trieResult.redirectTo,
                        ancestry: trieResult.ancestry,
                        ...(trieResult.pr ? { pr: true } : {}),
                        ...(trieResult.pt ? { pt: true } : {}),
                        ...(trieResult.responseType ? { responseType: trieResult.responseType } : {}),
                        ...(trieResult.negotiateVariants ? { negotiateVariants: trieResult.negotiateVariants } : {}),
                        ...(trieResult.rscFirst ? { rscFirst: true } : {}),
                    };
                    return lastFindMatchResult;
                }
            }
        }
        // Phase 2: Fall back to existing matching (regex iteration)
        const regexStart = performance.now();
        let result = findRouteMatch(pathname, routesEntries);
        // If we hit a lazy entry that needs evaluation, evaluate and retry.
        // Cap iterations to prevent infinite loops from pathological nesting.
        const MAX_LAZY_ITERATIONS = 100;
        let iterations = 0;
        while (isLazyEvaluationNeeded(result)) {
            if (++iterations > MAX_LAZY_ITERATIONS) {
                console.error(`[@rangojs/router] Exceeded ${MAX_LAZY_ITERATIONS} lazy evaluation iterations ` +
                    `for pathname "${pathname}". This likely indicates circular lazy includes.`);
                lastFindMatchPathname = pathname;
                lastFindMatchResult = null;
                return null;
            }
            evaluateLazyEntry(result.lazyEntry);
            result = findRouteMatch(pathname, routesEntries);
        }
        pushMetric?.("match:regex-fallback", regexStart);
        lastFindMatchPathname = pathname;
        lastFindMatchResult = result;
        return result;
    }
    /**
     * Build-time pre-render match. Resolves segments with a BuildContext
     * (no request/env/headers/cookies), skipping middleware and loaders.
     */
    async function matchForPrerender(pathname, params) {
        // 1. Find the matching route entry
        const matched = findMatch(pathname);
        if (!matched)
            return null;
        // Use params from trie match if available, fall back to provided params
        const matchedParams = matched.params ?? params;
        // Build a minimal RouterContext for loadManifest/traverseBack
        const routerCtx = {
            findMatch,
            loadManifest,
            traverseBack,
            createHandlerContext,
            setupLoaderAccess,
            setupLoaderAccessSilent,
            getContext,
            getMetricsStore,
            createCacheScope,
            findInterceptForRoute,
            resolveAllSegmentsWithRevalidation,
            resolveInterceptEntry,
            evaluateRevalidation,
            getRequestContext,
            resolveAllSegments,
            createHandleStore,
            buildEntryRevalidateMap,
            resolveLoadersOnlyWithRevalidation,
            resolveInterceptLoadersOnly,
            resolveLoadersOnly,
        };
        return runWithRouterContext(routerCtx, async () => {
            // 2. Load the manifest entry tree
            const manifestEntry = await loadManifest(matched.entry, matched.routeKey, pathname, undefined, false);
            // 3. Build ancestor chain [root, ..., route]
            const entries = [];
            for (const entry of traverseBack(manifestEntry)) {
                entries.push(entry);
            }
            // 4. Create handle store for collecting handle data
            const handleStore = createHandleStore();
            // 5. Create a minimal request context with the handle store
            const stubRes = new Response(null, { status: 200 });
            const minimalRequestContext = {
                env: {},
                request: new Request("http://prerender" + pathname),
                url: new URL("http://prerender" + pathname),
                pathname,
                searchParams: new URLSearchParams(),
                var: {},
                get: () => undefined,
                set: () => { },
                params: matchedParams,
                res: stubRes,
                cookie: () => undefined,
                cookies: () => ({}),
                setCookie: () => { },
                deleteCookie: () => { },
                header: () => { },
                use: (() => {
                    throw new Error("use() not available during pre-rendering");
                }),
                method: "GET",
                _handleStore: handleStore,
                waitUntil: () => { },
                onResponse: () => { },
                _onResponseCallbacks: [],
            };
            return runWithRequestContext(minimalRequestContext, async () => {
                // 6. Create handler context with synthetic request for pre-rendering.
                // The synthetic request and route map are available at build time,
                // so reverse() and other context properties work normally.
                const buildCtx = createHandlerContext(matchedParams, minimalRequestContext.request, minimalRequestContext.url.searchParams, pathname, minimalRequestContext.url, {}, mergedRouteMap, matched.routeKey);
                // 7. Wire use() for handles only (loaders throw)
                setupBuildUse(buildCtx);
                // 8. Resolve all segments with skipLoaders
                const loaderPromises = new Map();
                const allSegments = await resolveAllSegments(entries, matched.routeKey, matchedParams, buildCtx, loaderPromises, { skipLoaders: true });
                // 9. Filter out any loader segments (belt-and-suspenders)
                const nonLoaderSegments = allSegments.filter((s) => s.type !== "loader");
                // 10. Wait for handles to settle
                await handleStore.settled;
                // 11. Serialize segments using the cache serializer
                const { serializeSegments } = await import("./cache/cache-scope.js");
                const serializedSegments = await serializeSegments(nonLoaderSegments);
                // 12. Collect handle data per segment (skip segments with no handle data)
                const handles = {};
                for (const seg of nonLoaderSegments) {
                    const segHandles = handleStore.getDataForSegment(seg.id);
                    if (Object.keys(segHandles).length > 0) {
                        handles[seg.id] = segHandles;
                    }
                }
                // Use the trie-level route key (e.g., "docs", "docs.article")
                const routeName = matched.routeKey;
                return {
                    segments: serializedSegments,
                    handles,
                    routeName,
                    params: matchedParams,
                };
            });
        });
    }
    /**
     * Match request and return segments (document/SSR requests)
     *
     * Uses generator middleware pipeline for clean separation of concerns:
     * - cache-lookup: Check cache first
     * - segment-resolution: Resolve segments on cache miss
     * - cache-store: Store results in cache
     * - background-revalidation: SWR revalidation
     */
    async function match(request, env) {
        // Build RouterContext with all closure functions needed by middleware
        const routerCtx = {
            findMatch,
            loadManifest,
            traverseBack,
            createHandlerContext,
            setupLoaderAccess,
            setupLoaderAccessSilent,
            getContext,
            getMetricsStore,
            createCacheScope,
            findInterceptForRoute,
            resolveAllSegmentsWithRevalidation,
            resolveInterceptEntry,
            evaluateRevalidation,
            getRequestContext,
            resolveAllSegments,
            createHandleStore,
            buildEntryRevalidateMap,
            resolveLoadersOnlyWithRevalidation,
            resolveInterceptLoadersOnly,
            resolveLoadersOnly,
        };
        return runWithRouterLogContext({ request, transaction: "match" }, () => runWithRouterContext(routerCtx, async () => withRouterLogScope("match", async () => {
            const result = await createMatchContextForFull(request, env);
            // Handle redirect case
            if ("type" in result && result.type === "redirect") {
                return {
                    segments: [],
                    matched: [],
                    diff: [],
                    params: {},
                    redirect: result.redirectUrl,
                };
            }
            const ctx = result;
            try {
                const state = createPipelineState();
                const pipeline = createMatchPartialPipeline(ctx, state);
                return await collectMatchResult(pipeline, ctx, state);
            }
            catch (error) {
                if (error instanceof Response)
                    throw error;
                // Report unhandled errors during full match pipeline
                callOnError(error, "routing", {
                    request,
                    url: ctx.url,
                    env,
                    isPartial: false,
                    handledByBoundary: false,
                });
                throw sanitizeError(error);
            }
        })));
    }
    async function matchError(request, _context, error, segmentType = "route") {
        return runWithRouterLogContext({ request, transaction: "matchError" }, () => withRouterLogScope("matchError", () => _matchError(request, _context, error, matchApiDeps, defaultErrorBoundary, segmentType)));
    }
    async function createMatchContextForFull(request, env) {
        return _createMatchContextForFull(request, env, matchApiDeps, findInterceptForRoute);
    }
    async function createMatchContextForPartial(request, env, actionContext) {
        return _createMatchContextForPartial(request, env, matchApiDeps, findInterceptForRoute, actionContext);
    }
    /**
     * Match partial request with revalidation
     *
     * Uses generator middleware pipeline for clean separation of concerns:
     * - cache-lookup: Check cache first
     * - segment-resolution: Resolve segments on cache miss
     * - intercept-resolution: Handle intercept routes
     * - cache-store: Store results in cache
     * - background-revalidation: SWR revalidation
     */
    async function matchPartial(request, context, actionContext) {
        // Build RouterContext with all closure functions needed by middleware
        const routerCtx = {
            findMatch,
            loadManifest,
            traverseBack,
            createHandlerContext,
            setupLoaderAccess,
            setupLoaderAccessSilent,
            getContext,
            getMetricsStore,
            createCacheScope,
            findInterceptForRoute,
            resolveAllSegmentsWithRevalidation,
            resolveInterceptEntry,
            evaluateRevalidation,
            getRequestContext,
            resolveAllSegments,
            createHandleStore,
            buildEntryRevalidateMap,
            resolveLoadersOnlyWithRevalidation,
            resolveInterceptLoadersOnly,
        };
        return runWithRouterLogContext({ request, transaction: "matchPartial" }, () => runWithRouterContext(routerCtx, async () => withRouterLogScope("matchPartial", async () => {
            const ctx = await createMatchContextForPartial(request, context, actionContext);
            if (!ctx)
                return null;
            try {
                const state = createPipelineState();
                const pipeline = createMatchPartialPipeline(ctx, state);
                return await collectMatchResult(pipeline, ctx, state);
            }
            catch (error) {
                if (error instanceof Response)
                    throw error;
                // Report unhandled errors during partial match pipeline
                callOnError(error, actionContext ? "action" : "revalidation", {
                    request,
                    url: ctx.url,
                    env: context,
                    actionId: actionContext?.actionId,
                    isPartial: true,
                    handledByBoundary: false,
                });
                throw sanitizeError(error);
            }
        })));
    }
    /**
     * Preview match - returns route middleware without segment resolution.
     * Also returns responseType and handler for response routes (non-RSC short-circuit).
     */
    async function previewMatch(request, _context) {
        return runWithRouterLogContext({ request, transaction: "previewMatch" }, async () => withRouterLogScope("previewMatch", async () => {
            const url = new URL(request.url);
            const pathname = url.pathname;
            // Quick route matching
            const matched = findMatch(pathname);
            if (!matched) {
                return null;
            }
            // Skip redirect check - will be handled in full match
            if (matched.redirectTo) {
                return { routeMiddleware: undefined };
            }
            // Load manifest (without segment resolution)
            const manifestEntry = await loadManifest(matched.entry, matched.routeKey, pathname, undefined, // No metrics store for preview
            false);
            // Collect route-level middleware from entry tree
            // Includes middleware from orphan layouts (inline layouts within routes)
            const routeMiddleware = collectRouteMiddleware(traverseBack(manifestEntry), matched.params);
            // Check for response type (from trie match or manifest entry)
            const responseType = matched.responseType ||
                (manifestEntry.type === "route" ? manifestEntry.responseType : undefined);
            // Content negotiation: when negotiate variants exist, pick the best
            // handler based on the Accept header. Uses q-values and client order
            // as tiebreaker (matching Express/Hono behavior). RSC routes participate
            // as text/html candidates so browsers naturally get HTML without
            // special-casing.
            if (matched.negotiateVariants && matched.negotiateVariants.length > 0) {
                const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
                // Build candidate list preserving definition order.
                // For wildcard (*/*) and no-Accept fallback, the first candidate wins.
                const variants = matched.negotiateVariants;
                let candidates;
                if (responseType) {
                    // Primary is response-type — include it as a candidate
                    candidates = [...variants, { routeKey: matched.routeKey, responseType }];
                }
                else {
                    // Primary is RSC — insert as text/html candidate in definition order
                    const rscCandidate = { routeKey: matched.routeKey, responseType: RSC_RESPONSE_TYPE };
                    candidates = matched.rscFirst
                        ? [rscCandidate, ...variants]
                        : [...variants, rscCandidate];
                }
                const variant = pickNegotiateVariant(acceptEntries, candidates);
                // If the winner is RSC, fall through to default RSC handling
                if (variant.responseType === RSC_RESPONSE_TYPE) {
                    // Fall through — RSC won negotiation
                }
                else if (responseType && variant.routeKey === matched.routeKey) {
                    // Fall through — response-type primary won, already set
                }
                else {
                    const negotiateEntry = await loadManifest(matched.entry, variant.routeKey, pathname, undefined, false);
                    return {
                        routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
                        responseType: variant.responseType,
                        handler: negotiateEntry.type === "route" ? negotiateEntry.handler : undefined,
                        params: matched.params,
                        negotiated: true,
                    };
                }
            }
            // If we passed through the negotiation block (variants exist), mark as
            // negotiated so the handler sets Vary: Accept on the response.
            const hasVariants = matched.negotiateVariants && matched.negotiateVariants.length > 0;
            return {
                routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
                ...(responseType ? {
                    responseType,
                    handler: manifestEntry.type === "route" ? manifestEntry.handler : undefined,
                    params: matched.params,
                } : {}),
                ...(hasVariants ? { negotiated: true } : {}),
            };
        }));
    }
    /**
     * Create route builder with accumulated route types
     * The TNewRoutes type parameter captures the new routes being added
     */
    function createRouteBuilder(prefix, routes) {
        const currentMountIndex = mountIndex++;
        // Merge routes into the reverse map
        // Keys stay unchanged for composability - only URL patterns get prefixed
        const routeEntries = routes;
        for (const [key, pattern] of Object.entries(routeEntries)) {
            // Build prefixed pattern: "/shop" + "/cart" -> "/shop/cart"
            // Root prefix "/" is a no-op — don't double the leading slash.
            const effectivePrefix = prefix === "/" ? "" : prefix;
            const prefixedPattern = effectivePrefix && pattern !== "/"
                ? `${effectivePrefix}${pattern}`
                : effectivePrefix && pattern === "/"
                    ? effectivePrefix
                    : pattern;
            // Runtime validation: warn if key already exists with different pattern
            const existingPattern = mergedRouteMap[key];
            if (existingPattern !== undefined &&
                existingPattern !== prefixedPattern) {
                console.warn(`[rsc-router] Route key conflict: "${key}" already maps to "${existingPattern}", ` +
                    `overwriting with "${prefixedPattern}". Use unique key names to avoid this.`);
            }
            // Use original key - enables reusable route modules
            mergedRouteMap[key] = prefixedPattern;
        }
        // Auto-register route map for runtime reverse() usage
        registerRouteMap(mergedRouteMap);
        // Extract trailing slash config if present (attached by route())
        const trailingSlashConfig = routes.__trailingSlash;
        // Create builder object so .use() can return it
        const builder = {
            use(patternOrMiddleware, middleware) {
                // Mount-scoped middleware - prefix is the mount prefix
                addMiddleware(patternOrMiddleware, middleware, prefix || null);
                return builder;
            },
            map(handler) {
                // Store handler as-is - detection happens at call time based on return type
                // Both patterns use the same signature:
                // - Inline: ({ route }) => [...] - receives helpers, returns Array
                // - Lazy: () => import(...) - ignores helpers, returns Promise
                routesEntries.push({
                    prefix,
                    staticPrefix: extractStaticPrefix(prefix),
                    routes: routes,
                    trailingSlash: trailingSlashConfig,
                    handler: handler,
                    mountIndex: currentMountIndex,
                });
                // Return router with accumulated types
                // At runtime this is the same object, but TypeScript tracks the accumulated route types
                return router;
            },
            // Expose accumulated route map for typeof extraction
            get routeMap() {
                return mergedRouteMap;
            },
        };
        return builder;
    }
    /**
     * Router instance
     * The type system tracks accumulated routes through the builder chain
     * Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
     */
    const router = {
        __brand: RSC_ROUTER_BRAND,
        id: routerId,
        routes(prefixOrRoutes, maybeRoutes) {
            // Note: Multiple .routes() calls are allowed for backwards compatibility
            // with the old map() pattern. For new code, prefer urls() with include().
            // Check if argument is UrlPatterns (new Django-style API)
            // Detect by checking for handler and definitions properties
            if (typeof prefixOrRoutes === "object" &&
                prefixOrRoutes !== null &&
                "handler" in prefixOrRoutes &&
                "definitions" in prefixOrRoutes &&
                typeof prefixOrRoutes.handler === "function") {
                const urlPatterns = prefixOrRoutes;
                // Store reference for runtime manifest generation
                storedUrlPatterns = urlPatterns;
                const currentMountIndex = mountIndex++;
                // Create manifest and patterns maps for route registration
                const manifest = new Map();
                const patterns = new Map();
                const patternsByPrefix = new Map();
                const trailingSlashMap = new Map();
                // Run the handler once to extract patterns for route matching.
                // Note: loadManifest will re-run the handler to register entries in its context.
                // Lazy includes are detected in the return value and handled separately.
                //
                // Pattern extraction must use the same mountIndex and MapRootLayout root
                // parent as loadManifest so that shortCodes produced here match those at
                // runtime.  include() captures the current parent and counters; if those
                // shortCodes diverge from the runtime tree the segment reconciliation on
                // the client will see a full mismatch and remount the entire page.
                const syntheticMapRoot = {
                    type: "layout",
                    id: `#synthetic-maproot-M${currentMountIndex}`,
                    shortCode: `M${currentMountIndex}L0`,
                    parent: null,
                    handler: MapRootLayout,
                    middleware: [],
                    revalidate: [],
                    errorBoundary: [],
                    notFoundBoundary: [],
                    layout: [],
                    parallel: [],
                    intercept: [],
                    loader: [],
                };
                let handlerResult = [];
                RSCRouterContext.run({
                    manifest,
                    patterns,
                    patternsByPrefix,
                    trailingSlash: trailingSlashMap,
                    namespace: "root",
                    parent: syntheticMapRoot,
                    counters: {},
                    mountIndex: currentMountIndex,
                }, () => {
                    handlerResult = urlPatterns.handler();
                });
                // Store the ORIGINAL handler - loadManifest will re-run it to register manifest entries
                // Convert trailingSlash map to object for the router
                const trailingSlashConfig = trailingSlashMap.size > 0
                    ? Object.fromEntries(trailingSlashMap)
                    : undefined;
                // Collect route keys that have prerender handlers (for non-trie match path)
                let prerenderRouteKeys;
                for (const [name, entry] of manifest.entries()) {
                    if (entry.type === "route" && entry.isPrerender) {
                        if (!prerenderRouteKeys)
                            prerenderRouteKeys = new Set();
                        prerenderRouteKeys.add(name);
                    }
                }
                // Create separate RouteEntry for each URL prefix group
                // This enables prefix-based short-circuit optimization
                if (patternsByPrefix.size > 0) {
                    for (const [prefix, prefixPatterns] of patternsByPrefix.entries()) {
                        const routesObject = {};
                        for (const [name, pattern] of prefixPatterns.entries()) {
                            routesObject[name] = pattern;
                        }
                        routesEntries.push({
                            // prefix is "" because patterns already include the URL prefix
                            // (e.g., "/site/:locale/user1/:id" not just "/user1/:id")
                            prefix: "",
                            // staticPrefix is the actual prefix for short-circuit optimization
                            staticPrefix: extractStaticPrefix(prefix),
                            routes: routesObject,
                            trailingSlash: trailingSlashConfig,
                            handler: urlPatterns.handler,
                            mountIndex: currentMountIndex,
                            ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
                        });
                    }
                }
                else {
                    // Fallback: no prefix grouping, use flat patterns map
                    const routesObject = {};
                    for (const [name, pattern] of patterns.entries()) {
                        routesObject[name] = pattern;
                    }
                    routesEntries.push({
                        prefix: "",
                        staticPrefix: "",
                        routes: routesObject,
                        trailingSlash: trailingSlashConfig,
                        handler: urlPatterns.handler,
                        mountIndex: currentMountIndex,
                        ...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
                    });
                }
                // Build route map from registered patterns
                for (const [name, pattern] of patterns.entries()) {
                    // Runtime validation: warn if key already exists with different pattern
                    const existingPattern = mergedRouteMap[name];
                    if (existingPattern !== undefined && existingPattern !== pattern) {
                        console.warn(`[@rangojs/router] Route name conflict: "${name}" already maps to "${existingPattern}", ` +
                            `overwriting with "${pattern}". Use unique route names to avoid this.`);
                    }
                    mergedRouteMap[name] = pattern;
                }
                // Detect lazy includes in handler result and create placeholder entries
                // Uses findLazyIncludes from outer scope (shared with evaluateLazyEntry)
                const lazyIncludes = findLazyIncludes(handlerResult);
                // Create placeholder RouteEntry for each lazy include
                for (const lazyInclude of lazyIncludes) {
                    // Compute the full URL prefix (combining parent prefix if any)
                    const fullPrefix = lazyInclude.context.urlPrefix
                        ? lazyInclude.context.urlPrefix + lazyInclude.prefix
                        : lazyInclude.prefix;
                    const lazyEntry = {
                        prefix: "",
                        staticPrefix: extractStaticPrefix(fullPrefix),
                        routes: {}, // Empty until first match
                        trailingSlash: trailingSlashConfig,
                        handler: urlPatterns.handler,
                        mountIndex: mountIndex++,
                        // Lazy evaluation fields
                        lazy: true,
                        lazyPatterns: lazyInclude.patterns,
                        lazyContext: lazyInclude.context,
                        lazyEvaluated: false,
                        // Store the include prefix for evaluation
                        _lazyPrefix: lazyInclude.prefix,
                    };
                    // Insert lazy entry before any entry whose staticPrefix is a
                    // prefix of (but shorter than) this lazy entry's staticPrefix.
                    // This ensures more specific lazy includes are matched before
                    // less specific eager entries (e.g., "/href/nested" before "/href/:id").
                    const lazyPrefix = lazyEntry.staticPrefix;
                    let insertIndex = routesEntries.length;
                    if (lazyPrefix) {
                        for (let i = 0; i < routesEntries.length; i++) {
                            const existing = routesEntries[i];
                            if (lazyPrefix.startsWith(existing.staticPrefix) &&
                                lazyPrefix.length > existing.staticPrefix.length) {
                                insertIndex = i;
                                break;
                            }
                        }
                    }
                    routesEntries.splice(insertIndex, 0, lazyEntry);
                }
                // Auto-register route map for runtime reverse() usage
                registerRouteMap(mergedRouteMap);
                // Return the router (no .map() needed for UrlPatterns)
                return router;
            }
            // Legacy API: route() + map() pattern
            // If second argument exists, first is prefix
            if (maybeRoutes !== undefined) {
                return createRouteBuilder(prefixOrRoutes, maybeRoutes);
            }
            // Otherwise, first argument is routes with empty prefix
            return createRouteBuilder("", prefixOrRoutes);
        },
        use(patternOrMiddleware, middleware) {
            // Global middleware - no mount prefix
            addMiddleware(patternOrMiddleware, middleware, null);
            return router;
        },
        // Type-safe URL builder using merged route map
        // Types are tracked through the builder chain via TRoutes parameter
        // Falls back to static route names from the generated file (injected by Vite)
        reverse: createReverse(mergedRouteMap, () => staticRouteNames),
        // Expose accumulated route map for typeof extraction
        // Returns {} initially, but builder chain accumulates specific route types
        get routeMap() {
            return mergedRouteMap;
        },
        // Expose rootLayout for renderSegments
        rootLayout,
        // Expose onError callback for error handling
        onError,
        // Expose cache configuration for RSC handler
        cache,
        // Expose notFound component for RSC handler
        notFound,
        // Expose resolved theme configuration for NavigationProvider and MetaTags
        themeConfig: resolvedThemeConfig,
        // Expose warmup enabled flag for handler and client
        warmupEnabled,
        // Expose debug manifest flag for handler
        allowDebugManifest: allowDebugManifestOption,
        // Expose global middleware for RSC handler
        middleware: globalMiddleware,
        match,
        matchForPrerender,
        matchPartial,
        matchError,
        previewMatch,
        // Expose nonce provider for fetch
        nonce,
        // Expose version for fetch
        version,
        // Expose urlpatterns for runtime manifest generation
        get urlpatterns() {
            return storedUrlPatterns ?? undefined;
        },
        // Expose source file for per-router type generation
        __sourceFile,
        // RSC request handler (lazily created on first call)
        fetch: (() => {
            // Handler is created on first call and reused
            let handler = null;
            return async (request, env) => {
                // Trigger lazy import of per-router manifest data before route matching.
                // No-op if data is already loaded or no loader is registered.
                await ensureRouterManifest(routerId);
                if (!handler) {
                    // Lazy import deferred to first request to avoid dev mode issues
                    const { createRSCHandler } = await import("./rsc/handler.js");
                    handler = createRSCHandler({
                        router: router,
                        cache,
                        nonce,
                        version,
                    });
                }
                return handler(request, env);
            };
        })(),
        // Debug utility for manifest inspection
        async debugManifest() {
            const manifest = new Map();
            for (const entry of routesEntries) {
                const Store = {
                    manifest,
                    namespace: `debug.M${entry.mountIndex}`,
                    parent: null,
                    counters: {},
                    mountIndex: entry.mountIndex,
                    patterns: new Map(),
                    trailingSlash: new Map(),
                };
                await getContext().runWithStore(Store, `debug.M${entry.mountIndex}`, null, async () => {
                    const helpers = createRouteHelpers();
                    // Wrap handler execution in root layout (same as loadManifest)
                    let promiseResult = null;
                    helpers.layout(MapRootLayout, () => {
                        const result = entry.handler();
                        if (result instanceof Promise) {
                            promiseResult = result;
                            return [];
                        }
                        return result;
                    });
                    if (promiseResult !== null) {
                        const load = await promiseResult;
                        if (load && typeof load === "object" && "default" in load) {
                            const useItems = load.default;
                            if (typeof useItems === "function") {
                                useItems(helpers);
                            }
                        }
                    }
                });
            }
            return serializeManifest(manifest);
        },
    };
    // Register router in the global registry for build-time discovery
    RouterRegistry.set(routerId, router);
    // If urls option was provided, auto-register them
    if (urlsOption) {
        return router.routes(urlsOption);
    }
    return router;
}
//# sourceMappingURL=router.js.map