import * as plugins from '../../plugins.js';
import { DomainMatcher, PathMatcher } from '../../core/routing/matchers/index.js';
/**
 * Unified HTTP Router for reverse proxy requests
 *
 * Domain matching patterns:
 * - Exact matches: "example.com"
 * - Wildcard subdomains: "*.example.com" (matches any subdomain of example.com)
 * - TLD wildcards: "example.*" (matches example.com, example.org, etc.)
 * - Complex wildcards: "*.lossless*" (matches any subdomain of any lossless domain)
 * - Default fallback: "*" (matches any unmatched domain)
 *
 * Path pattern matching:
 * - Exact path: "/api/users"
 * - Wildcard paths: "/api/*"
 * - Path parameters: "/users/:id/profile"
 */
export class HttpRouter {
    constructor(routes, logger) {
        // Store routes sorted by priority
        this.routes = [];
        this.logger = logger || {
            error: console.error.bind(console),
            warn: console.warn.bind(console),
            info: console.info.bind(console),
            debug: console.debug?.bind(console)
        };
        if (routes) {
            this.setRoutes(routes);
        }
    }
    /**
     * Sets a new set of routes
     * @param routes Array of route configurations
     */
    setRoutes(routes) {
        this.routes = [...routes];
        // Sort routes by priority (higher priority first)
        this.routes.sort((a, b) => {
            const priorityA = a.priority ?? 0;
            const priorityB = b.priority ?? 0;
            return priorityB - priorityA;
        });
        // Find default route if any (route with "*" as domain)
        this.defaultRoute = this.routes.find(route => {
            const domains = Array.isArray(route.match.domains)
                ? route.match.domains
                : route.match.domains ? [route.match.domains] : [];
            return domains.includes('*');
        });
        const uniqueDomains = this.getHostnames();
        this.logger.info(`HttpRouter initialized with ${this.routes.length} routes (${uniqueDomains.length} unique hosts)`);
    }
    /**
     * Routes a request based on hostname and path
     * @param req The incoming HTTP request
     * @returns The matching route or undefined if no match found
     */
    routeReq(req) {
        const result = this.routeReqWithDetails(req);
        return result ? result.route : undefined;
    }
    /**
     * Routes a request with detailed matching information
     * @param req The incoming HTTP request
     * @returns Detailed routing result including matched route and path information
     */
    routeReqWithDetails(req) {
        // Extract and validate host header
        const originalHost = req.headers.host;
        if (!originalHost) {
            this.logger.error('No host header found in request');
            return this.defaultRoute ? { route: this.defaultRoute } : undefined;
        }
        // Parse URL for path matching
        const parsedUrl = plugins.url.parse(req.url || '/');
        const urlPath = parsedUrl.pathname || '/';
        // Extract hostname without port
        const hostWithoutPort = originalHost.split(':')[0].toLowerCase();
        // Find matching route
        const matchingRoute = this.findMatchingRoute(hostWithoutPort, urlPath);
        if (matchingRoute) {
            return matchingRoute;
        }
        // Fall back to default route if available
        if (this.defaultRoute) {
            this.logger.warn(`No specific route found for host: ${hostWithoutPort}, using default`);
            return { route: this.defaultRoute };
        }
        this.logger.error(`No route found for host: ${hostWithoutPort}`);
        return undefined;
    }
    /**
     * Find the best matching route for a given hostname and path
     */
    findMatchingRoute(hostname, path) {
        // Try each route in priority order
        for (const route of this.routes) {
            // Skip disabled routes
            if (route.enabled === false) {
                continue;
            }
            // Check domain match
            if (route.match.domains) {
                const domains = Array.isArray(route.match.domains)
                    ? route.match.domains
                    : [route.match.domains];
                // Check if any domain pattern matches
                const domainMatches = domains.some(domain => DomainMatcher.match(domain, hostname));
                if (!domainMatches) {
                    continue;
                }
            }
            // Check path match if specified
            if (route.match.path) {
                const pathResult = PathMatcher.match(route.match.path, path);
                if (pathResult.matches) {
                    return {
                        route,
                        pathMatch: pathResult.pathMatch || path,
                        pathParams: pathResult.params,
                        pathRemainder: pathResult.pathRemainder
                    };
                }
            }
            else {
                // No path specified, so domain match is sufficient
                return { route };
            }
        }
        return undefined;
    }
    /**
     * Gets all currently active route configurations
     * @returns Array of all active routes
     */
    getRoutes() {
        return [...this.routes];
    }
    /**
     * Gets all hostnames that this router is configured to handle
     * @returns Array of unique hostnames
     */
    getHostnames() {
        const hostnames = new Set();
        for (const route of this.routes) {
            if (!route.match.domains)
                continue;
            const domains = Array.isArray(route.match.domains)
                ? route.match.domains
                : [route.match.domains];
            for (const domain of domains) {
                if (domain !== '*') {
                    hostnames.add(domain.toLowerCase());
                }
            }
        }
        return Array.from(hostnames);
    }
    /**
     * Adds a single new route configuration
     * @param route The route configuration to add
     */
    addRoute(route) {
        this.routes.push(route);
        // Re-sort routes by priority
        this.routes.sort((a, b) => {
            const priorityA = a.priority ?? 0;
            const priorityB = b.priority ?? 0;
            return priorityB - priorityA;
        });
    }
    /**
     * Removes routes by domain pattern
     * @param domain The domain pattern to remove routes for
     * @returns Boolean indicating whether any routes were removed
     */
    removeRoutesByDomain(domain) {
        const initialCount = this.routes.length;
        // Filter out routes that match the domain
        this.routes = this.routes.filter(route => {
            if (!route.match.domains)
                return true;
            const domains = Array.isArray(route.match.domains)
                ? route.match.domains
                : [route.match.domains];
            return !domains.includes(domain);
        });
        return this.routes.length !== initialCount;
    }
    /**
     * Remove a specific route by reference
     * @param route The route to remove
     * @returns Boolean indicating if the route was found and removed
     */
    removeRoute(route) {
        const index = this.routes.indexOf(route);
        if (index !== -1) {
            this.routes.splice(index, 1);
            return true;
        }
        return false;
    }
}
//# sourceMappingURL=data:application/json;base64, 