import { RouteRegistry, normalizeRouteMeta, } from './RouteRegistry.js';
export const createRouter = () => ({
    routes: [],
    prefix: '',
    routeIndex: new Map(),
});
/**
 * Router - HTTP Routing Engine
 * Matches incoming requests to route handlers
 */
/**
 * Convert a path pattern to a regex
 * Example: /users/:id/posts/:postId -> /users/([^/]+)/posts/([^/]+)
 */
const pathToRegex = (path) => {
    const paramNames = [];
    // Supports:
    // - :id     => single path segment (no slashes)
    // - :path*  => greedy match (may include slashes)
    let regexPath = path.replaceAll(/:([a-zA-Z_]\w*)(\*)?/g, (_full, paramName, star) => {
        paramNames.push(paramName);
        const isGreedy = star === '*';
        return isGreedy ? '(.+)' : '([^/]+)';
    });
    regexPath = `^${regexPath}$`;
    const pattern = new RegExp(regexPath);
    return { pattern, paramNames };
};
/**
 * Register a route
 */
const registerRoute = (router, method, path, handler, options) => {
    const { pattern, paramNames } = pathToRegex(path);
    // Merge inherited middleware with route-specific middleware
    let routeMiddleware;
    if (router.inheritedMiddleware && router.inheritedMiddleware.length > 0) {
        const routeSpecificMiddleware = Array.isArray(options?.middleware)
            ? options?.middleware
            : [];
        routeMiddleware = [...router.inheritedMiddleware, ...routeSpecificMiddleware];
    }
    else {
        const hasRouteSpecificMiddleware = Array.isArray(options?.middleware);
        if (hasRouteSpecificMiddleware) {
            routeMiddleware = options?.middleware;
        }
    }
    const route = {
        method,
        path,
        pattern,
        handler,
        paramNames,
        middleware: routeMiddleware,
        meta: normalizeRouteMeta(options?.meta),
    };
    router.routes.push(route);
    RouteRegistry.record({
        method,
        path,
        middleware: route.middleware,
        meta: route.meta,
    });
    // Index by method for faster lookup
    if (!router.routeIndex.has(method)) {
        router.routeIndex.set(method, []);
    }
    router.routeIndex.get(method)?.push(route);
};
/**
 * Extract parameters from a matching route
 */
const getRouteMatch = (route, path) => {
    const match = route.pattern.exec(path);
    if (!match)
        return null;
    const params = {};
    route.paramNames.forEach((paramName, index) => {
        params[paramName] = match[index + 1];
    });
    return {
        handler: route.handler,
        params,
        middleware: route.middleware,
        meta: route.meta,
        routePath: route.path,
    };
};
/**
 * Fallback linear search for manually added routes
 */
const findInFallback = (router, method, path) => {
    for (const route of router.routes) {
        // Skip if already checked via index
        if (router.routeIndex !== undefined) {
            const methodRoutes = router.routeIndex.get(route.method);
            if (methodRoutes?.includes(route) ?? false)
                continue;
        }
        if (route.method !== method && route.method !== '*')
            continue;
        const match = getRouteMatch(route, path);
        if (match)
            return match;
    }
    return null;
};
/**
 * Match a request to a route
 */
const matchRoute = (router, method, path) => {
    // Try fast lookup first
    if (router.routeIndex !== undefined) {
        const candidates = [
            ...(router.routeIndex.get(method) ?? []),
            ...(router.routeIndex.get('*') ?? []),
        ];
        for (const route of candidates) {
            const match = getRouteMatch(route, path);
            if (match)
                return match;
        }
    }
    // Fallback to linear search for manually added routes or if index is missing
    return findInFallback(router, method, path);
};
const stripTrailingSlashes = (value) => {
    let end = value.length;
    while (end > 0 && value.codePointAt(end - 1) === 47) {
        end--;
    }
    return value.slice(0, end);
};
const normalizePrefix = (prefix) => {
    const trimmed = prefix.trim();
    if (trimmed === '' || trimmed === '/')
        return '';
    const withoutTrailing = stripTrailingSlashes(trimmed);
    return withoutTrailing.startsWith('/') ? withoutTrailing : `/${withoutTrailing}`;
};
const normalizePath = (path) => {
    const trimmed = path.trim();
    if (trimmed === '')
        return '/';
    return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
};
const joinPaths = (prefix, path) => {
    const pfx = normalizePrefix(prefix);
    const pth = normalizePath(path);
    if (pfx === '')
        return pth;
    if (pth === '/')
        return pfx || '/';
    return `${pfx}${pth}`;
};
const scopeRouter = (router, prefix, inheritedMiddleware) => ({
    routes: router.routes,
    prefix: joinPaths(router.prefix, prefix),
    routeIndex: router.routeIndex,
    inheritedMiddleware,
});
const group = (router, prefix, callback, options) => {
    callback(scopeRouter(router, prefix, options?.middleware));
};
function buildResourcePaths(prefix, path) {
    const base = joinPaths(prefix, path);
    const withId = `${base.endsWith('/') ? base.slice(0, -1) : base}/:id`;
    return { base, withId };
}
function resolveResourceDefaultOptions(options) {
    return options?.middleware ? { middleware: options.middleware } : undefined;
}
function registerResourceIndex(router, base, controller, options, defaultOptions) {
    if (!controller.index)
        return;
    registerRoute(router, 'GET', base, controller.index, options?.index ?? defaultOptions);
}
function registerResourceStore(router, base, controller, options, defaultOptions) {
    if (!controller.store)
        return;
    registerRoute(router, 'POST', base, controller.store, options?.store ?? defaultOptions);
}
function registerResourceShow(router, withId, controller, options, defaultOptions) {
    if (!controller.show)
        return;
    registerRoute(router, 'GET', withId, controller.show, options?.show ?? defaultOptions);
}
function registerResourceUpdate(router, withId, controller, options, defaultOptions) {
    if (!controller.update)
        return;
    const updateOptions = options?.update ?? defaultOptions;
    registerRoute(router, 'PUT', withId, controller.update, updateOptions);
    registerRoute(router, 'PATCH', withId, controller.update, updateOptions);
}
function registerResourceDestroy(router, withId, controller, options, defaultOptions) {
    if (!controller.destroy)
        return;
    registerRoute(router, 'DELETE', withId, controller.destroy, options?.destroy ?? defaultOptions);
}
const resource = (router, path, controller, options) => {
    const { base, withId } = buildResourcePaths(router.prefix, path);
    const defaultOptions = resolveResourceDefaultOptions(options);
    registerResourceIndex(router, base, controller, options, defaultOptions);
    registerResourceStore(router, base, controller, options, defaultOptions);
    registerResourceShow(router, withId, controller, options, defaultOptions);
    registerResourceUpdate(router, withId, controller, options, defaultOptions);
    registerResourceDestroy(router, withId, controller, options, defaultOptions);
};
const get = (router, path, handler, options) => {
    registerRoute(router, 'GET', joinPaths(router.prefix, path), handler, options);
};
const post = (router, path, handler, options) => {
    registerRoute(router, 'POST', joinPaths(router.prefix, path), handler, options);
};
const put = (router, path, handler, options) => {
    registerRoute(router, 'PUT', joinPaths(router.prefix, path), handler, options);
};
const patch = (router, path, handler, options) => {
    registerRoute(router, 'PATCH', joinPaths(router.prefix, path), handler, options);
};
const del = (router, path, handler, options) => {
    registerRoute(router, 'DELETE', joinPaths(router.prefix, path), handler, options);
};
const any = (router, path, handler, options) => {
    const fullPath = joinPaths(router.prefix, path);
    ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
        registerRoute(router, method, fullPath, handler, options);
    });
};
const match = (router, method, path) => matchRoute(router, method, path);
const getRoutes = (router) => router.routes;
/**
 * Router - Sealed namespace for HTTP routing
 * All operations grouped in frozen namespace to prevent mutation
 */
export const Router = Object.freeze({
    createRouter,
    scopeRouter,
    group,
    resource,
    get,
    post,
    put,
    patch,
    del,
    any,
    match,
    getRoutes,
});
export default Router;
