import { getContext, runWithPrefixes, getUrlPrefix, getNamePrefix, } from "./server/context";
import { invariant } from "./errors";
import { isPrerenderHandler, } from "./prerender.js";
import { isStaticHandler, } from "./static-handler.js";
import { registerSearchSchema } from "./route-map-builder.js";
// ============================================================================
// Response Route Symbol and Types
// ============================================================================
/**
 * Symbol marking a route as a response route (non-RSC).
 * Stored on PathOptions and UrlPatterns to signal the trie to short-circuit.
 */
export const RESPONSE_TYPE = Symbol.for("rangojs.responseType");
// ============================================================================
// Helper Implementations
// ============================================================================
/**
 * Check if a value is a valid use item
 */
const isValidUseItem = (item) => {
    return (typeof item === "undefined" ||
        item === null ||
        (item &&
            typeof item === "object" &&
            "type" in item &&
            [
                "layout",
                "route",
                "middleware",
                "revalidate",
                "parallel",
                "intercept",
                "loader",
                "loading",
                "errorBoundary",
                "notFoundBoundary",
                "when",
                "cache",
                "include",
            ].includes(item.type)));
};
/**
 * Apply URL prefix to a pattern
 * Handles edge cases like "/" patterns and double slashes
 */
function applyUrlPrefix(prefix, pattern) {
    if (!prefix)
        return pattern;
    if (pattern === "/")
        return prefix;
    if (prefix.endsWith("/") && pattern.startsWith("/")) {
        return prefix + pattern.slice(1);
    }
    return prefix + pattern;
}
/**
 * Apply name prefix to a route name
 */
function applyNamePrefix(prefix, name) {
    if (!prefix)
        return name;
    return `${prefix}.${name}`;
}
/**
 * Create path() helper
 *
 * The path() function is the key new feature - it combines URL pattern
 * with handler at the definition site.
 */
/**
 * Resolve response type from path options (set by path.json(), path.text(), etc.)
 */
function resolveResponseType(options) {
    return options?.[RESPONSE_TYPE];
}
function createPathHelper() {
    return ((pattern, handler, optionsOrUse, maybeUse) => {
        const store = getContext();
        const ctx = store.getStore();
        if (!ctx)
            throw new Error("path() must be called inside urls()");
        invariant(!ctx.parent || ctx.parent.type !== "parallel", "path() cannot be used inside parallel()");
        // Walk the parent chain to prevent path() nested under another path(),
        // even when separated by intermediate layouts (e.g. path(layout(path())))
        {
            let ancestor = ctx.parent;
            while (ancestor) {
                invariant(ancestor.type !== "route", "path() cannot be nested inside another path()");
                ancestor = ancestor.parent;
            }
        }
        // Determine options and use based on argument types
        let options;
        let use;
        if (typeof optionsOrUse === "function") {
            // path(pattern, handler, use)
            use = optionsOrUse;
        }
        else if (typeof optionsOrUse === "object") {
            // path(pattern, handler, options) or path(pattern, handler, options, use)
            options = optionsOrUse;
            use = maybeUse;
        }
        // Get prefixes from context (set by include())
        const urlPrefix = getUrlPrefix();
        const namePrefix = getNamePrefix();
        // Apply URL prefix to pattern
        const prefixedPattern = applyUrlPrefix(urlPrefix, pattern);
        // Generate route name - use provided name or generate from pattern
        const localName = options?.name || `$path_${pattern.replace(/[/:*?]/g, "_")}`;
        // Apply name prefix if set (from include())
        const routeName = applyNamePrefix(namePrefix, localName);
        const namespace = `${ctx.namespace}.${store.getNextIndex("route")}.${routeName}`;
        // Per-request pruning: skip registration for routes that won't be rendered.
        // forRoute is set by loadManifest() to the matched route name. During
        // evaluateLazyEntry() (route matching), forRoute is unset so all routes
        // register normally. We still increment counters to keep shortCodes stable
        // across different routes (needed for segment reconciliation on navigation).
        //
        // include() does not need its own forRoute pruning. include() creates lazy
        // entries that defer handler execution until route matching. When the lazy
        // handler eventually runs inside loadManifest(), this path() check already
        // covers all routes defined inside the include.
        if (ctx.forRoute && routeName !== ctx.forRoute) {
            store.getShortCode("route");
            return { type: "route" };
        }
        // Ensure handler is always a function (wrap ReactNode or extract from prerender/static def)
        const wrappedHandler = typeof handler === "function"
            ? handler
            : isPrerenderHandler(handler)
                ? handler.handler
                : isStaticHandler(handler)
                    ? handler.handler
                    : () => handler;
        const entry = {
            id: namespace,
            shortCode: store.getShortCode("route"),
            type: "route",
            parent: ctx.parent,
            handler: wrappedHandler,
            // Store the PREFIXED pattern for route matching
            pattern: prefixedPattern,
            loading: undefined,
            middleware: [],
            revalidate: [],
            errorBoundary: [],
            notFoundBoundary: [],
            layout: [],
            parallel: [],
            intercept: [],
            loader: [],
            ...(urlPrefix ? { mountPath: urlPrefix } : {}),
            ...(isPrerenderHandler(handler)
                ? {
                    isPrerender: true,
                    prerenderDef: handler,
                }
                : {}),
            ...(isStaticHandler(handler)
                ? { isStaticPrerender: true }
                : {}),
            ...(resolveResponseType(options)
                ? { responseType: resolveResponseType(options) }
                : {}),
        };
        // Check for duplicate route names (TypeScript should catch this, but runtime check too)
        invariant(ctx.manifest.get(routeName) === undefined, `Duplicate route name: ${routeName} at ${namespace}`);
        // Register route entry with prefixed name
        ctx.manifest.set(routeName, entry);
        // Also store pattern in a separate map for URL generation
        if (ctx.patterns) {
            ctx.patterns.set(routeName, prefixedPattern);
        }
        // Store pattern grouped by URL prefix for separate entry creation
        if (ctx.patternsByPrefix) {
            const urlPrefix = getUrlPrefix() || "";
            if (!ctx.patternsByPrefix.has(urlPrefix)) {
                ctx.patternsByPrefix.set(urlPrefix, new Map());
            }
            ctx.patternsByPrefix.get(urlPrefix).set(routeName, prefixedPattern);
        }
        // Store trailing slash config if specified
        if (options?.trailingSlash && ctx.trailingSlash) {
            ctx.trailingSlash.set(routeName, options.trailingSlash);
        }
        // Store search schema if specified
        if (options?.search) {
            if (ctx.searchSchemas) {
                ctx.searchSchemas.set(routeName, options.search);
            }
            registerSearchSchema(routeName, options.search);
        }
        // Run use callback if provided
        if (use && typeof use === "function") {
            const result = store.run(namespace, entry, use);
            invariant(Array.isArray(result) && result.every((item) => isValidUseItem(item)), `path() use() callback must return an array of use items [${namespace}]`);
            return { name: namespace, type: "route", uses: result };
        }
        return { name: namespace, type: "route" };
    });
}
/**
 * Attach response type tag methods (.json, .text, .html, .xml, .md, .image, .stream, .any) to a path helper.
 * Each tag wraps the original path() call with the RESPONSE_TYPE option set.
 */
function attachPathResponseTags(pathFn) {
    function createTagged(responseType) {
        return ((pattern, handler, optionsOrUse, maybeUse) => {
            let options;
            let use;
            if (typeof optionsOrUse === "function") {
                options = { [RESPONSE_TYPE]: responseType };
                use = optionsOrUse;
            }
            else {
                options = { ...optionsOrUse, [RESPONSE_TYPE]: responseType };
                use = maybeUse;
            }
            return pathFn(pattern, handler, options, use);
        });
    }
    const extended = pathFn;
    extended.json = createTagged("json");
    extended.text = createTagged("text");
    extended.html = createTagged("html");
    extended.xml = createTagged("xml");
    extended.md = createTagged("md");
    extended.image = createTagged("image");
    extended.stream = createTagged("stream");
    extended.any = createTagged("any");
    return extended;
}
/**
 * Process an IncludeItem by executing its nested patterns with prefixes
 * This expands the include into actual route registrations
 */
function processIncludeItem(item) {
    const { prefix, patterns, options } = item;
    const namePrefix = options?.name;
    // Execute the nested patterns' handler with URL and name prefixes
    // The urlPrefix being set tells nested urls() to skip RootLayout wrapping
    return runWithPrefixes(prefix, namePrefix, () => {
        // Call the nested patterns' handler - this registers routes with prefixed patterns/names
        return patterns.handler();
    });
}
/**
 * Recursively process items, expanding any IncludeItems
 * Returns items with IncludeItems expanded into actual route items
 *
 * Lazy includes are kept as-is (not expanded) for the router to handle later.
 */
function processItems(items) {
    const result = [];
    for (const item of items) {
        if (!item)
            continue;
        if (item.type === "include") {
            const includeItem = item;
            // Lazy includes are NOT expanded here - kept for router to handle
            if (includeItem.lazy) {
                result.push(item);
                continue;
            }
            // Eager includes are already expanded during include() call
            if (includeItem._expanded) {
                // Items were expanded immediately - just process them recursively
                result.push(...processItems(includeItem._expanded));
            }
            else {
                // Fallback for legacy include items without _expanded
                const expanded = processIncludeItem(item);
                result.push(...processItems(expanded));
            }
        }
        else if (item.type === "layout" && item.uses) {
            // Process nested items in layout
            const layoutItem = item;
            layoutItem.uses = processItems(layoutItem.uses);
            result.push(layoutItem);
        }
        else {
            result.push(item);
        }
    }
    return result;
}
/**
 * Create include() helper for composing URL patterns
 *
 * By default, include() IMMEDIATELY expands the nested patterns. This ensures
 * that routes from included patterns inherit the correct parent context
 * (the layout they're included in).
 *
 * With `lazy: true`, patterns are NOT expanded at definition time. Instead,
 * they're evaluated on first request that matches the prefix. This improves
 * cold start time for apps with many routes.
 */
function createIncludeHelper() {
    return (prefix, patterns, options) => {
        var _a;
        const store = getContext();
        const ctx = store.getStore();
        if (!ctx)
            throw new Error("include() must be called inside urls()");
        const namePrefix = options?.name;
        const name = `$include_${prefix.replace(/[/:*?]/g, "_")}`;
        // Capture context for deferred evaluation
        const capturedUrlPrefix = getUrlPrefix();
        const capturedNamePrefix = getNamePrefix();
        const capturedParent = ctx.parent;
        const fullPrefix = capturedUrlPrefix
            ? (capturedUrlPrefix.endsWith("/") && prefix.startsWith("/")
                ? capturedUrlPrefix + prefix.slice(1)
                : capturedUrlPrefix + prefix)
            : prefix;
        const fullNamePrefix = namePrefix
            ? capturedNamePrefix
                ? `${capturedNamePrefix}.${namePrefix}`
                : namePrefix
            : capturedNamePrefix;
        // Track this include for build-time manifest generation
        if (ctx.trackedIncludes) {
            ctx.trackedIncludes.push({
                prefix,
                fullPrefix,
                namePrefix: fullNamePrefix,
                patterns,
                lazy: true,
            });
        }
        // Snapshot parent's counters so lazy manifest generation starts
        // at the correct index, preventing shortCode collisions with
        // sibling entries (e.g., BlogLayout and ArticlesLayout under NavLayout).
        const capturedCounters = { ...ctx.counters };
        // Reserve a layout slot in the parent's counter so sibling lazy includes
        // produce different shortCode indices for their root layout.
        // Without this, consecutive include() calls capture identical counters
        // and their first child layouts get the same shortCode (e.g., both M0L0L0),
        // causing the client partial-update diff to see no changes on navigation.
        if (capturedParent?.shortCode) {
            const layoutCounterKey = `${capturedParent.shortCode}_layout`;
            (_a = ctx.counters)[layoutCounterKey] ?? (_a[layoutCounterKey] = 0);
            ctx.counters[layoutCounterKey]++;
        }
        // All includes are lazy - patterns are evaluated on first matching request
        // This improves cold start time significantly for large route sets
        return {
            type: "include",
            name,
            prefix,
            patterns,
            options,
            lazy: true,
            _lazyContext: {
                urlPrefix: capturedUrlPrefix,
                namePrefix: fullNamePrefix,
                parent: capturedParent,
                counters: capturedCounters,
            },
        };
    };
}
// ============================================================================
// Re-use existing helpers from route-definition.ts
// ============================================================================
// Import the helper creation functions from route-definition
import { createRouteHelpers } from "./route-definition.js";
// ============================================================================
// urls() Main Entry Point
// ============================================================================
/**
 * Define URL patterns with Django-inspired syntax
 *
 * Replaces map() as the entry point for route definitions.
 * URL patterns are now visible at the definition site via path().
 *
 * @example
 * ```typescript
 * export const blogPatterns = urls(({ path, layout, loader }) => [
 *   layout(BlogLayout, () => [
 *     path("/", BlogIndex, { name: "index" }),
 *     path("/:slug", BlogPost, { name: "post" }, () => [
 *       loader(PostLoader),
 *     ]),
 *   ]),
 * ]);
 * ```
 */
export function urls(builder) {
    // Collect path definitions during build
    const definitions = [];
    // Create the handler function that will be called by the router
    const handler = () => {
        invariant(typeof builder === "function", "urls() expects a builder function as its argument");
        // Get base helpers from the existing route-definition module
        const baseHelpers = createRouteHelpers();
        // Create the path helper (with .json, .text, .html, .xml, .image, .stream, .any tags)
        const pathHelper = attachPathResponseTags(createPathHelper());
        // Create the include helper
        const includeHelper = createIncludeHelper();
        // Combine all helpers
        // Note: layout and cache are cast to their typed versions - phantom types don't affect runtime
        const helpers = {
            path: pathHelper,
            include: includeHelper,
            layout: baseHelpers.layout,
            parallel: baseHelpers.parallel,
            intercept: baseHelpers.intercept,
            middleware: baseHelpers.middleware,
            revalidate: baseHelpers.revalidate,
            loader: baseHelpers.loader,
            loading: baseHelpers.loading,
            errorBoundary: baseHelpers.errorBoundary,
            notFoundBoundary: baseHelpers.notFoundBoundary,
            when: baseHelpers.when,
            cache: baseHelpers.cache,
        };
        // Execute builder directly - manifest.ts handles RootLayout wrapping
        // for inline handlers (non-Promise results).
        // For nested include() calls, routes inherit the outer RootLayout.
        const builderResult = builder(helpers);
        return processItems(builderResult);
    };
    // trailingSlash config is populated when handler() runs
    // We expose it via a getter that reads from the context after handler execution
    return {
        definitions,
        handler,
        get trailingSlash() {
            // Get the trailingSlash map from the current context
            // This will be populated after handler() is called
            const store = getContext();
            const ctx = store.context.getStore();
            if (!ctx?.trailingSlash) {
                return {};
            }
            return Object.fromEntries(ctx.trailingSlash);
        },
    };
}
//# sourceMappingURL=urls.js.map