/**
 * ============================================================
 *  src/plugins/pluginLoader.ts —— plugin entry dynamic import 缓存层
 * ------------------------------------------------------------
 *  按 pluginRoot 路径 lazy import 插件目录下的 plugin.js（优先）
 *  或 plugin.ts（回退）。
 *
 *  双候选探测的原因：
 *   - install 模式（npm 包内）：tsc 编译后只剩 plugin.js，Node + Bun
 *     都能加载
 *   - dev 模式（仓库源码）：物理只有 plugin.ts，Bun 直接吃
 *   - 用户 cwd 自带插件：可能写 .ts（须 Bun 跑）或 .js（Node + Bun 都行）
 *
 *  契约：
 *   - 两个文件都不存在 → 缓存 null（纯 markdown 声明式插件）
 *   - 两个文件都存在 + plugin.ts mtime > plugin.js mtime → console.warn
 *     提示 stale（dev 模式跑过 build 后又改 .ts 源码的常见坑），加载行为
 *     不变仍优先 plugin.js
 *   - import 失败（语法错 / 运行时抛错）→ console.warn + 缓存 null
 *   - import 成功但 default 不是对象 → 缓存 null
 *   - 缓存 key = pluginRoot 绝对路径
 *
 *  框架 hot-reload 由测试调用 _resetPluginLoader() 实现。
 * ============================================================
 */
import { existsSync, statSync } from 'node:fs';
import { basename, join } from 'node:path';
import { pathToFileURL } from 'node:url';
const loaderCache = new Map();
/**
 * 内置插件静态注册表（仅绿色版 compile 入口 entry.compiled.tsx 会填充）。
 *
 * 背景：`bun --compile` 后，rich 插件的 plugin.ts 顺链 import 的框架 SDK
 * （`../../../src/plugin-sdk.js`）已被焊进 exe、磁盘上不存在 → 动态 import
 * 外部 plugin.ts 必抛错。绿色版改为在 compile 入口**静态 import** 内置插件
 * （`src/plugins/builtinRegistry.ts`），让 bun 把执行器整树打进 exe，再经
 * setBuiltinPluginApi 注册到这里；loadPluginApi 在磁盘加载缺失/失败时回退到此表。
 *
 * dev / npm 模式：entry.compiled 永不被 import → 此表恒为空 → 回退分支不触发
 * → loadPluginApi 行为与改动前逐字节一致。
 *
 * key = 插件目录名（`plugins/<id>/` 的 `<id>`，等于 `basename(pluginRoot)`）。
 */
const builtinApis = new Map();
/** 由绿色版 compile 入口调用，注册内置插件执行器。dev/npm 不会调用。 */
export function setBuiltinPluginApi(key, api) {
    builtinApis.set(key, api);
}
function isPluginApi(value) {
    if (!value || typeof value !== 'object')
        return false;
    const { runCommand } = value;
    if (runCommand !== undefined && typeof runCommand !== 'function')
        return false;
    return true;
}
/**
 * 加载插件目录下的 plugin.ts；不存在或失败返回 null。
 * 同一 pluginRoot 多次调用走缓存，避免重复 import。
 */
export async function loadPluginApi(pluginRoot) {
    if (loaderCache.has(pluginRoot)) {
        return loaderCache.get(pluginRoot) ?? null;
    }
    // prefer plugin.js（install / Node 兼容），fallback plugin.ts（dev / 用户 Bun 插件）
    const jsPath = join(pluginRoot, 'plugin.js');
    const tsPath = join(pluginRoot, 'plugin.ts');
    const jsExists = existsSync(jsPath);
    const tsExists = existsSync(tsPath);
    // dev 模式 stale 检测：两文件都存在 + .ts 比 .js 新 → 用户改了源码但没清编译产物
    if (jsExists && tsExists) {
        try {
            const jsMtime = statSync(jsPath).mtimeMs;
            const tsMtime = statSync(tsPath).mtimeMs;
            if (tsMtime > jsMtime) {
                console.warn(`[minimal-agent] 检测到 stale plugin.js（${pluginRoot}）：plugin.ts 比 plugin.js 新，` +
                    `加载器仍优先吃 plugin.js。如果你刚改过源码却没生效，请跑 \`bun run clean\` 删掉原地编译产物。`);
            }
        }
        catch {
            // stat 失败静默，不影响加载
        }
    }
    // 绿色版回退：磁盘加载缺失/失败时，按目录名查 exe 内静态注册表。
    // dev/npm 注册表恒空 → 永远返回 null → 不改变下方任何分支的最终结果。
    const builtinKey = basename(pluginRoot);
    const builtinFallback = () => {
        const api = builtinApis.get(builtinKey);
        if (api) {
            loaderCache.set(pluginRoot, api);
            return api;
        }
        return null;
    };
    const pluginEntry = jsExists ? jsPath : tsExists ? tsPath : undefined;
    if (!pluginEntry) {
        // 磁盘无 plugin.js/.ts：绿色目录只铺声明式部分（plugin.json + commands/），
        // 执行器在 exe 内注册表 → 这里命中。dev/npm 注册表空 → 维持原 null。
        const fb = builtinFallback();
        if (fb)
            return fb;
        loaderCache.set(pluginRoot, null);
        return null;
    }
    try {
        const mod = await import(pathToFileURL(pluginEntry).href);
        const candidate = mod.default;
        if (!isPluginApi(candidate)) {
            console.warn(`[minimal-agent] plugin.ts default export is not a valid PluginApi: ${pluginRoot}`);
            loaderCache.set(pluginRoot, null);
            return null;
        }
        loaderCache.set(pluginRoot, candidate);
        return candidate;
    }
    catch (err) {
        // 绿色版：磁盘铺了 plugin.js 但它顺链 import 的框架 SDK 在 exe 内、磁盘没有
        // → import 抛错 → 回退到 exe 内注册表。dev/npm 注册表空 → 维持原 warn + null。
        const fb = builtinFallback();
        if (fb)
            return fb;
        console.warn(`[minimal-agent] failed to load plugin entry at ${pluginRoot}: ${err instanceof Error ? err.message : String(err)}`);
        loaderCache.set(pluginRoot, null);
        return null;
    }
}
/** 测试用：重置加载器缓存。 */
export function _resetPluginLoader() {
    loaderCache.clear();
}
