/// <reference types="@vitejs/plugin-rsc/types" />
/// <reference path="../vite/version.d.ts" />
/**
 * RSC Request Handler
 *
 * Main request handler for RSC rendering, server actions, loader fetching,
 * and progressive enhancement (no-JS form submissions).
 */
import { createElement } from "react";
import { renderSegments } from "../segment-system.js";
import { RouteNotFoundError, RouterError } from "../errors.js";
import { getLoaderLazy } from "../server/loader-registry.js";
import { matchMiddleware, executeMiddleware, executeLoaderMiddleware, } from "../router/middleware.js";
import { runWithRequestContext, setRequestContextParams, requireRequestContext, createRequestContext, } from "../server/request-context.js";
import * as rscDeps from "@vitejs/plugin-rsc/rsc";
import { hasBodyContent, createResponseWithMergedHeaders } from "./helpers.js";
import { generateNonce } from "./nonce.js";
import { VERSION } from "@rangojs/router:version";
import { invokeOnError } from "../router/error-handling.js";
import { getGlobalRouteMap, hasCachedManifest, setCachedManifest, getRouteTrie, setRouteTrie, getPrecomputedEntries, waitForManifestReady, getRouterManifest, getRouterTrie, setRouterManifest, setRouterTrie, } from "../route-map-builder.js";
/**
 * Build a ResponseError payload from a caught error.
 * RouterError messages are always exposed (developer-crafted).
 * Standard Error messages are hidden in production.
 */
function createResponseErrorPayload(error, isDev) {
    if (error instanceof RouterError) {
        return {
            message: error.message,
            code: error.code,
            ...(error.type ? { type: error.type } : {}),
            ...(isDev && error.stack ? { stack: error.stack } : {}),
        };
    }
    if (error instanceof Error) {
        return {
            message: isDev ? error.message : "Internal Server Error",
            ...(isDev && error.stack ? { stack: error.stack } : {}),
        };
    }
    return {
        message: isDev ? String(error) : "Internal Server Error",
    };
}
/**
 * Create an RSC request handler.
 *
 * **Recommended:** Use `router.createHandler()` instead for simpler setup:
 * ```tsx
 * const router = createRouter({ document, urls, nonce: () => true });
 * export const fetch = router.createHandler();
 * ```
 *
 * This function is still useful for advanced cases like per-request cache
 * configuration (e.g., Cloudflare Workers with ExecutionContext).
 *
 * @example Basic usage (deps and loadSSRModule have sensible defaults)
 * ```tsx
 * import { createRSCHandler } from "@rangojs/router/rsc";
 * import { router } from "./router.js";
 *
 * export default createRSCHandler({ router });
 * ```
 *
 * @example With custom deps (advanced)
 * ```tsx
 * import { createRSCHandler } from "@rangojs/router/rsc";
 * import * as rsc from "@vitejs/plugin-rsc/rsc";
 * import { router } from "./router.js";
 *
 * export default createRSCHandler({
 *   router,
 *   deps: rsc,
 *   loadSSRModule: () => import.meta.viteRsc.loadModule("ssr", "index"),
 * });
 * ```
 */
export function createRSCHandler(options) {
    const { router, version = VERSION, nonce: nonceProvider } = options;
    // Use provided deps or default to @vitejs/plugin-rsc/rsc exports
    const deps = options.deps ?? rscDeps;
    const { renderToReadableStream, decodeReply, createTemporaryReferenceSet, loadServerAction, decodeAction, decodeFormState, } = deps;
    // Use provided loadSSRModule or default to vite RSC module loader
    const loadSSRModule = options.loadSSRModule ??
        (() => import.meta.viteRsc.loadModule("ssr", "index"));
    /**
     * 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(router.onError, error, phase, context, "RSC");
    }
    return async function handler(request, env = {}) {
        const handlerStart = performance.now();
        // Connection warmup: return 204 immediately before any processing
        if (router.warmupEnabled && request.method === "HEAD") {
            const warmupUrl = new URL(request.url);
            if (warmupUrl.searchParams.has("_rsc_warmup")) {
                return new Response(null, { status: 204 });
            }
        }
        // Resolve nonce if provider is set
        const nonceStart = performance.now();
        let nonce;
        if (nonceProvider) {
            const result = await nonceProvider(request, env);
            nonce = result === true ? generateNonce() : result;
        }
        const nonceDur = performance.now() - nonceStart;
        const url = new URL(request.url);
        // Match global middleware
        const mwMatchStart = performance.now();
        const matchedMiddleware = matchMiddleware(url.pathname, router.middleware);
        const mwMatchDur = performance.now() - mwMatchStart;
        // Shared variables between middleware and route handlers
        // Initialize from env.Variables if provided (allows pre-seeding from worker entry)
        const variables = {
            ...(env?.Variables ?? {}),
        };
        // Store nonce in variables so middleware can access via ctx.get('nonce')
        if (nonce) {
            variables.nonce = nonce;
        }
        // Resolve cache store configuration
        // Priority: options.cache (handler override) > router.cache (router default)
        // Store is enabled only if: config provided, enabled, and no ?__no_cache query param
        let cacheStore = undefined;
        const cacheOption = options.cache ?? router.cache;
        if (cacheOption && !url.searchParams.has("__no_cache")) {
            const cacheConfig = typeof cacheOption === "function" ? cacheOption(env) : cacheOption;
            if (cacheConfig.enabled !== false) {
                cacheStore = cacheConfig.store;
            }
        }
        // Route manifest is populated at startup via the virtual module
        // (virtual:rsc-router/routes-manifest). In build/production, it's inlined
        // into the bundle. In dev mode (Node), the discovery plugin populates it
        // via setManifestReadyPromise(). In dev mode (Cloudflare), Miniflare runs
        // in a separate isolate where module-level state doesn't carry over, so
        // we generate inline from the router's urlpatterns.
        //
        // In multi-router setups (e.g. createHostRouter), each router must have
        // its own per-router manifest. We check per-router data first: even if
        // the global manifest was set by a different router, this router still
        // needs its own trie and manifest for correct matching.
        const manifestCacheStart = performance.now();
        const hasRouterData = getRouterManifest(router.id) !== undefined;
        if (!hasRouterData) {
            if (!hasCachedManifest()) {
                const readyPromise = waitForManifestReady();
                if (readyPromise) {
                    await readyPromise;
                }
            }
            if (!getRouterManifest(router.id) && router.urlpatterns) {
                // Cloudflare dev: generate manifest inline for this router.
                // Each router generates its own manifest independently so
                // multi-router setups (host routing) work correctly.
                const { generateManifest } = await import("../build/generate-manifest.js");
                const generated = generateManifest(router.urlpatterns);
                if (generated._routeAncestry &&
                    Object.keys(generated._routeAncestry).length > 0) {
                    const { buildRouteTrie } = await import("../build/route-trie.js");
                    // Map each route to its include() staticPrefix so the trie
                    // returns the correct sp for lazy entry lookup in findMatch.
                    const routeToStaticPrefix = {};
                    for (const name of Object.keys(generated.routeManifest)) {
                        routeToStaticPrefix[name] = "";
                    }
                    // Override with prefix from include() entries so the trie
                    // returns the correct sp for lazy entry lookup in findMatch.
                    // Walk recursively to include routes in nested includes.
                    if (generated.prefixTree) {
                        const visitPrefixNode = (node) => {
                            const sp = node.staticPrefix || "";
                            for (const route of (node.routes || [])) {
                                routeToStaticPrefix[route] = sp;
                            }
                            for (const child of Object.values(node.children || {})) {
                                visitPrefixNode(child);
                            }
                        };
                        for (const node of Object.values(generated.prefixTree)) {
                            visitPrefixNode(node);
                        }
                    }
                    const trie = buildRouteTrie(generated.routeManifest, generated._routeAncestry, routeToStaticPrefix, generated.routeTrailingSlash, generated.prerenderRoutes ? new Set(generated.prerenderRoutes) : undefined, generated.passthroughRoutes ? new Set(generated.passthroughRoutes) : undefined, generated.responseTypeRoutes);
                    setRouterTrie(router.id, trie);
                    // Set global trie only if not already set by another router
                    if (!getRouteTrie()) {
                        setRouteTrie(trie);
                    }
                }
                setRouterManifest(router.id, generated.routeManifest);
                // Merge into global manifest (needed for reverse/href across routers)
                const existing = hasCachedManifest() ? getGlobalRouteMap() : {};
                setCachedManifest({ ...existing, ...generated.routeManifest });
            }
            if (!getRouterManifest(router.id) && !hasCachedManifest()) {
                throw new Error('Route manifest not available. Ensure "virtual:rsc-router/routes-manifest" is imported in your entry file.');
            }
        }
        const manifestCacheDur = performance.now() - manifestCacheStart;
        // Note: Route map for useHref() is loaded lazily via getGlobalRouteMap()
        // This allows it to include all routes from lazy includes after manifest loading
        // Create unified request context with all methods
        // Includes: stub response, handle store, loader memoization, use(), cookies, headers, cache store
        // params starts empty, populated after route matching via setRequestContextParams
        const ctxCreateStart = performance.now();
        const requestContext = createRequestContext({
            env,
            request,
            url,
            variables,
            cacheStore,
            executionContext: env.ctx,
            themeConfig: router.themeConfig,
        });
        const ctxCreateDur = performance.now() - ctxCreateStart;
        // Accumulate handler-level timing for Server-Timing header
        const handlerTiming = [
            `handler-nonce;dur=${nonceDur.toFixed(2)}`,
            `handler-mw-match;dur=${mwMatchDur.toFixed(2)}`,
            `handler-manifest-cache;dur=${manifestCacheDur.toFixed(2)}`,
            `handler-ctx-create;dur=${ctxCreateDur.toFixed(2)}`,
        ];
        // Store timing data in variables for downstream access
        variables.__handlerTiming = handlerTiming;
        variables.__handlerStart = handlerStart;
        // Wrap entire request handling in request context
        // Makes context available via getRequestContext() throughout:
        // - Middleware execution
        // - Route handlers and loaders
        // - Server components during rendering
        // - Error boundaries
        // - Streaming
        return runWithRequestContext(requestContext, async () => {
            // Core handler logic (wrapped by middleware)
            const coreHandler = async () => {
                return coreRequestHandler(request, env, url, variables, nonce);
            };
            // Execute middleware chain if any, otherwise call core handler directly
            if (matchedMiddleware.length > 0) {
                return executeMiddleware(matchedMiddleware, request, env, variables, coreHandler);
            }
            return coreHandler();
        });
    };
    // Core request handling logic (separated for middleware wrapping)
    async function coreRequestHandler(request, env, url, variables, nonce) {
        // First, check for route-level middleware
        const previewStart = performance.now();
        const preview = await router.previewMatch(request, env);
        const previewDur = performance.now() - previewStart;
        const handlerTiming = variables.__handlerTiming || [];
        handlerTiming.push(`handler-preview-match;dur=${previewDur.toFixed(2)}`);
        // Response route short-circuit: skip entire RSC pipeline
        if (preview?.responseType && preview.handler) {
            const isPartial = url.searchParams.has("_rsc_partial");
            // Partial requests (client-side navigation) to response routes
            // get X-RSC-Reload to trigger hard navigation in the browser
            if (isPartial) {
                const cleanUrl = new URL(url);
                cleanUrl.searchParams.delete("_rsc_partial");
                cleanUrl.searchParams.delete("_rsc_segments");
                cleanUrl.searchParams.delete("_rsc_v");
                cleanUrl.searchParams.delete("_rsc_stale");
                cleanUrl.searchParams.delete("_rsc_action");
                cleanUrl.searchParams.delete("_rsc_prev");
                return createResponseWithMergedHeaders(null, {
                    status: 200,
                    headers: {
                        "X-RSC-Reload": cleanUrl.toString(),
                        "content-type": "text/x-component;charset=utf-8",
                    },
                });
            }
            // Build lightweight context for response handler
            const bindings = env?.Bindings ?? env;
            const reqCtx = requireRequestContext();
            const responseHandlerCtx = {
                request,
                params: preview.params || {},
                env: bindings,
                searchParams: url.searchParams,
                url,
                pathname: url.pathname,
                href: (name, hrefParams) => {
                    if (name.startsWith("/")) {
                        if (!hrefParams)
                            return name;
                        return name.replace(/:([^/]+)/g, (_, key) => {
                            const value = hrefParams[key];
                            if (value === undefined)
                                throw new Error(`Missing param "${key}" for path "${name}"`);
                            return encodeURIComponent(value);
                        });
                    }
                    return name;
                },
                get: (key) => variables[key],
                header: (name, value) => reqCtx.header(name, value),
                setCookie: (name, value, options) => reqCtx.setCookie(name, value, options),
            };
            // Call handler directly, wrapped by route middleware if present
            const callHandler = async () => {
                // JSON response routes: wrap in { data } / { error } envelope
                if (preview.responseType === "json") {
                    const errorCtx = { request, url, env };
                    try {
                        const result = await preview.handler(responseHandlerCtx);
                        if (result instanceof Response) {
                            const mergedHeaders = {};
                            result.headers.forEach((value, key) => {
                                mergedHeaders[key] = value;
                            });
                            return createResponseWithMergedHeaders(result.body, {
                                status: result.status,
                                headers: mergedHeaders,
                            });
                        }
                        return createResponseWithMergedHeaders(JSON.stringify({ data: result }), { status: 200, headers: { "content-type": "application/json;charset=utf-8" } });
                    }
                    catch (error) {
                        callOnError(error, "handler", errorCtx);
                        const isDev = process.env.NODE_ENV !== "production";
                        const status = error instanceof RouterError ? error.status : 500;
                        return createResponseWithMergedHeaders(JSON.stringify({ error: createResponseErrorPayload(error, isDev) }), { status, headers: { "content-type": "application/json;charset=utf-8" } });
                    }
                }
                // Non-JSON response routes: catch errors and return plain Response
                const errorCtx = { request, url, env };
                try {
                    const result = await preview.handler(responseHandlerCtx);
                    if (result instanceof Response) {
                        // Handler returned a Response directly -- pass through
                        const mergedHeaders = {};
                        result.headers.forEach((value, key) => {
                            mergedHeaders[key] = value;
                        });
                        return createResponseWithMergedHeaders(result.body, {
                            status: result.status,
                            headers: mergedHeaders,
                        });
                    }
                    // Auto-wrap based on response type tag
                    switch (preview.responseType) {
                        case "text":
                            return createResponseWithMergedHeaders(String(result), { status: 200, headers: { "content-type": "text/plain;charset=utf-8" } });
                        case "html":
                            return createResponseWithMergedHeaders(String(result), { status: 200, headers: { "content-type": "text/html;charset=utf-8" } });
                        case "xml":
                            return createResponseWithMergedHeaders(String(result), { status: 200, headers: { "content-type": "application/xml;charset=utf-8" } });
                        case "md":
                            return createResponseWithMergedHeaders(String(result), { status: 200, headers: { "content-type": "text/markdown;charset=utf-8" } });
                        default:
                            // image, stream, any -- must return Response
                            throw new Error(`Response route handler for "${preview.responseType}" must return a Response object, got ${typeof result}`);
                    }
                }
                catch (error) {
                    callOnError(error, "handler", errorCtx);
                    const isDev = process.env.NODE_ENV !== "production";
                    const status = error instanceof RouterError ? error.status : 500;
                    const message = error instanceof RouterError
                        ? error.message
                        : isDev && error instanceof Error
                            ? error.message
                            : "Internal Server Error";
                    return createResponseWithMergedHeaders(message, {
                        status,
                        headers: { "content-type": "text/plain;charset=utf-8" },
                    });
                }
            };
            // Wrap callHandler to append Vary: Accept on content-negotiated responses
            const callHandlerWithVary = async () => {
                const response = await callHandler();
                if (preview.negotiated) {
                    response.headers.append("Vary", "Accept");
                }
                return response;
            };
            if (preview.routeMiddleware && preview.routeMiddleware.length > 0) {
                const middlewareEntries = preview.routeMiddleware.map((mw) => ({
                    entry: {
                        pattern: null,
                        regex: null,
                        paramNames: [],
                        handler: mw.handler,
                        mountPrefix: null,
                    },
                    params: mw.params,
                }));
                return executeMiddleware(middlewareEntries, request, env, variables, callHandlerWithVary);
            }
            return callHandlerWithVary();
        }
        // Wrap RSC handler to append Vary: Accept on content-negotiated routes
        const rscHandler = async () => {
            const response = await coreRequestHandlerInner(request, env, url, variables, nonce);
            if (preview?.negotiated) {
                response.headers.append("Vary", "Accept");
            }
            return response;
        };
        if (preview?.routeMiddleware && preview.routeMiddleware.length > 0) {
            // Convert route middleware to app middleware format for execution
            const middlewareEntries = preview.routeMiddleware.map((mw) => ({
                entry: {
                    pattern: null,
                    regex: null,
                    paramNames: [],
                    handler: mw.handler,
                    mountPrefix: null,
                },
                params: mw.params,
            }));
            // Execute route middleware wrapping the actual request handling
            return executeMiddleware(middlewareEntries, request, env, variables, rscHandler);
        }
        // No route middleware, proceed directly
        return rscHandler();
    }
    // Inner request handler (actual RSC logic, wrapped by route middleware if any)
    async function coreRequestHandlerInner(request, env, url, variables, nonce) {
        const isPartial = url.searchParams.has("_rsc_partial");
        const isAction = request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
        const actionId = request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
        // Version mismatch detection - client may have stale code after HMR/deployment
        // If versions don't match, tell the client to reload
        const clientVersion = url.searchParams.get("_rsc_v");
        if (version && clientVersion && clientVersion !== version) {
            console.log(`[RSC] Version mismatch: client=${clientVersion}, server=${version}. Forcing reload.`);
            // Clean URL by removing RSC params
            const cleanUrl = new URL(url);
            cleanUrl.searchParams.delete("_rsc_partial");
            cleanUrl.searchParams.delete("_rsc_segments");
            cleanUrl.searchParams.delete("_rsc_v");
            cleanUrl.searchParams.delete("_rsc_stale");
            cleanUrl.searchParams.delete("_rsc_action");
            cleanUrl.searchParams.delete("_rsc_prev");
            // For actions, reload current page (referer)
            // For navigation, load the target URL
            const reloadUrl = isAction
                ? request.headers.get("referer") || cleanUrl.toString()
                : cleanUrl.toString();
            // Return special response that tells client to reload
            return createResponseWithMergedHeaders(null, {
                status: 200,
                headers: {
                    "X-RSC-Reload": reloadUrl,
                    "content-type": "text/x-component;charset=utf-8",
                },
            });
        }
        // Debug manifest endpoint: ?__debug_manifest on any route.
        // Always available in dev, requires allowDebugManifest option in production.
        const isDev = process.env.NODE_ENV !== "production";
        if (url.searchParams.has("__debug_manifest") &&
            (isDev || router.allowDebugManifest)) {
            const trie = getRouterTrie(router.id) ?? getRouteTrie();
            const routeManifest = getRouterManifest(router.id) ?? getGlobalRouteMap();
            const { extractAncestryFromTrie } = await import("../build/route-trie.js");
            return new Response(JSON.stringify({
                routerId: router.id,
                routeManifest,
                routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
                routeTrie: trie,
                precomputedEntries: getPrecomputedEntries(),
            }, null, 2), {
                headers: { "Content-Type": "application/json" },
            });
        }
        // Get handle store from request context (created at start of request)
        const handleStore = requireRequestContext()._handleStore;
        try {
            // ============================================================================
            // PROGRESSIVE ENHANCEMENT: No-JS Form Submissions
            // ============================================================================
            const progressiveResult = await handleProgressiveEnhancement(request, env, url, isAction, handleStore, nonce);
            if (progressiveResult) {
                return progressiveResult;
            }
            // ============================================================================
            // SERVER ACTION EXECUTION (JavaScript-enabled client)
            // ============================================================================
            if (isAction && actionId) {
                return handleServerAction(request, env, url, actionId, handleStore);
            }
            // ============================================================================
            // LOADER FETCH EXECUTION (data fetching with RSC serialization)
            // ============================================================================
            const isLoaderRequest = url.searchParams.has("_rsc_loader");
            if (isLoaderRequest) {
                return handleLoaderFetch(request, env, url, variables);
            }
            // ============================================================================
            // REGULAR RSC RENDERING (Navigation)
            // ============================================================================
            // Note: Must use "return await" for try/catch to catch async rejections
            return await handleRscRendering(request, env, url, isPartial, handleStore, nonce);
        }
        catch (error) {
            // Check if middleware/handler returned Response
            if (error instanceof Response) {
                // During partial (client-side navigation), a 200 Response from a handler
                // means the route serves raw content (JSON, text, etc.), not JSX.
                // Signal the browser to hard-navigate so it renders the raw response.
                // Only for 200 — redirects (3xx) work already because the browser follows
                // them automatically to a URL that serves Flight data.
                if (isPartial && error.status === 200) {
                    console.warn(`[RSC] Route handler at ${url.pathname} returned a Response during client-side navigation. ` +
                        `Falling back to hard navigation. Use data-external on the <Link> to avoid the extra round-trip.`);
                    const cleanUrl = new URL(url);
                    cleanUrl.searchParams.delete("_rsc_partial");
                    cleanUrl.searchParams.delete("_rsc_segments");
                    cleanUrl.searchParams.delete("_rsc_v");
                    cleanUrl.searchParams.delete("_rsc_stale");
                    cleanUrl.searchParams.delete("_rsc_action");
                    cleanUrl.searchParams.delete("_rsc_prev");
                    return createResponseWithMergedHeaders(null, {
                        status: 200,
                        headers: {
                            "X-RSC-Reload": cleanUrl.toString(),
                            "content-type": "text/x-component;charset=utf-8",
                        },
                    });
                }
                return error;
            }
            // Render 404 page for unmatched routes
            // Check both instanceof and error.name for cross-bundle compatibility
            const isRouteNotFound = error instanceof RouteNotFoundError ||
                (error instanceof Error && error.name === "RouteNotFoundError");
            if (isRouteNotFound) {
                callOnError(error, "routing", {
                    request,
                    url,
                    env,
                    handledByBoundary: true, // Handled by notFound component
                });
                // Get notFound component from router options or use default
                const notFoundOption = router.notFound;
                const notFoundComponent = typeof notFoundOption === "function"
                    ? notFoundOption({ pathname: url.pathname })
                    : (notFoundOption ?? createElement("h1", null, "Not Found"));
                // Create a simple segment for the 404 page
                const notFoundSegment = {
                    id: "notFound",
                    namespace: "notFound",
                    type: "route",
                    index: 0,
                    component: notFoundComponent,
                    params: {},
                };
                // Render with rootLayout to maintain app shell
                const root = await renderSegments([notFoundSegment], {
                    rootLayout: router.rootLayout,
                    // No routeName for not-found routes
                });
                const payload = {
                    root,
                    metadata: {
                        pathname: url.pathname,
                        segments: [notFoundSegment],
                        matched: [],
                        diff: [],
                        isPartial: false,
                        handles: handleStore.stream(),
                        version,
                        themeConfig: router.themeConfig,
                        warmupEnabled: router.warmupEnabled,
                        initialTheme: requireRequestContext().theme,
                        // No routeName for not-found routes
                    },
                };
                const rscStream = renderToReadableStream(payload);
                // Determine if this is an RSC request or HTML request
                const isRscRequest = (!request.headers.get("accept")?.includes("text/html") &&
                    !url.searchParams.has("__html")) ||
                    url.searchParams.has("__rsc");
                if (isRscRequest) {
                    return createResponseWithMergedHeaders(rscStream, {
                        status: 404,
                        headers: { "content-type": "text/x-component;charset=utf-8" },
                    });
                }
                // Delegate to SSR for HTML response
                const ssrModule = await loadSSRModule();
                const htmlStream = await ssrModule.renderHTML(rscStream, { nonce });
                return createResponseWithMergedHeaders(htmlStream, {
                    status: 404,
                    headers: { "content-type": "text/html;charset=utf-8" },
                });
            }
            // Report unhandled errors
            callOnError(error, "routing", {
                request,
                url,
                env,
                handledByBoundary: false,
            });
            console.error(`[RSC] Error:`, error);
            throw error;
        }
    }
    // ============================================================================
    // PROGRESSIVE ENHANCEMENT HANDLER
    // When JavaScript is disabled, React renders forms with hidden fields
    // ($ACTION_REF_*, $ACTION_KEY) containing the action reference.
    // We detect these and return HTML instead of RSC stream.
    // ============================================================================
    async function handleProgressiveEnhancement(request, env, url, isAction, handleStore, nonce) {
        const contentType = request.headers.get("content-type") || "";
        const isFormSubmission = contentType.includes("multipart/form-data") ||
            contentType.includes("application/x-www-form-urlencoded");
        if (request.method !== "POST" || isAction || !isFormSubmission) {
            return null;
        }
        // Clone the request to read FormData without consuming it
        const formData = await request.clone().formData();
        // Look for React's progressive enhancement hidden fields
        let isDirectAction = false;
        let isUseActionState = false;
        let directActionId = null;
        formData.forEach((_value, key) => {
            if (key.startsWith("$ACTION_ID_")) {
                isDirectAction = true;
                directActionId = key.slice("$ACTION_ID_".length);
            }
            else if (key.startsWith("$ACTION_REF_")) {
                isUseActionState = true;
            }
        });
        if (!isDirectAction && !isUseActionState) {
            return null;
        }
        // Execute action and return HTML
        let actionResult = undefined;
        let reactFormState = null;
        if (isUseActionState) {
            try {
                const boundAction = await decodeAction(formData);
                actionResult = await boundAction();
            }
            catch (error) {
                callOnError(error, "action", {
                    request,
                    url,
                    env,
                    handledByBoundary: false,
                });
                console.error("[RSC] Progressive enhancement action error:", error);
            }
        }
        else if (isDirectAction && directActionId) {
            const temporaryReferences = createTemporaryReferenceSet();
            let args = [];
            try {
                args = await decodeReply(formData, { temporaryReferences });
            }
            catch {
                args = [formData];
            }
            try {
                const loadedAction = await loadServerAction(directActionId);
                actionResult = await loadedAction.apply(null, args);
            }
            catch (error) {
                callOnError(error, "action", {
                    request,
                    url,
                    env,
                    actionId: directActionId,
                    handledByBoundary: false,
                });
                console.error("[RSC] Progressive enhancement action error:", error);
            }
        }
        // Decode form state for useActionState progressive enhancement
        try {
            reactFormState = await decodeFormState(actionResult, formData);
        }
        catch (error) {
            callOnError(error, "action", {
                request,
                url,
                env,
                handledByBoundary: false,
            });
            console.error("[RSC] Failed to decode form state:", error);
        }
        // Re-render the page and return HTML
        const renderRequest = new Request(url.toString(), {
            method: "GET",
            headers: new Headers({ accept: "text/html" }),
        });
        const match = await router.match(renderRequest, env);
        if (match.redirect) {
            return createResponseWithMergedHeaders(null, {
                status: 308,
                headers: { Location: match.redirect },
            });
        }
        const root = renderSegments(match.segments, {
            rootLayout: router.rootLayout,
        });
        const payload = {
            root,
            metadata: {
                pathname: url.pathname,
                segments: match.segments,
                matched: match.matched,
                diff: match.diff,
                isPartial: false,
                rootLayout: router.rootLayout,
                handles: handleStore.stream(),
                version,
                themeConfig: router.themeConfig,
                warmupEnabled: router.warmupEnabled,
                initialTheme: requireRequestContext().theme,
            },
            formState: actionResult,
        };
        const rscStream = renderToReadableStream(payload);
        const ssrModule = await loadSSRModule();
        const htmlStream = await ssrModule.renderHTML(rscStream, {
            formState: reactFormState,
            nonce,
        });
        return createResponseWithMergedHeaders(htmlStream, {
            headers: { "content-type": "text/html;charset=utf-8" },
        });
    }
    // ============================================================================
    // SERVER ACTION HANDLER
    // ============================================================================
    async function handleServerAction(request, env, url, actionId, handleStore) {
        const temporaryReferences = createTemporaryReferenceSet();
        // Decode action arguments from request body
        const contentType = request.headers.get("content-type") || "";
        let args = [];
        let actionFormData;
        try {
            const body = contentType.includes("multipart/form-data")
                ? await request.formData()
                : await request.text();
            if (body instanceof FormData) {
                actionFormData = body;
            }
            if (hasBodyContent(body)) {
                args = await decodeReply(body, { temporaryReferences });
            }
        }
        catch (error) {
            callOnError(error, "action", {
                request,
                url,
                env,
                actionId,
                handledByBoundary: false,
            });
            throw new Error(`Failed to decode action arguments: ${error}`, {
                cause: error,
            });
        }
        // Execute the server action
        let returnValue;
        let actionStatus = 200;
        let loadedAction;
        try {
            loadedAction = await loadServerAction(actionId);
            const data = await loadedAction.apply(null, args);
            returnValue = { ok: true, data };
        }
        catch (error) {
            returnValue = { ok: false, data: error };
            actionStatus = 500;
            // Try to render error boundary
            const errorResult = await router.matchError(request, env, error, "route");
            // Report the action error (handledByBoundary indicates if error boundary will render)
            callOnError(error, "action", {
                request,
                url,
                env,
                actionId,
                handledByBoundary: !!errorResult,
            });
            if (errorResult) {
                setRequestContextParams(errorResult.params);
                const payload = {
                    root: null,
                    metadata: {
                        pathname: url.pathname,
                        segments: errorResult.segments,
                        isPartial: true,
                        matched: errorResult.matched,
                        diff: errorResult.diff,
                        isError: true,
                        handles: handleStore.stream(),
                        version,
                    },
                    returnValue,
                };
                const rscStream = renderToReadableStream(payload, {
                    temporaryReferences,
                });
                return createResponseWithMergedHeaders(rscStream, {
                    status: actionStatus,
                    headers: { "content-type": "text/x-component;charset=utf-8" },
                });
            }
        }
        // Revalidate after action
        const resolvedActionId = loadedAction?.$id ??
            loadedAction?.$$id ??
            actionId;
        const actionContext = {
            actionId: resolvedActionId,
            actionUrl: new URL(request.url),
            actionResult: returnValue.data,
            formData: actionFormData,
        };
        const matchResult = await router.matchPartial(request, env, actionContext);
        if (!matchResult) {
            // Fall back to full render
            const fullMatch = await router.match(request, env);
            setRequestContextParams(fullMatch.params);
            if (fullMatch.redirect) {
                return createResponseWithMergedHeaders(null, {
                    status: 308,
                    headers: { Location: fullMatch.redirect },
                });
            }
            const renderStart = performance.now();
            const root = renderSegments(fullMatch.segments, {
                rootLayout: router.rootLayout,
                isAction: true,
            });
            const renderDuration = performance.now() - renderStart;
            const serverTiming = fullMatch.serverTiming
                ? `${fullMatch.serverTiming}, rendering;dur=${renderDuration.toFixed(2)}`
                : `rendering;dur=${renderDuration.toFixed(2)}`;
            const payload = {
                root,
                metadata: {
                    pathname: url.pathname,
                    segments: fullMatch.segments,
                    matched: fullMatch.matched,
                    diff: fullMatch.diff,
                    handles: handleStore.stream(),
                    version,
                },
                returnValue,
            };
            const rscStream = renderToReadableStream(payload, {
                temporaryReferences,
            });
            const headers = {
                "content-type": "text/x-component;charset=utf-8",
            };
            if (serverTiming) {
                headers["Server-Timing"] = serverTiming;
            }
            return createResponseWithMergedHeaders(rscStream, {
                status: actionStatus,
                headers,
            });
        }
        // Return updated segments
        setRequestContextParams(matchResult.params);
        const renderStart = performance.now();
        const renderDuration = performance.now() - renderStart;
        const serverTiming = matchResult.serverTiming
            ? `${matchResult.serverTiming}, rendering;dur=${renderDuration.toFixed(2)}`
            : `rendering;dur=${renderDuration.toFixed(2)}`;
        const payload = {
            root: null,
            metadata: {
                pathname: url.pathname,
                segments: matchResult.segments,
                isPartial: true,
                matched: matchResult.matched,
                diff: matchResult.diff,
                slots: matchResult.slots,
                handles: handleStore.stream(),
                version,
            },
            returnValue,
        };
        const rscStream = renderToReadableStream(payload, {
            temporaryReferences,
        });
        const actionHeaders = {
            "content-type": "text/x-component;charset=utf-8",
        };
        if (serverTiming) {
            actionHeaders["Server-Timing"] = serverTiming;
        }
        return createResponseWithMergedHeaders(rscStream, {
            status: actionStatus,
            headers: actionHeaders,
        });
    }
    // ============================================================================
    // LOADER FETCH HANDLER
    // Supports GET (params in query string) and POST/PUT/PATCH/DELETE (JSON body)
    // ============================================================================
    async function handleLoaderFetch(request, env, url, variables) {
        const loaderId = url.searchParams.get("_rsc_loader");
        if (!loaderId) {
            return createResponseWithMergedHeaders("Missing _rsc_loader parameter", {
                status: 400,
            });
        }
        // Look up loader lazily
        const registeredLoader = await getLoaderLazy(loaderId);
        if (!registeredLoader) {
            return createResponseWithMergedHeaders(`Loader "${loaderId}" not found in registry`, { status: 404 });
        }
        // Parse params and body based on request method
        let loaderParams = {};
        let loaderBody = undefined;
        const isBodyMethod = request.method !== "GET" && request.method !== "HEAD";
        if (isBodyMethod) {
            try {
                const contentType = request.headers.get("content-type") || "";
                if (contentType.includes("application/json")) {
                    const jsonBody = (await request.json());
                    loaderParams = jsonBody.params ?? {};
                    loaderBody = jsonBody.body;
                }
            }
            catch {
                return createResponseWithMergedHeaders("Invalid JSON body", {
                    status: 400,
                });
            }
        }
        else {
            const loaderParamsJson = url.searchParams.get("_rsc_loader_params");
            if (loaderParamsJson) {
                try {
                    loaderParams = JSON.parse(loaderParamsJson);
                }
                catch {
                    return createResponseWithMergedHeaders("Invalid _rsc_loader_params JSON", { status: 400 });
                }
            }
        }
        // Execute the loader with middleware
        try {
            const { fn, middleware } = registeredLoader;
            return await executeLoaderMiddleware(middleware, request, env, loaderParams, variables, async () => {
                const ctx = requireRequestContext();
                const loaderCtx = {
                    ...ctx,
                    params: loaderParams,
                    body: loaderBody,
                };
                const result = await fn(loaderCtx);
                const loaderPayload = { loaderResult: result };
                const rscStream = renderToReadableStream(loaderPayload);
                return createResponseWithMergedHeaders(rscStream, {
                    headers: { "content-type": "text/x-component;charset=utf-8" },
                });
            });
        }
        catch (error) {
            const err = error instanceof Error ? error : new Error(String(error));
            const isDev = process.env.NODE_ENV !== "production";
            console.error("[RSC] Loader error:", error);
            callOnError(error, "loader", {
                request,
                url,
                env,
                loaderName: loaderId,
                handledByBoundary: false,
            });
            const errorPayload = {
                loaderResult: null,
                loaderError: {
                    message: isDev ? err.message : "An error occurred",
                    name: err.name,
                },
            };
            const rscStream = renderToReadableStream(errorPayload);
            return createResponseWithMergedHeaders(rscStream, {
                status: 500,
                headers: { "content-type": "text/x-component;charset=utf-8" },
            });
        }
    }
    // ============================================================================
    // RSC RENDERING HANDLER (Navigation)
    // ============================================================================
    async function handleRscRendering(request, env, url, isPartial, handleStore, nonce) {
        // Retrieve handler-level timing from variables
        const reqCtx = requireRequestContext();
        const handlerTimingArr = reqCtx.var.__handlerTiming || [];
        const handlerStart = reqCtx.var.__handlerStart || 0;
        let payload;
        let serverTiming;
        if (isPartial) {
            // Partial render (navigation)
            const result = await router.matchPartial(request, env);
            if (!result) {
                // Fall back to full render
                const match = await router.match(request, env);
                setRequestContextParams(match.params);
                if (match.redirect) {
                    return createResponseWithMergedHeaders(null, {
                        status: 308,
                        headers: { Location: match.redirect },
                    });
                }
                const renderStart = performance.now();
                const root = renderSegments(match.segments, {
                    rootLayout: router.rootLayout,
                });
                const renderDuration = performance.now() - renderStart;
                serverTiming = match.serverTiming
                    ? `${match.serverTiming}, rendering;dur=${renderDuration.toFixed(2)}`
                    : `rendering;dur=${renderDuration.toFixed(2)}`;
                payload = {
                    root,
                    metadata: {
                        pathname: url.pathname,
                        segments: match.segments,
                        matched: match.matched,
                        diff: match.diff,
                        isPartial: false,
                        handles: handleStore.stream(),
                        version,
                        themeConfig: router.themeConfig,
                        initialTheme: reqCtx.theme,
                    },
                };
            }
            else {
                setRequestContextParams(result.params);
                serverTiming = result.serverTiming;
                payload = {
                    root: null,
                    metadata: {
                        pathname: url.pathname,
                        segments: result.segments,
                        matched: result.matched,
                        diff: result.diff,
                        isPartial: true,
                        slots: result.slots,
                        handles: handleStore.stream(),
                        version,
                    },
                };
            }
        }
        else {
            // Full render (initial page load)
            const match = await router.match(request, env);
            setRequestContextParams(match.params);
            if (match.redirect) {
                return createResponseWithMergedHeaders(null, {
                    status: 308,
                    headers: { Location: match.redirect },
                });
            }
            // Caching is now handled in router.match() via cache provider in request context
            // match.segments already contains cached or fresh segments as appropriate
            if (url.searchParams.has("__prerender_collect")) {
                // Build-time prerender collection: serialize segments and handle data
                // to JSON for storage as build artifacts. At runtime the worker
                // deserializes these and feeds them through the normal segment pipeline.
                const nonLoaderSegments = match.segments.filter((s) => s.type !== "loader");
                await handleStore.settled;
                const { serializeSegments } = await import("../cache/cache-scope.js");
                const serializedSegments = await serializeSegments(nonLoaderSegments);
                const handles = {};
                for (const seg of nonLoaderSegments) {
                    const segHandles = handleStore.getDataForSegment(seg.id);
                    if (Object.keys(segHandles).length > 0) {
                        handles[seg.id] = segHandles;
                    }
                }
                return new Response(JSON.stringify({
                    segments: serializedSegments,
                    handles,
                    routeName: match.routeName,
                    params: match.params,
                }), { headers: { "Content-Type": "application/json" } });
            }
            else {
                const renderStart = performance.now();
                const root = renderSegments(match.segments, {
                    rootLayout: router.rootLayout,
                });
                const renderDuration = performance.now() - renderStart;
                serverTiming = match.serverTiming
                    ? `${match.serverTiming}, rendering;dur=${renderDuration.toFixed(2)}`
                    : `rendering;dur=${renderDuration.toFixed(2)}`;
                payload = {
                    root,
                    metadata: {
                        pathname: url.pathname,
                        segments: match.segments,
                        matched: match.matched,
                        diff: match.diff,
                        isPartial: false,
                        rootLayout: router.rootLayout,
                        handles: handleStore.stream(),
                        version,
                        themeConfig: router.themeConfig,
                        initialTheme: reqCtx.theme,
                    },
                };
            }
        }
        // Serialize to RSC stream
        const rscSerializeStart = performance.now();
        const rscStream = renderToReadableStream(payload);
        const rscSerializeDur = performance.now() - rscSerializeStart;
        // Determine if this is an RSC request or HTML request
        const isRscRequest = (!request.headers.get("accept")?.includes("text/html") &&
            !url.searchParams.has("__html")) ||
            url.searchParams.has("__rsc");
        // Build complete Server-Timing: handler phases + match/manifest + rendering + RSC serialize
        const timingParts = [...handlerTimingArr];
        if (serverTiming) {
            timingParts.push(serverTiming);
        }
        timingParts.push(`rsc-serialize;dur=${rscSerializeDur.toFixed(2)}`);
        if (isRscRequest) {
            const fullTiming = timingParts.join(", ");
            const rscHeaders = {
                "content-type": "text/x-component;charset=utf-8",
                vary: "accept",
            };
            if (fullTiming) {
                rscHeaders["Server-Timing"] = fullTiming;
            }
            return createResponseWithMergedHeaders(rscStream, {
                headers: rscHeaders,
            });
        }
        // Delegate to SSR for HTML response
        const ssrModuleStart = performance.now();
        const ssrModule = await loadSSRModule();
        const ssrModuleDur = performance.now() - ssrModuleStart;
        timingParts.push(`ssr-module-load;dur=${ssrModuleDur.toFixed(2)}`);
        const ssrRenderStart = performance.now();
        const htmlStream = await ssrModule.renderHTML(rscStream, { nonce });
        const ssrRenderDur = performance.now() - ssrRenderStart;
        timingParts.push(`ssr-render-html;dur=${ssrRenderDur.toFixed(2)}`);
        // Add total handler duration
        if (handlerStart) {
            const totalHandler = performance.now() - handlerStart;
            timingParts.push(`handler-total;dur=${totalHandler.toFixed(2)}`);
        }
        const fullTiming = timingParts.join(", ");
        const htmlHeaders = {
            "content-type": "text/html;charset=utf-8",
        };
        if (fullTiming) {
            htmlHeaders["Server-Timing"] = fullTiming;
        }
        return createResponseWithMergedHeaders(htmlStream, {
            headers: htmlHeaders,
        });
    }
}
//# sourceMappingURL=handler.js.map