import { readdir, stat, readFile } from "node:fs/promises";
import { existsSync, readFileSync } from "node:fs";
import { join, extname, dirname } from "node:path";
import { createRequire } from "node:module";
import { resolveModuleDefault } from "./interop.js";
import { pathToFileURL } from "node:url";
// ── ESM-only 包兼容层 ─────────────────────────────────────────
//
// vext dev 编译器将用户代码输出为 CJS，当用户插件 import 了一个
// ESM-only 包（package.json 有 "type":"module" 但无 "require" 条件）时，
// CJS require() 会抛 ERR_REQUIRE_ESM。
//
// 解决方案：
//   1. 加载插件前扫描其 require() 调用
//   2. 对 ESM-only 包提前 await import() 拿到模块命名空间
//   3. 注入 Module._load 缓存，让后续 require() 命中预加载值
//
// 注意：plugin-loader.ts 以 ESM 格式编译（vextjs 使用 NodeNext module），
//       不能使用全局 require()，所有 CJS 调用必须通过 createRequire 创建的 _req。
/** module-level CJS require，供 ESM 兼容层内部使用 */
const _req = createRequire(import.meta.url);
/** ESM-only 包的预加载缓存：pkgId → 模块命名空间对象 */
const _esmPreloadCache = new Map();
/** 是否已 patch Module._load */
let _moduleLoadPatched = false;
/**
 * （一次性）patch Node.js Module._load，拦截对已预加载 ESM 包的 require()。
 *
 * 使用 _req("node:module") 而非全局 require()（ESM 上下文无全局 require）。
 */
function _ensureModuleLoadPatch() {
    if (_moduleLoadPatched)
        return;
    _moduleLoadPatched = true;
    const Module = _req("node:module");
    const _orig = Module._load;
    Module._load = function (request, ...args) {
        if (_esmPreloadCache.has(request)) {
            return _esmPreloadCache.get(request);
        }
        return _orig.call(this, request, ...args);
    };
}
/**
 * 读取包的 package.json。
 *
 * 两步策略：
 *   1. 直接 _req.resolve('pkg/package.json')（快速路径，但 strict exports 会报错）
 *   2. 回退：_req.resolve('pkg') 定位主文件，向上遍历目录找 package.json
 *
 * @returns 解析后的 package.json 对象，找不到返回 null
 */
function _findPkgJsonPath(pkgId, resolver) {
    try {
        // 方法 1：直接解析 package.json（包允许访问时最快）
        try {
            return resolver.resolve(`${pkgId}/package.json`);
        }
        catch {
            // strict exports 不导出 ./package.json 时继续走 node_modules 搜索路径
        }
        // 方法 2：基于当前插件文件的 module resolution paths 手动定位包目录
        const searchPaths = resolver.resolve.paths(pkgId) ?? [];
        const pkgSegments = pkgId.split("/");
        for (const searchPath of searchPaths) {
            const candidate = join(searchPath, ...pkgSegments, "package.json");
            if (existsSync(candidate)) {
                return candidate;
            }
        }
    }
    catch {
        // ignore
    }
    return null;
}
function _readPkgJson(pkgId, resolver) {
    try {
        const pkgJsonPath = _findPkgJsonPath(pkgId, resolver);
        if (!pkgJsonPath)
            return null;
        return JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
    }
    catch {
        return null;
    }
}
function _parsePackageRequest(requestId) {
    const parts = requestId.split("/");
    const packageParts = requestId.startsWith("@")
        ? parts.slice(0, 2)
        : parts.slice(0, 1);
    const subpathParts = parts.slice(packageParts.length);
    return {
        packageId: packageParts.join("/"),
        exportKey: subpathParts.length === 0 ? "." : `./${subpathParts.join("/")}`,
    };
}
function _getExportEntry(pkgJson, exportKey) {
    const exports = pkgJson["exports"];
    if (exports === undefined)
        return undefined;
    if (typeof exports !== "object" || exports === null) {
        return exportKey === "." ? exports : undefined;
    }
    const exportsMap = exports;
    if (Object.prototype.hasOwnProperty.call(exportsMap, exportKey)) {
        return exportsMap[exportKey];
    }
    return exportKey === "." ? exportsMap : undefined;
}
function _resolveEsmEntryPath(requestId, resolver) {
    const { packageId, exportKey } = _parsePackageRequest(requestId);
    const pkgJsonPath = _findPkgJsonPath(packageId, resolver);
    if (!pkgJsonPath)
        return null;
    const pkgJson = _readPkgJson(packageId, resolver);
    if (!pkgJson)
        return null;
    const pkgDir = dirname(pkgJsonPath);
    const entry = _getExportEntry(pkgJson, exportKey);
    if (typeof entry === "string") {
        return join(pkgDir, entry);
    }
    if (entry !== null && typeof entry === "object") {
        const cond = entry;
        if (typeof cond["import"] === "string") {
            return join(pkgDir, cond["import"]);
        }
        if (typeof cond["default"] === "string") {
            return join(pkgDir, cond["default"]);
        }
    }
    if (exportKey !== ".")
        return null;
    if (typeof pkgJson["main"] === "string") {
        return join(pkgDir, pkgJson["main"]);
    }
    return join(pkgDir, "index.js");
}
/**
 * 检测一个外部包是否为 ESM-only（无法被 CJS require() 加载）。
 *
 * 判断标准：
 *   - package.json 中 "type" === "module" 且
 *   - exports["."] 只有 "import" 条件，无 "require" / "default" 条件
 */
function _isEsmOnly(requestId, resolver) {
    const { packageId, exportKey } = _parsePackageRequest(requestId);
    const pkgJson = _readPkgJson(packageId, resolver);
    if (!pkgJson)
        return false;
    if (pkgJson["type"] !== "module")
        return false;
    const exports = pkgJson["exports"];
    if (!exports)
        return true; // type:module 无 exports → ESM-only
    const entry = _getExportEntry(pkgJson, exportKey);
    if (typeof entry !== "object" || entry === null)
        return false;
    const cond = entry;
    // 有 require 或 default 条件 → 支持 CJS
    if (cond["require"] !== undefined || cond["default"] !== undefined) {
        return false;
    }
    // 只有 import / types → ESM-only
    return cond["import"] !== undefined;
}
/**
 * 扫描已编译的 CJS 插件文件，找出其中 require() 的 ESM-only 包，
 * 提前 await import() 并注入缓存，避免运行时 ERR_REQUIRE_ESM 错误。
 *
 * @param compiledFilePath 编译产物的绝对路径（.vext/dev/plugins/xxx.js）
 */
async function _preloadEsmDeps(compiledFilePath) {
    const resolver = createRequire(compiledFilePath);
    let content;
    try {
        content = await readFile(compiledFilePath, "utf-8");
    }
    catch {
        return; // 文件不存在或读取失败，跳过
    }
    // 提取所有 require('pkg') / require("pkg") 中的外部包名（排除相对路径）
    const requireRegex = /require\s*\(\s*["']([^./][^"']*?)["']\s*\)/g;
    const candidates = new Set();
    for (const m of content.matchAll(requireRegex)) {
        candidates.add(m[1]);
    }
    for (const requestId of candidates) {
        if (_esmPreloadCache.has(requestId))
            continue;
        if (!_isEsmOnly(requestId, resolver))
            continue;
        try {
            const esmEntryPath = _resolveEsmEntryPath(requestId, resolver);
            if (!esmEntryPath)
                continue;
            const mod = await import(pathToFileUrl(esmEntryPath));
            _esmPreloadCache.set(requestId, mod);
        }
        catch {
            // 预加载失败，忽略（让 require() 在运行时自然报错，给出真实错误信息）
        }
    }
}
/**
 * loadPlugins — 扫描 plugins/ 目录，拓扑排序后依次执行 setup
 *
 * @param app        VextApp 实例（plugin 的 setup 接收此对象）
 * @param pluginsDir plugins/ 目录的绝对路径（如 /path/to/my-app/src/plugins）
 * @param options    加载选项
 *
 * @example
 * ```typescript
 * // bootstrap 内部
 * await loadPlugins(app, path.join(rootDir, 'src/plugins'), {
 *   setupTimeout: app.config.plugin?.setupTimeout ?? 30_000,
 * })
 * ```
 */
export async function loadPlugins(app, pluginsDir, options = {}) {
    const { setupTimeout = 30_000 } = options;
    // ── 1. 检查 plugins/ 目录是否存在 ─────────────────────────
    const dirExists = await directoryExists(pluginsDir);
    if (!dirExists) {
        app.logger.debug("[vextjs] Plugins directory not found, skipping plugin loading.");
        return;
    }
    // ── 2. 递归扫描所有插件文件 ────────────────────────────────
    const pluginFiles = await scanPluginFiles(pluginsDir);
    if (pluginFiles.length === 0) {
        app.logger.debug("[vextjs] No plugin files found, skipping plugin loading.");
        return;
    }
    // ── 3. 动态 import 每个文件，收集 VextPlugin 实例 ──────────
    const plugins = [];
    const nameSet = new Map(); // name → sourceFile（重复检测）
    for (const filePath of pluginFiles) {
        const plugin = await loadPluginFile(filePath, pluginsDir);
        // Fail Fast：插件名称重复
        const existing = nameSet.get(plugin.name);
        if (existing) {
            throw new Error(`[vextjs] Plugin name "${plugin.name}" is already registered.\n` +
                `         First:    ${existing}\n` +
                `         Conflict: ${filePath}\n` +
                `         Each plugin must have a unique name.`);
        }
        nameSet.set(plugin.name, filePath);
        plugins.push({ plugin, sourceFile: filePath });
    }
    // ── 4. 拓扑排序（按 dependencies 字段）────────────────────
    const sorted = topoSort(plugins, nameSet);
    const lifecycleLevel = app.config.logger?.lifecycleLevel ?? "concise";
    // ── 5. 依次执行 setup（含超时保护）────────────────────────
    for (const { plugin, sourceFile } of sorted) {
        if (lifecycleLevel === "verbose") {
            app.logger.info(`[plugin] loading: ${plugin.name}`);
        }
        await executeSetupWithTimeout(plugin, app, setupTimeout, sourceFile);
        if (lifecycleLevel === "verbose") {
            app.logger.info(`[plugin] loaded:  ${plugin.name}`);
        }
    }
    app.logger.info(`[vextjs] ${sorted.length} plugin(s) loaded`);
}
// ── 文件扫描 ──────────────────────────────────────────────────
/**
 * 支持的插件文件扩展名
 */
const SUPPORTED_EXTENSIONS = new Set([".ts", ".js", ".mjs", ".cjs"]);
/**

 * 应排除的文件
 */
function shouldExclude(filename) {
    // 排除 _ 开头的文件（约定：辅助/私有文件）
    if (filename.startsWith("_"))
        return true;
    // 排除测试文件
    if (filename.includes(".test.") || filename.includes(".spec."))
        return true;
    // 排除类型声明文件
    if (filename.endsWith(".d.ts"))
        return true;
    return false;
}
/**
 * 递归扫描 plugins/ 目录下的所有插件文件
 *
 * @param dir 当前扫描的目录路径
 * @returns 所有插件文件的绝对路径数组
 */
async function scanPluginFiles(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 scanPluginFiles(fullPath);
            files.push(...subFiles);
        }
        else if (entry.isFile()) {
            const ext = extname(entry.name);
            if (!SUPPORTED_EXTENSIONS.has(ext))
                continue;
            if (shouldExclude(entry.name))
                continue;
            files.push(fullPath);
        }
    }
    return files;
}
// ── 文件加载 ──────────────────────────────────────────────────
/**
 * 加载单个插件文件
 *
 * 通过 dynamic import 加载插件模块，获取其 default export。
 * default export 必须是包含 name + setup 的对象（即 definePlugin 返回值）。
 *
 * @param filePath   插件文件的绝对路径
 * @param pluginsDir plugins/ 目录的绝对路径（用于错误信息中的相对路径显示）
 * @returns VextPlugin 对象
 * @throws 文件无 default export 或格式不合法
 */
async function loadPluginFile(filePath, _pluginsDir) {
    try {
        // ESM-only 兼容：预加载此插件依赖的 ESM-only 包，注入 Module._load 缓存
        _ensureModuleLoadPatch();
        await _preloadEsmDeps(filePath);
        const fileUrl = pathToFileUrl(filePath);
        const mod = await import(fileUrl);
        const plugin = resolveModuleDefault(mod);
        // Fail Fast：无 default export
        if (!plugin) {
            throw new Error(`[vextjs] Plugin file has no default export.\n` +
                `         File: ${filePath}\n` +
                `         Must export default definePlugin({ name, setup })`);
        }
        // Fail Fast：default export 不是合法的 plugin 对象
        if (typeof plugin !== "object" ||
            plugin === null ||
            typeof plugin.name !== "string" ||
            !plugin.name ||
            typeof plugin.setup !== "function") {
            throw new Error(`[vextjs] Plugin file default export is not a valid plugin.\n` +
                `         File: ${filePath}\n` +
                `         Expected: definePlugin({ name: string, setup: (app) => void | Promise<void> })\n` +
                `         Got: ${describeValue(plugin)}`);
        }
        // 验证 dependencies 字段（如果存在必须是字符串数组）
        if (plugin.dependencies !== undefined) {
            if (!Array.isArray(plugin.dependencies) ||
                !plugin.dependencies.every((d) => typeof d === "string")) {
                throw new Error(`[vextjs] Plugin "${plugin.name}" has invalid dependencies field.\n` +
                    `         File: ${filePath}\n` +
                    `         Expected: string[] | undefined\n` +
                    `         Got: ${describeValue(plugin.dependencies)}`);
            }
        }
        return plugin;
    }
    catch (err) {
        // 如果是我们自己抛出的 vextjs 错误，直接抛出
        if (err instanceof Error && err.message.startsWith("[vextjs]")) {
            throw err;
        }
        // 其他错误（语法错误、模块找不到等），包装后抛出
        throw new Error(`[vextjs] Failed to load plugin file: ${filePath}\n` +
            `         ${err.message}`);
    }
}
// ── 拓扑排序（Kahn 算法）──────────────────────────────────────
/**
 * topoSort — 按 dependencies 字段对插件进行拓扑排序
 *
 * 使用 Kahn 算法：
 *   1. 构建邻接表和入度表
 *   2. 将入度为 0 的节点入队
 *   3. BFS 逐个处理，减少后继节点入度
 *   4. 处理完毕后若有节点未访问 → 存在循环依赖
 *
 * Fail Fast 检测：
 *   - dependencies 引用不存在的插件名 → 抛错
 *   - 循环依赖 → 抛错（附带环路链描述）
 *
 * @param plugins 所有已加载的插件（含源文件信息）
 * @param nameMap 插件名 → 源文件路径映射
 * @returns 拓扑排序后的插件数组
 */
function topoSort(plugins, _nameMap) {
    // 按名称建索引
    const byName = new Map();
    for (const entry of plugins) {
        byName.set(entry.plugin.name, entry);
    }
    // 构建邻接表（name → 依赖它的 names）和入度表
    const adjacency = new Map(); // 被依赖 → 依赖者列表
    const inDegree = new Map();
    for (const entry of plugins) {
        const name = entry.plugin.name;
        if (!adjacency.has(name))
            adjacency.set(name, []);
        if (!inDegree.has(name))
            inDegree.set(name, 0);
    }
    for (const entry of plugins) {
        const name = entry.plugin.name;
        const deps = entry.plugin.dependencies ?? [];
        for (const dep of deps) {
            // Fail Fast：依赖的插件不存在
            if (!byName.has(dep)) {
                throw new Error(`[vextjs] Plugin "${name}" depends on "${dep}" which is not found.\n` +
                    `         File: ${entry.sourceFile}\n` +
                    `         Available plugins: ${[...byName.keys()].join(", ") || "(none)"}\n` +
                    `         Make sure the dependency plugin exists in src/plugins/.`);
            }
            // dep → name（dep 完成后 name 才能执行）
            adjacency.get(dep).push(name);
            inDegree.set(name, (inDegree.get(name) ?? 0) + 1);
        }
    }
    // Kahn BFS
    const queue = [];
    for (const [name, degree] of inDegree) {
        if (degree === 0)
            queue.push(name);
    }
    // 入度为 0 的节点按字母序排列（保证确定性顺序）
    queue.sort();
    const sorted = [];
    while (queue.length > 0) {
        const current = queue.shift();
        sorted.push(byName.get(current));
        const dependents = adjacency.get(current) ?? [];
        for (const dep of dependents) {
            const newDegree = (inDegree.get(dep) ?? 1) - 1;
            inDegree.set(dep, newDegree);
            if (newDegree === 0) {
                // 插入时保持字母序（确定性排序）
                const insertIdx = queue.findIndex((q) => q.localeCompare(dep) > 0);
                if (insertIdx === -1) {
                    queue.push(dep);
                }
                else {
                    queue.splice(insertIdx, 0, dep);
                }
            }
        }
    }
    // 检测循环依赖：如果有节点未被处理，则存在环
    if (sorted.length !== plugins.length) {
        const remaining = plugins
            .filter((p) => !sorted.some((s) => s.plugin.name === p.plugin.name))
            .map((p) => p.plugin.name);
        // 尝试找出一个具体的环路（用 DFS）
        const cycle = findCycle(remaining, plugins);
        throw new Error(`[vextjs] Circular dependency detected in plugins: ${cycle}\n` +
            `         Break the cycle by removing or restructuring dependencies.`);
    }
    return sorted;
}
/**
 * findCycle — 在剩余未排序的节点中找出一个循环依赖链
 *
 * @param remaining 未被 Kahn 算法处理的插件名列表
 * @param plugins   所有插件
 * @returns 循环链的字符串表示（如 "a → b → c → a"）
 */
function findCycle(remaining, plugins) {
    const remainingSet = new Set(remaining);
    // 只在环路涉及的节点中构建依赖图
    const deps = new Map();
    for (const entry of plugins) {
        if (remainingSet.has(entry.plugin.name)) {
            deps.set(entry.plugin.name, (entry.plugin.dependencies ?? []).filter((d) => remainingSet.has(d)));
        }
    }
    // DFS 找环
    const visited = new Set();
    const stack = new Set();
    const path = [];
    function dfs(node) {
        if (stack.has(node)) {
            const cycleStart = path.indexOf(node);
            return [...path.slice(cycleStart), node].join(" → ");
        }
        if (visited.has(node))
            return null;
        visited.add(node);
        stack.add(node);
        path.push(node);
        for (const dep of deps.get(node) ?? []) {
            const result = dfs(dep);
            if (result)
                return result;
        }
        stack.delete(node);
        path.pop();
        return null;
    }
    for (const name of remaining) {
        if (!visited.has(name)) {
            const result = dfs(name);
            if (result)
                return result;
        }
    }
    // 兜底：无法找出具体环路时返回列表
    return `${remaining.join(" → ")} → ...`;
}
// ── setup 超时保护 ────────────────────────────────────────────
/**
 * executeSetupWithTimeout — 执行 plugin.setup() 并施加超时保护
 *
 * 关键设计：
 *   - 使用 Promise.race([setup, timeout]) 实现超时保护
 *   - setup 完成后**必须** clearTimeout，防止定时器泄漏
 *     （定时器持有 reject 闭包 → 闭包持有 plugin/app 引用 → GC 无法回收）
 *   - setup 抛出异常时，也 clearTimeout（finally 语义）
 *
 * @param plugin       插件对象
 * @param app          VextApp 实例
 * @param timeoutMs    超时时间（毫秒）
 * @param sourceFile   源文件路径（用于错误信息）
 */
async function executeSetupWithTimeout(plugin, app, timeoutMs, sourceFile) {
    let timer;
    try {
        await Promise.race([
            // setup 执行
            Promise.resolve(plugin.setup(app)).then((result) => {
                // setup 完成后立即清理定时器
                if (timer !== undefined) {
                    clearTimeout(timer);
                    timer = undefined;
                }
                // ── 自动注册插件级生命周期钩子（onReady / onClose）──
                //
                // 插件可以在对象上声明 onReady / onClose 可选方法，
                // plugin-loader 在 setup() 成功完成后自动将它们注册到 app 的生命周期。
                // 这等价于在 setup() 内手动调用 app.onReady(() => ...) / app.onClose(() => ...)，
                // 但语义更清晰，与 setup 形成完整的生命周期三件套。
                //
                if (typeof plugin.onReady === "function") {
                    app.onReady(() => plugin.onReady(app));
                }
                if (typeof plugin.onClose === "function") {
                    app.onClose(() => plugin.onClose(app));
                }
                return result;
            }),
            // 超时保护
            new Promise((_, reject) => {
                timer = setTimeout(() => {
                    timer = undefined;
                    reject(new Error(`[vextjs] Plugin "${plugin.name}" setup() timed out after ${timeoutMs}ms.\n` +
                        `         File: ${sourceFile}\n` +
                        `         Check for unresolved async operations (e.g. database connection without timeout).\n` +
                        `         You can increase the timeout via config.plugin.setupTimeout.`));
                }, timeoutMs);
            }),
        ]);
    }
    catch (err) {
        // 确保异常时也清理定时器
        if (timer !== undefined) {
            clearTimeout(timer);
            timer = undefined;
        }
        // 如果是我们自己的超时错误或 vextjs 错误，直接抛出
        if (err instanceof Error && err.message.startsWith("[vextjs]")) {
            throw err;
        }
        // 包装 setup 中抛出的其他错误
        throw new Error(`[vextjs] Plugin "${plugin.name}" setup() failed.\n` +
            `         File: ${sourceFile}\n` +
            `         ${err.message}`);
    }
}
// ── 工具函数 ──────────────────────────────────────────────────
/**
 * 检查目录是否存在
 */
async function directoryExists(dirPath) {
    try {
        const s = await stat(dirPath);
        return s.isDirectory();
    }
    catch {
        return false;
    }
}
/**
 * 将文件系统路径转为 file:// URL
 *
 * dynamic import 在 Windows 上需要 file:// 协议前缀才能正确加载。
 */
function pathToFileUrl(filePath) {
    return pathToFileURL(filePath).href;
}
/**
 * 描述一个值的类型（用于错误信息）
 */
function describeValue(value) {
    if (value === null)
        return "null";
    if (value === undefined)
        return "undefined";
    if (Array.isArray(value))
        return `Array(${value.length})`;
    if (typeof value === "object") {
        const keys = Object.keys(value);
        if (keys.length === 0)
            return "{}";
        return `{ ${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", ..." : ""} }`;
    }
    return `${typeof value}: ${String(value).slice(0, 50)}`;
}
