import fs from "node:fs";
import path from "node:path";
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { recordPluginInstall } from "../../plugins/installs.js";
import { enablePluginInConfig } from "../../plugins/enable.js";
import { loadClawdbotPlugins } from "../../plugins/loader.js";
import { installPluginFromNpmSpec } from "../../plugins/install.js";
import { resolveBundledPluginsDir } from "../../plugins/bundled-dir.js";
function normalizeSpecPackageId(spec) {
    const trimmed = spec?.trim();
    if (!trimmed)
        return "";
    if (trimmed.startsWith("@")) {
        const slash = trimmed.indexOf("/");
        if (slash === -1)
            return trimmed;
        const scope = trimmed.slice(0, slash);
        const nameAndVersion = trimmed.slice(slash + 1);
        const at = nameAndVersion.lastIndexOf("@");
        const name = at > 0 ? nameAndVersion.slice(0, at) : nameAndVersion;
        return `${scope}/${name}`;
    }
    const at = trimmed.lastIndexOf("@");
    return at > 0 ? trimmed.slice(0, at) : trimmed;
}
function hasGitWorkspace(workspaceDir) {
    const candidates = new Set();
    candidates.add(path.join(process.cwd(), ".git"));
    if (workspaceDir && workspaceDir !== process.cwd()) {
        candidates.add(path.join(workspaceDir, ".git"));
    }
    for (const candidate of candidates) {
        if (fs.existsSync(candidate))
            return true;
    }
    return false;
}
function resolveLocalPath(entry, workspaceDir, allowLocal) {
    const raw = entry.install.localPath?.trim();
    if (!raw)
        return null;
    const candidates = new Set();
    // 1. Check bundled plugins (always allowed)
    const bundledDir = resolveBundledPluginsDir();
    if (bundledDir) {
        // raw is like "extensions/feishu", bundledDir is ".../extensions"
        // So we resolve from bundledDir's parent.
        candidates.add(path.resolve(bundledDir, "..", raw));
    }
    // 2. Check local workspace (if allowed)
    if (allowLocal) {
        candidates.add(path.resolve(process.cwd(), raw));
        if (workspaceDir && workspaceDir !== process.cwd()) {
            candidates.add(path.resolve(workspaceDir, raw));
        }
    }
    for (const candidate of candidates) {
        if (fs.existsSync(candidate))
            return candidate;
    }
    return null;
}
function addPluginLoadPath(cfg, pluginPath) {
    const existing = cfg.plugins?.load?.paths ?? [];
    const merged = Array.from(new Set([...existing, pluginPath]));
    return {
        ...cfg,
        plugins: {
            ...cfg.plugins,
            load: {
                ...cfg.plugins?.load,
                paths: merged,
            },
        },
    };
}
async function promptInstallChoice(params) {
    const { entry, localPath, prompter, defaultChoice } = params;
    const localOptions = localPath
        ? [
            {
                value: "local",
                label: "使用本地插件路径",
                hint: localPath,
            },
        ]
        : [];
    const officialOptions = entry
        .install.officialSpec
        ? [
            {
                value: "official",
                label: `${entry.meta.label}官方插件`,
                hint: entry.install.officialSpec,
            },
        ]
        : [];
    // Hide the community npm option when npmSpec is the same as officialSpec
    // (i.e. no separate community build exists for this channel).
    const hasSeparateNpm = !entry.install.officialSpec ||
        normalizeSpecPackageId(entry.install.npmSpec) !==
            normalizeSpecPackageId(entry.install.officialSpec);
    const npmOptions = hasSeparateNpm
        ? [{ value: "npm", label: `中文社区版 (${entry.install.npmSpec})` }]
        : [];
    const options = [
        ...officialOptions,
        ...npmOptions,
        ...localOptions,
        { value: "skip", label: "暂时跳过" },
    ];
    const initialValue = defaultChoice === "local" && !localPath
        ? officialOptions.length > 0
            ? "official"
            : "npm"
        : defaultChoice === "npm" && officialOptions.length > 0
            ? "official"
            : defaultChoice;
    return await prompter.select({
        message: `安装 ${entry.meta.label} 插件?`,
        options,
        initialValue,
    });
}
function resolveInstallDefaultChoice(params) {
    const { cfg, entry, localPath } = params;
    const updateChannel = cfg.update?.channel;
    if (updateChannel === "dev") {
        return localPath ? "local" : "npm";
    }
    if (updateChannel === "stable" || updateChannel === "beta") {
        return "npm";
    }
    const entryDefault = entry.install.defaultChoice;
    if (entryDefault === "local")
        return localPath ? "local" : "npm";
    if (entryDefault === "npm")
        return "npm";
    return localPath ? "local" : "npm";
}
export async function ensureOnboardingPluginInstalled(params) {
    const { entry, prompter, runtime, workspaceDir } = params;
    let next = params.cfg;
    const allowLocal = hasGitWorkspace(workspaceDir);
    const localPath = resolveLocalPath(entry, workspaceDir, allowLocal);
    const defaultChoice = resolveInstallDefaultChoice({
        cfg: next,
        entry,
        localPath,
    });
    const choice = await promptInstallChoice({
        entry,
        localPath,
        defaultChoice,
        prompter,
    });
    if (choice === "skip") {
        return { cfg: next, installed: false };
    }
    if (choice === "official" && entry.install.officialSpec) {
        const result = await installPluginFromNpmSpec({
            spec: entry.install.officialSpec,
            mode: "update",
            logger: {
                info: (msg) => runtime.log?.(msg),
                warn: (msg) => runtime.log?.(msg),
            },
        });
        if (result.ok) {
            next = enablePluginInConfig(next, result.pluginId).config;
            next = recordPluginInstall(next, {
                pluginId: result.pluginId,
                source: "npm",
                spec: entry.install.officialSpec,
                installPath: result.targetDir,
                version: result.version,
            });
            return { cfg: next, installed: true };
        }
        await prompter.note(`安装官方插件失败: ${result.error}`, "插件安装");
        runtime.error?.(`官方插件安装失败: ${result.error}`);
        return { cfg: next, installed: false };
    }
    if (choice === "local" && localPath) {
        next = addPluginLoadPath(next, localPath);
        next = enablePluginInConfig(next, entry.id).config;
        // We must write the config file here so that re-loading works if it reads from disk,
        // although reloadOnboardingPluginRegistry takes cfg object.
        // However, loadClawdbotPlugins might rely on some disk state or the cfg object passed is enough.
        // But let's verify if `loadClawdbotPlugins` properly uses the passed config for paths.
        // Yes it does: loadClawdbotPlugins({ config: params.cfg ... })
        // BUT: resolveLocalPath returns an absolute path in 'candidate'.
        // addPluginLoadPath adds it to cfg.plugins.load.paths.
        return { cfg: next, installed: true };
    }
    const result = await installPluginFromNpmSpec({
        spec: entry.install.npmSpec,
        logger: {
            info: (msg) => runtime.log?.(msg),
            warn: (msg) => runtime.log?.(msg),
        },
    });
    if (result.ok) {
        next = enablePluginInConfig(next, result.pluginId).config;
        next = recordPluginInstall(next, {
            pluginId: result.pluginId,
            source: "npm",
            spec: entry.install.npmSpec,
            installPath: result.targetDir,
            version: result.version,
        });
        return { cfg: next, installed: true };
    }
    await prompter.note(`安装 ${entry.install.npmSpec} 失败: ${result.error}`, "插件安装");
    if (localPath) {
        const fallback = await prompter.confirm({
            message: `改用本地插件路径？(${localPath})`,
            initialValue: true,
        });
        if (fallback) {
            next = addPluginLoadPath(next, localPath);
            next = enablePluginInConfig(next, entry.id).config;
            return { cfg: next, installed: true };
        }
    }
    runtime.error?.(`插件安装失败: ${result.error}`);
    return { cfg: next, installed: false };
}
export function reloadOnboardingPluginRegistry(params) {
    const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg));
    const log = createSubsystemLogger("plugins");
    loadClawdbotPlugins({
        config: params.cfg,
        workspaceDir,
        cache: false,
        logger: {
            info: (msg) => log.info(msg),
            warn: (msg) => log.warn(msg),
            error: (msg) => log.error(msg),
            debug: (msg) => log.debug(msg),
        },
    });
}
