/**
 * Request Context - AsyncLocalStorage for passing request-scoped data throughout rendering
 *
 * This is the unified context used everywhere:
 * - Middleware execution
 * - Route handlers and loaders
 * - Server components during rendering
 * - Error boundaries and streaming
 *
 * Available via getRequestContext() anywhere in the request lifecycle.
 */
import { AsyncLocalStorage } from "node:async_hooks";
import { createHandleStore } from "./handle-store.js";
import { isHandle } from "../handle.js";
import { track } from "./context.js";
import { getFetchableLoader } from "./fetchable-loader-store.js";
import { THEME_COOKIE } from "../theme/constants.js";
// AsyncLocalStorage instance for request context
const requestContextStorage = new AsyncLocalStorage();
/**
 * Run a function within a request context
 * Used by the RSC handler to provide context to server actions
 */
export function runWithRequestContext(context, fn) {
    return requestContextStorage.run(context, fn);
}
/**
 * Get the current request context
 * Returns undefined if not running within a request context
 */
export function getRequestContext() {
    return requestContextStorage.getStore();
}
/**
 * Update params on the current request context
 * Called after route matching to populate route params
 */
export function setRequestContextParams(params) {
    const ctx = requestContextStorage.getStore();
    if (ctx) {
        ctx.params = params;
    }
}
/**
 * Get the current request context, throwing if not available
 * Use this when context is required (e.g., in loader actions)
 */
export function requireRequestContext() {
    const ctx = getRequestContext();
    if (!ctx) {
        throw new Error("Request context not available. This function must be called from within a server action " +
            "executed through the RSC handler.");
    }
    return ctx;
}
/**
 * Create a full request context with all methods implemented
 *
 * This is used by the RSC handler to create the unified context that's:
 * - Available via getRequestContext() throughout the request
 * - Passed to middleware as ctx
 * - Passed to handlers as ctx
 */
export function createRequestContext(options) {
    const { env, request, url, variables, cacheStore, executionContext, themeConfig } = options;
    const cookieHeader = request.headers.get("Cookie");
    let parsedCookies = null;
    // Create stub response for collecting headers/cookies
    const stubResponse = new Response(null, { status: 200 });
    // Create handle store and loader memoization for this request
    const handleStore = createHandleStore();
    const loaderPromises = new Map();
    // Lazy parse cookies
    const getParsedCookies = () => {
        if (!parsedCookies) {
            parsedCookies = parseCookiesFromHeader(cookieHeader);
        }
        return parsedCookies;
    };
    // Theme helpers (only used when themeConfig is provided)
    const getTheme = () => {
        if (!themeConfig)
            return undefined;
        const stored = getParsedCookies()[themeConfig.storageKey];
        if (stored) {
            // Validate stored value
            if (stored === "system" && themeConfig.enableSystem) {
                return "system";
            }
            if (themeConfig.themes.includes(stored)) {
                return stored;
            }
        }
        return themeConfig.defaultTheme;
    };
    const setTheme = (theme) => {
        if (!themeConfig)
            return;
        // Validate theme value
        if (theme !== "system" && !themeConfig.themes.includes(theme)) {
            console.warn(`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`);
            return;
        }
        // Set cookie
        stubResponse.headers.append("Set-Cookie", serializeCookieValue(themeConfig.storageKey, theme, {
            path: THEME_COOKIE.path,
            maxAge: THEME_COOKIE.maxAge,
            sameSite: THEME_COOKIE.sameSite,
        }));
    };
    // Build the context object first (without use), then add use
    const ctx = {
        env,
        request,
        url,
        pathname: url.pathname,
        searchParams: url.searchParams,
        var: variables,
        get: (key) => variables[key],
        set: (key, value) => {
            variables[key] = value;
        },
        params: {},
        res: stubResponse,
        cookie(name) {
            return getParsedCookies()[name];
        },
        cookies() {
            return { ...getParsedCookies() };
        },
        setCookie(name, value, options) {
            stubResponse.headers.append("Set-Cookie", serializeCookieValue(name, value, options));
        },
        deleteCookie(name, options) {
            stubResponse.headers.append("Set-Cookie", serializeCookieValue(name, "", { ...options, maxAge: 0 }));
        },
        header(name, value) {
            stubResponse.headers.set(name, value);
        },
        // Placeholder - will be replaced below
        use: null,
        method: request.method,
        _handleStore: handleStore,
        _cacheStore: cacheStore,
        waitUntil(fn) {
            if (executionContext?.waitUntil) {
                // Cloudflare Workers: use native waitUntil
                executionContext.waitUntil(fn());
            }
            else {
                // Node.js / dev: fire-and-forget with error logging
                fn().catch((err) => console.error("[waitUntil] Background task failed:", err));
            }
        },
        _onResponseCallbacks: [],
        onResponse(callback) {
            this._onResponseCallbacks.push(callback);
        },
        // Theme properties (only set when themeConfig is provided)
        theme: themeConfig ? getTheme() : undefined,
        setTheme: themeConfig ? setTheme : undefined,
        _themeConfig: themeConfig,
    };
    // Now create use() with access to ctx
    ctx.use = createUseFunction({
        handleStore,
        loaderPromises,
        getContext: () => ctx,
    });
    return ctx;
}
/**
 * Parse cookies from Cookie header
 */
function parseCookiesFromHeader(cookieHeader) {
    if (!cookieHeader)
        return {};
    const cookies = {};
    const pairs = cookieHeader.split(";");
    for (const pair of pairs) {
        const [name, ...rest] = pair.trim().split("=");
        if (name) {
            const raw = rest.join("=");
            try {
                cookies[name] = decodeURIComponent(raw);
            }
            catch {
                // Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
                cookies[name] = raw;
            }
        }
    }
    return cookies;
}
/**
 * Serialize a cookie for Set-Cookie header
 */
function serializeCookieValue(name, value, options = {}) {
    let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
    if (options.domain)
        cookie += `; Domain=${options.domain}`;
    if (options.path)
        cookie += `; Path=${options.path}`;
    if (options.maxAge !== undefined)
        cookie += `; Max-Age=${options.maxAge}`;
    if (options.expires)
        cookie += `; Expires=${options.expires.toUTCString()}`;
    if (options.httpOnly)
        cookie += "; HttpOnly";
    if (options.secure)
        cookie += "; Secure";
    if (options.sameSite)
        cookie += `; SameSite=${options.sameSite}`;
    return cookie;
}
/**
 * Create the use() function for loader and handle composition.
 *
 * This is the unified implementation used by both RequestContext and HandlerContext.
 * - For loaders: executes and memoizes loader functions
 * - For handles: returns a push function to add handle data
 */
export function createUseFunction(options) {
    const { handleStore, loaderPromises, getContext } = options;
    return ((item) => {
        // Handle case: return a push function
        if (isHandle(item)) {
            const handle = item;
            const ctx = getContext();
            const segmentId = ctx._currentSegmentId;
            if (!segmentId) {
                throw new Error(`Handle "${handle.$$id}" used outside of handler context. ` +
                    `Handles must be used within route/layout handlers.`);
            }
            // Return a push function bound to this handle and segment
            return (dataOrFn) => {
                // If it's a function, call it immediately to get the promise
                const valueOrPromise = typeof dataOrFn === "function"
                    ? dataOrFn()
                    : dataOrFn;
                // Push directly - promises will be serialized by RSC and streamed
                handleStore.push(handle.$$id, segmentId, valueOrPromise);
            };
        }
        // Loader case
        const loader = item;
        // Return cached promise if already started
        if (loaderPromises.has(loader.$$id)) {
            return loaderPromises.get(loader.$$id);
        }
        // Get loader function - either from loader object or fetchable registry
        let loaderFn = loader.fn;
        if (!loaderFn) {
            const fetchable = getFetchableLoader(loader.$$id);
            if (fetchable) {
                loaderFn = fetchable.fn;
            }
        }
        if (!loaderFn) {
            throw new Error(`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`);
        }
        const ctx = getContext();
        // Create loader context with recursive use() support
        const loaderCtx = {
            params: ctx.params,
            request: ctx.request,
            searchParams: ctx.searchParams,
            pathname: ctx.pathname,
            url: ctx.url,
            env: ctx.env,
            var: ctx.var,
            get: ctx.get,
            use: (dep) => {
                // Recursive call - will start dep loader if not already started
                return ctx.use(dep);
            },
            method: "GET",
            body: undefined,
        };
        // Start loader execution with tracking
        const doneLoader = track(`loader:${loader.$$id}`);
        const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
            doneLoader();
        });
        // Memoize for subsequent calls
        loaderPromises.set(loader.$$id, promise);
        return promise;
    });
}
//# sourceMappingURL=request-context.js.map