/**
 * Host Router Implementation
 *
 * Main router that handles host-based routing with middleware and cookie override.
 */
import { matchPattern, parseRequest, normalizePattern, validatePattern, } from './pattern-matcher.js';
import { handleCookieOverride, createCookieErrorResponse, } from './cookie-handler.js';
import { HostRouterError, NoRouteMatchError, InvalidHandlerError, } from './errors.js';
/**
 * Global registry for host routers (parallel to RouterRegistry for RSC routers).
 * Populated by createHostRouter() so the build-time discovery plugin can find
 * host routers and resolve their lazy handlers to trigger sub-app createRouter() calls.
 */
export const HostRouterRegistry = new Map();
let hostRouterAutoId = 0;
/**
 * Create a host router
 */
export function createHostRouter(options = {}) {
    const routes = [];
    const globalMiddleware = [];
    let fallbackRoute = null;
    const { debug = false, hostOverride } = options;
    function log(message, ...args) {
        if (debug) {
            console.log(`[HostRouter] ${message}`, ...args);
        }
    }
    /**
     * Create a route builder for chaining
     */
    function createRouteBuilder(patterns, isFallback = false) {
        const middleware = [];
        return {
            use(...mw) {
                middleware.push(...mw);
                return this;
            },
            map(handler) {
                const entry = {
                    patterns,
                    middleware,
                    handler,
                    isFallback,
                };
                if (isFallback) {
                    fallbackRoute = entry;
                }
                else {
                    routes.push(entry);
                }
                log(`Registered ${isFallback ? 'fallback' : 'route'}:`, patterns.join(', '));
                return router;
            },
        };
    }
    /**
     * Find matching route for hostname and path
     */
    function findMatchingRoute(hostname, pathname) {
        const parts = hostname.split('.');
        for (const route of routes) {
            for (const pattern of route.patterns) {
                if (matchPattern(pattern, hostname, pathname, parts)) {
                    log(`Matched pattern: "${pattern}"`);
                    return route;
                }
            }
        }
        return null;
    }
    /**
     * Execute middleware chain
     */
    async function executeMiddleware(middleware, request, context, finalHandler) {
        let index = 0;
        async function next() {
            if (index >= middleware.length) {
                return finalHandler();
            }
            const mw = middleware[index++];
            if (!mw) {
                return finalHandler();
            }
            return mw(request, context, next);
        }
        return next();
    }
    /**
     * Execute handler (lazy or direct)
     */
    async function executeHandler(handler, request, context) {
        // Check if it's a lazy handler (function that returns promise)
        if (typeof handler === 'function') {
            const result = handler(request, context);
            // If it returns a promise with default export
            if (result && typeof result === 'object' && 'then' in result) {
                const module = await result;
                if (typeof module === 'object' &&
                    module !== null &&
                    'default' in module) {
                    const defaultExport = module
                        .default;
                    // If default export is a router with match method
                    if (typeof defaultExport === 'object' &&
                        defaultExport !== null &&
                        'match' in defaultExport) {
                        return defaultExport.match(request, context);
                    }
                    // Otherwise treat as handler
                    return defaultExport(request, context);
                }
                // If promise resolves to Response
                return result;
            }
            // Direct handler
            return result;
        }
        throw new InvalidHandlerError(handler, {
            cause: { handlerType: typeof handler },
        });
    }
    /**
     * Router instance
     */
    const router = {
        host(patterns) {
            const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
            // Validate and normalize patterns
            const normalized = patternsArray.map((p) => {
                validatePattern(p);
                return normalizePattern(p);
            });
            return createRouteBuilder(normalized, false);
        },
        use(...middleware) {
            globalMiddleware.push(...middleware);
            log(`Registered global middleware (${middleware.length})`);
            return router;
        },
        fallback() {
            return createRouteBuilder([], true);
        },
        test(hostname) {
            const parts = hostname.split('.');
            const pathname = '/';
            for (const route of routes) {
                for (const pattern of route.patterns) {
                    if (matchPattern(pattern, hostname, pathname, parts)) {
                        return {
                            pattern,
                            handler: route.handler,
                        };
                    }
                }
            }
            return null;
        },
        async match(request, context = {}) {
            log(`Request: ${request.url}`);
            let effectiveHostname;
            try {
                // Handle cookie override (may throw HostRouterError)
                effectiveHostname = handleCookieOverride(request, hostOverride, context);
            }
            catch (error) {
                // If it's a HostRouterError from cookie override
                if (error instanceof HostRouterError) {
                    log(`Cookie override error: ${error.message}`);
                    // If fallback exists, use it
                    if (fallbackRoute) {
                        context.error = error;
                        const allMiddleware = [
                            ...globalMiddleware,
                            ...fallbackRoute.middleware,
                        ];
                        return executeMiddleware(allMiddleware, request, context, () => executeHandler(fallbackRoute.handler, request, context));
                    }
                    // Otherwise return error response with cookie deletion
                    if (hostOverride) {
                        return createCookieErrorResponse(hostOverride.cookieName, error.message);
                    }
                }
                // Re-throw non-HostRouterErrors
                throw error;
            }
            const { pathname } = parseRequest(request);
            if (effectiveHostname !== parseRequest(request).hostname) {
                log(`Cookie override: ${effectiveHostname}`);
            }
            // Find matching route
            const matchedRoute = findMatchingRoute(effectiveHostname, pathname);
            if (!matchedRoute) {
                log(`No route matched`);
                throw new NoRouteMatchError(effectiveHostname, pathname, {
                    cause: {
                        hostname: effectiveHostname,
                        pathname,
                    },
                });
            }
            // Combine global and route-specific middleware
            const allMiddleware = [...globalMiddleware, ...matchedRoute.middleware];
            // Execute middleware chain and handler
            return executeMiddleware(allMiddleware, request, context, () => executeHandler(matchedRoute.handler, request, context));
        },
    };
    // Register in the global HostRouterRegistry for build-time discovery.
    // The routes array and fallbackRoute ref are live - they reflect routes
    // added via .host().map() after this point.
    const registryId = `host-router-${hostRouterAutoId++}`;
    HostRouterRegistry.set(registryId, {
        get routes() { return routes; },
        get fallback() { return fallbackRoute; },
    });
    return router;
}
//# sourceMappingURL=router.js.map