import { readdir, stat } from "node:fs/promises";
import { join, relative, extname, sep } from "node:path";
import { executeRouteFactory } from "./define-routes.js";
import { buildValidateMiddleware } from "./validate-middleware.js";
import { resolveModuleDefault } from "./interop.js";
import { resolveMiddlewares, validateMiddlewareRefs, } from "./middleware-loader.js";
import { normalizeCacheOptions, buildRouteCacheMiddleware, } from "./middlewares/route-cache.js";
import { createRouteMultipartMiddleware } from "./middlewares/body-parser.js";
import { pathToFileURL } from "node:url";
/**
 * loadRoutes — 扫描 routes/ 目录加载路由文件并注册到 adapter
 *
 * @param app       VextApp 实例
 * @param routesDir routes/ 目录的绝对路径（如 /path/to/my-app/src/routes）
 * @param options   加载选项（中间件注册表 + 全局中间件）
 * @param collector 🆕 OpenAPI 路由元信息收集器（可选，未配置 openapi 时为 null）
 *
 * @example
 * ```typescript
 * // bootstrap 内部（Phase 1）
 * await loadRoutes(app, path.join(rootDir, 'src/routes'), {
 *   middlewareDefs: middlewareRegistry,
 *   globalMiddlewares: internals.getGlobalMiddlewares(),
 * }, collector)
 * ```
 */
export async function loadRoutes(app, routesDir, options, collector) {
    // ── 规范化 middlewareDefs（兼容 Phase 0 的 Map 和 Phase 1 的 Registry）──
    const registry = normalizeMiddlewareDefs(options.middlewareDefs);
    // ── 1. 检查 routes/ 目录是否存在 ──────────────────────────
    const dirExists = await directoryExists(routesDir);
    if (!dirExists) {
        app.logger.warn(`[vextjs] Routes directory not found: ${routesDir}. No routes loaded.`);
        return;
    }
    // ── 2. 递归扫描所有路由文件 ────────────────────────────────
    const routeFiles = await scanRouteFiles(routesDir);
    if (routeFiles.length === 0) {
        app.logger.warn(`[vextjs] No route files found in: ${routesDir}`);
        return;
    }
    // ── 3. 按文件路径推导路由前缀 ──────────────────────────────
    const routeEntries = routeFiles.map((filePath) => ({
        filePath,
        prefix: filePathToPrefix(filePath, routesDir),
    }));
    // ── 4. 检测前缀冲突 ──────────────────────────────────────
    detectPrefixConflicts(routeEntries);
    // ── 5. 排序：静态段优先于动态段 ───────────────────────────
    routeEntries.sort(compareRouteEntries);
    // ── 6. 逐个加载、收集、注册 ───────────────────────────────
    let loadedRouteCount = 0;
    const allRouteDefs = [];
    // 重复路由检测：'METHOD /path' → sourceFile
    const registeredRoutes = new Map();
    for (const entry of routeEntries) {
        const routeDefinition = await loadRouteFile(entry.filePath, app);
        if (!routeDefinition) {
            continue;
        }
        // 设置来源文件路径（用于错误信息）
        routeDefinition.sourceFile = entry.filePath;
        // ── 6.1 重复路由检测 ─────────────────────────────────
        for (const route of routeDefinition.routes) {
            const fullPath = normalizePath(entry.prefix, route.path);
            const routeKey = `${route.method.toUpperCase()} ${fullPath}`;
            const existingFile = registeredRoutes.get(routeKey);
            if (existingFile) {
                throw new Error(`[vextjs] Duplicate route: ${routeKey}\n` +
                    `         Already registered by: ${existingFile}\n` +
                    `         Conflict in: ${entry.filePath}\n` +
                    `         Rename the route or use a different path/method.`);
            }
            registeredRoutes.set(routeKey, entry.filePath);
        }
        // ── 6.2 注册到 adapter（含中间件解析 + validate 构建）──
        registerRouteDefinition(routeDefinition, app, entry.prefix, registry, options.globalMiddlewares, collector);
        loadedRouteCount += routeDefinition.routes.length;
        // 收集用于后续统一验证
        allRouteDefs.push({
            routes: routeDefinition.routes.map((r) => ({
                method: r.method,
                path: normalizePath(entry.prefix, r.path),
                options: r.options ?? {},
            })),
            sourceFile: entry.filePath,
        });
    }
    // ── 7. 启动时统一验证所有路由的中间件引用 ─────────────────
    validateMiddlewareRefs(allRouteDefs, registry);
    app.logger.info(`[vextjs] ${loadedRouteCount} route(s) loaded from ${routeEntries.length} file(s)`);
}
// ── 路由注册（含中间件解析 + validate 构建）──────────────────
/**
 * registerRouteDefinition — 将一个 RouteDefinition 的所有路由注册到 adapter
 *
 * 对每条路由：
 *   1. 拼接完整路径：fullPath = prefix + route.path
 *   2. 从 MiddlewareRegistry 解析路由级中间件引用 → VextMiddleware[]
 *   3. 构建 validate 中间件（若有 options.validate）
 *   4. 将 handler 包装为 VextMiddleware（执行链的最后一环）
 *   5. 组装执行链：[...routeMiddlewares, validateMiddleware?, handlerMiddleware]
 *   6. adapter.registerRoute(method, fullPath, chain)
 *
 * 注意：全局中间件由 adapter 内部拼接（registerMiddleware 已收集），
 * 这里只处理路由级链。
 *
 * @param routeDef          RouteDefinition 对象
 * @param app               VextApp 实例
 * @param prefix            文件路径推导出的路由前缀
 * @param registry          已加载的中间件注册表
 * @param _globalMiddlewares 全局中间件列表（当前由 adapter 内部处理，预留参数）
 */
function registerRouteDefinition(routeDef, app, prefix, registry, _globalMiddlewares, collector) {
    for (const route of routeDef.routes) {
        // ── 1. 拼接完整路径 ──────────────────────────────────
        const fullPath = normalizePath(prefix, route.path);
        // ── 2. 解析路由级中间件引用 ─────────────────────────
        const routeMiddlewares = [];
        if (route.options?.middlewares && route.options.middlewares.length > 0) {
            const resolved = resolveMiddlewares(route.options.middlewares, registry);
            routeMiddlewares.push(...resolved);
        }
        // ── 2.5 auth+cache 安全警告 ──────────────────────────
        if (route.options?.cache && route.options?.middlewares) {
            const hasAuth = route.options.middlewares.some((m) => (typeof m === "string" ? m : m.name).toLowerCase().includes("auth"));
            const cacheOpts = normalizeCacheOptions(route.options.cache, app.config.cache?.defaultTtl);
            if (hasAuth &&
                cacheOpts &&
                !cacheOpts.partitionKey &&
                !cacheOpts.allowAuthorizationCache) {
                app.logger.warn(`[vextjs] ⚠️ Route ${route.method} "${fullPath}" has both cache and auth middleware. ` +
                    `Authorization requests bypass response cache unless partitionKey or allowAuthorizationCache is configured. ` +
                    `Prefer partitionKey for user or tenant isolation.`);
            }
        }
        // ── 2.6 构建路由级响应缓存中间件 ──────────────────────
        if (app.config.cache?.enabled !== false) {
            const cacheOpts = normalizeCacheOptions(route.options?.cache, app.config.cache?.defaultTtl);
            const cacheMiddleware = buildRouteCacheMiddleware(cacheOpts, () => app.cache._getResponseCache());
            if (cacheMiddleware) {
                routeMiddlewares.push(cacheMiddleware);
            }
        }
        // ── 2.7 路由级 multipart 解析中间件 ──────────────────
        //
        // 当路由声明 multipart.enabled = true 时，自动注入路由级解析中间件。
        // 插入到用户中间件链之前（第一个执行），确保 req.files 对所有后续中间件可见。
        // 行为：
        //   - 若全局 body-parser 已解析（req.files 不为 undefined）：只做路由级二次校验
        //   - 若全局未解析：用合并配置（路由级覆盖全局默认值）解析 multipart
        //
        if (route.options?.multipart
            ?.enabled === true) {
            const routeMultipartMW = createRouteMultipartMiddleware(route.options
                .multipart, app.config.multipart, app.config.bodyParser);
            routeMiddlewares.unshift(routeMultipartMW);
        }
        // ── 3. 构建 validate 中间件（Phase 1 升级）──────────
        //
        // 当 route.options.validate 存在时，通过 buildValidateMiddleware()
        // 在启动时预编译 schema，生成校验中间件。
        // 校验中间件插入到路由级中间件之后、handler 之前。
        //
        const validateMiddleware = buildValidateMiddleware(route.options?.validate, () => app.getValidator());
        // ── 4. 将 handler 包装为 VextMiddleware ─────────────
        // handler 是执行链的最后一环，不调用 next()
        const handlerMiddleware = (req, res, _next) => route.handler(req, res);
        // ── 5. 组装执行链 ──────────────────────────────────
        const chain = [...routeMiddlewares];
        if (validateMiddleware) {
            chain.push(validateMiddleware);
        }
        chain.push(handlerMiddleware);
        // ── 6. 注册到 adapter ──────────────────────────────────
        app.adapter.registerRoute(route.method, fullPath, chain, route.options ?? {});
        // ── 7. 🆕 收集路由元信息（用于 OpenAPI 文档生成）────────
        if (collector) {
            collector.addRoute(route.method, fullPath, route.options ?? {}, routeDef.sourceFile);
        }
    }
}
// ── 文件扫描 ──────────────────────────────────────────────────
/**
 * 支持的路由文件扩展名
 */
const SUPPORTED_EXTENSIONS = new Set([".ts", ".js", ".mjs", ".cjs"]);
/**
 * 应排除的文件模式
 */
function shouldExclude(filename) {
    // 排除以 _ 开头的文件（约定：私有/辅助文件）
    if (filename.startsWith("_"))
        return true;
    // 排除类型声明文件
    if (filename.endsWith(".d.ts"))
        return true;
    return false;
}
/**
 * 检测路由目录内的测试文件（Fail Fast）
 *
 * routes/ 目录内不应包含 .test. 或 .spec. 文件，
 * 这些文件应放到 test/routes/ 目录中。
 *
 * @param filename 文件名
 * @returns true 表示是测试文件
 */
function isTestFile(filename) {
    return filename.includes(".test.") || filename.includes(".spec.");
}
/**
 * 递归扫描 routes/ 目录下的所有路由文件
 *
 * @param dir 当前扫描的目录路径
 * @returns 所有路由文件的绝对路径数组
 */
async function scanRouteFiles(dir) {
    const files = [];
    const entries = await readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
        const fullPath = join(dir, entry.name);
        if (entry.isDirectory()) {
            // 跳过以 _ 或 . 开头的目录
            if (entry.name.startsWith("_") || entry.name.startsWith("."))
                continue;
            // 递归扫描子目录
            const subFiles = await scanRouteFiles(fullPath);
            files.push(...subFiles);
        }
        else if (entry.isFile()) {
            const ext = extname(entry.name);
            if (!SUPPORTED_EXTENSIONS.has(ext))
                continue;
            if (shouldExclude(entry.name))
                continue;
            // Fail Fast：路由目录内不应包含测试文件
            if (isTestFile(entry.name)) {
                throw new Error(`[vextjs] Unexpected file in routes/: ${entry.name}\n` +
                    `         routes/ should only contain route files.\n` +
                    `         Move test files to test/routes/ instead.`);
            }
            files.push(fullPath);
        }
    }
    return files;
}
// ── 路径映射 ──────────────────────────────────────────────────
/**
 * 文件路径 → 路由前缀转换
 *
 * 转换规则：
 *   1. 取相对路径（去掉 routesDir 前缀）
 *   2. 统一路径分隔符为 /
 *   3. 去掉扩展名
 *   4. index → 空字符串（使用上级目录路径）
 *   5. [param] 段 → :param（动态段转换）
 *   6. 确保以 / 开头
 *
 * @param filePath  路由文件的绝对路径
 * @param routesDir routes/ 目录的绝对路径
 * @returns 路由前缀（如 /users、/api/users、/users/:id）
 *
 * @example
 * filePathToPrefix('/app/src/routes/users.ts', '/app/src/routes')
 * // → '/users'
 *
 * filePathToPrefix('/app/src/routes/api/index.ts', '/app/src/routes')
 * // → '/api'
 *
 * filePathToPrefix('/app/src/routes/users/[id].ts', '/app/src/routes')
 * // → '/users/:id'
 */
function filePathToPrefix(filePath, routesDir) {
    // 1. 取相对路径
    let rel = relative(routesDir, filePath);
    // 2. 统一路径分隔符为 /（Windows 兼容）
    rel = rel.split(sep).join("/");
    // 3. 去掉扩展名
    const ext = extname(rel);
    rel = rel.slice(0, -ext.length);
    // 4. 处理 index（去除末尾的 /index）
    if (rel === "index") {
        rel = "";
    }
    else if (rel.endsWith("/index")) {
        rel = rel.slice(0, -"/index".length);
    }
    // 5. 动态段转换：[param] → :param
    rel = rel.replace(/\[([^\]]+)\]/g, ":$1");
    // 6. 确保以 / 开头
    if (!rel.startsWith("/")) {
        rel = `/${rel}`;
    }
    // 去除尾部 /（根路径 / 除外）
    if (rel.length > 1 && rel.endsWith("/")) {
        rel = rel.slice(0, -1);
    }
    return rel;
}
// ── 前缀冲突检测 ────────────────────────────────────────────
/**
 * 检测前缀冲突
 *
 * 两个不同的文件映射到相同的路由前缀时 Fail Fast 报错。
 * 典型冲突：routes/users.ts 和 routes/users/index.ts（两者前缀均为 /users）。
 *
 * @param entries 所有路由条目
 * @throws 存在前缀冲突时抛出错误
 */
function detectPrefixConflicts(entries) {
    const prefixMap = new Map();
    for (const entry of entries) {
        const existing = prefixMap.get(entry.prefix);
        if (existing) {
            throw new Error(`[vextjs] Route prefix conflict detected:\n` +
                `         Prefix: "${entry.prefix}"\n` +
                `         File 1: ${existing}\n` +
                `         File 2: ${entry.filePath}\n` +
                `         Two route files cannot map to the same prefix. ` +
                `Consider merging them into a single file or reorganizing the directory structure.`);
        }
        prefixMap.set(entry.prefix, entry.filePath);
    }
}
// ── 排序 ────────────────────────────────────────────────────
/**
 * 路由条目排序比较函数
 *
 * 规则：静态段优先于动态段
 *   - 不含 : 的路径排在前面
 *   - 同类型按字母序排列
 *
 * 这确保了 /users/profile 在 /users/:id 之前注册，
 * 防止动态段路由拦截静态路径。
 *
 * @param a 路由条目 A
 * @param b 路由条目 B
 * @returns 排序值（负数 = a 在前，正数 = b 在前）
 */
function compareRouteEntries(a, b) {
    const aIsDynamic = a.prefix.includes(":");
    const bIsDynamic = b.prefix.includes(":");
    // 静态路由优先
    if (!aIsDynamic && bIsDynamic)
        return -1;
    if (aIsDynamic && !bIsDynamic)
        return 1;
    // 同类型按字母序
    return a.prefix.localeCompare(b.prefix);
}
// ── 文件加载 ────────────────────────────────────────────────
/**
 * 加载单个路由文件
 *
 * 通过 dynamic import 加载路由模块，获取其 default export。
 * default export 必须是 defineRoutes() 返回的 RouteDefinition 对象。
 *
 * Fail Fast：
 *   - 无 default export → 报错
 *   - default export 不是有效的 RouteDefinition → 报错
 *
 * @param filePath 路由文件的绝对路径
 * @param app      VextApp 实例
 * @returns RouteDefinition 对象，加载失败时返回 null
 */
async function loadRouteFile(filePath, app) {
    try {
        // 将 Windows 路径转换为 file:// URL（dynamic import 兼容性）
        // 添加 cache-busting query string，避免 ESM 模块缓存导致重复加载时
        // _factory/_collector 已被 executeRouteFactory 删除。
        // 这在测试场景（多次 createTestApp）和 Phase 2 热重载中都是必要的。
        const fileUrl = `${pathToFileUrl(filePath)}?t=${Date.now()}`;
        const mod = await import(fileUrl);
        const routeDefinition = resolveModuleDefault(mod);
        if (!routeDefinition) {
            throw new Error(`[vextjs] Route file "${filePath}" has no default export.\n` +
                `         Route files must use: export default defineRoutes((app) => { ... })`);
        }
        // 验证是否为有效的 RouteDefinition（至少有 routes 数组和 register 方法）
        if (!Array.isArray(routeDefinition.routes) ||
            typeof routeDefinition.register !== "function") {
            throw new Error(`[vextjs] Route file "${filePath}" default export is not a valid RouteDefinition.\n` +
                `         Make sure to use defineRoutes() to create the route definition.`);
        }
        // 执行 factory 回调（传入真正的 app 引用，收集路由）
        executeRouteFactory(routeDefinition, app);
        return routeDefinition;
    }
    catch (err) {
        // 如果是我们自己抛出的 vextjs 错误，直接抛出（Fail Fast）
        if (err instanceof Error && err.message.startsWith("[vextjs]")) {
            throw err;
        }
        // 其他错误（如语法错误、模块找不到），包装后抛出
        throw new Error(`[vextjs] Failed to load route file: ${filePath}\n` +
            `         ${err.message}`);
    }
}
// ── 路径规范化 ──────────────────────────────────────────────
/**
 * 规范化路由路径
 *
 * 将前缀和子路径拼接为完整路径，处理边界情况：
 *   - 去除重复的 /
 *   - 确保路径以 / 开头
 *   - 去除尾部 /（根路径 / 除外）
 *
 * @param prefix  文件路径推导出的路由前缀（如 /api/users）
 * @param subPath 文件内注册的子路径（如 /list、/:id、/）
 * @returns 规范化后的完整路径
 *
 * @example
 * normalizePath('/users', '/list')   → '/users/list'
 * normalizePath('/users', '/')       → '/users'
 * normalizePath('/users', '/:id')    → '/users/:id'
 * normalizePath('/', '/')            → '/'
 * normalizePath('/api/users', '')    → '/api/users'
 */
function normalizePath(prefix, subPath) {
    // 去除前缀尾部 /（根路径 '/' 除外）
    const cleanPrefix = prefix.endsWith("/") && prefix.length > 1 ? prefix.slice(0, -1) : prefix;
    // 去除子路径的前导 /（避免拼接后出现 //）
    const cleanSubPath = subPath.startsWith("/") ? subPath.slice(1) : subPath;
    // 拼接
    if (!cleanSubPath) {
        // 子路径为空或 '/'，直接使用前缀
        return cleanPrefix || "/";
    }
    // 当 prefix 是根路径 '/' 时，直接拼接为 '/' + subPath，避免 '//health'
    if (cleanPrefix === "/") {
        return `/${cleanSubPath}`;
    }
    const fullPath = `${cleanPrefix}/${cleanSubPath}`;
    // 去除尾部 /（根路径除外）
    if (fullPath.length > 1 && fullPath.endsWith("/")) {
        return fullPath.slice(0, -1);
    }
    return fullPath;
}
// ── MiddlewareRegistry 兼容层 ────────────────────────────────
/**
 * normalizeMiddlewareDefs — 将 Phase 0 的 Map 或 Phase 1 的 Registry 统一为 MiddlewareRegistry
 *
 * Phase 0 bootstrap 传入 `new Map()` → 转换为空 MiddlewareRegistry
 * Phase 1 bootstrap 传入 MiddlewareRegistry → 直接使用
 *
 * @param defs 中间件定义（Map 或 Registry）
 * @returns MiddlewareRegistry
 */
function normalizeMiddlewareDefs(defs) {
    // 如果是 Map 类型（Phase 0 兼容），转换为 Registry 格式
    if (defs instanceof Map) {
        const registry = {};
        for (const [name, handler] of defs.entries()) {
            registry[name] = {
                handler,
                defaultOptions: undefined,
                kind: "middleware",
            };
        }
        return registry;
    }
    // 已经是 MiddlewareRegistry 对象
    return defs;
}
// ── 工具函数 ────────────────────────────────────────────────
/**
 * 检查目录是否存在
 *
 * @param dirPath 目录路径
 * @returns 目录是否存在
 */
async function directoryExists(dirPath) {
    try {
        const s = await stat(dirPath);
        return s.isDirectory();
    }
    catch {
        return false;
    }
}
/**
 * 将文件系统路径转为 file:// URL
 *
 * dynamic import 在 Windows 上需要 file:// 协议前缀才能正确加载。
 * Unix 系统上也兼容 file:// URL。
 *
 * @param filePath 文件的绝对路径
 * @returns file:// URL 字符串
 */
function pathToFileUrl(filePath) {
    return pathToFileURL(filePath).href;
}
