import path from "node:path";
import { resolvePaths } from "./constants.js";
import { loadState, saveState } from "./state.js";
import { buildMcpFromInput, mergeMcp, applyMcpAppFlag, } from "./services/mcp-model.js";
import { syncMcpServerToApps } from "./services/mcp-service.js";
import { buildSkillFromInput, mergeSkill, applySkillAppFlag, skillSourceDir, } from "./services/skill-model.js";
import { syncSkillToApps } from "./services/skill-service.js";
import { copyDir, copyDirIfExists, copyFile, pathExists, removeIfExists, } from "./utils/fs.js";
import { SdkError } from "./errors.js";
/**
 * 导入时合并 App 标记
 * 注意：此函数移到 sdk.ts 中，因为它是导入逻辑的一部分
 */
function mergeAppFlagOnImport(server, appId) {
    const canonicalAppId = appId === "claude" ? "claudecode" : appId;
    return {
        ...server,
        apps: {
            ...server.apps,
            [canonicalAppId]: true,
            claude: false,
        },
    };
}
export class CcSwitchSdk {
    /**
     * 创建 CcSwitchSdk 实例
     *
     * 注意：adapters 必须通过 options.adapters 注入，
     * cc-switch-sdk 不再提供默认的 adapter 实现。
     *
     * @param options - SDK 配置选项
     * @param options.adapters.mcp - MCP 适配器映射（必需）
     * @param options.adapters.skill - Skill 适配器映射（必需）
     */
    constructor(options) {
        this.state = { mcpServers: {}, skills: {} };
        this.operationQueue = Promise.resolve();
        if (!options.adapters?.mcp || !options.adapters?.skill) {
            throw new SdkError("ADAPTERS_REQUIRED", "Adapters are required. Please provide mcp and skill adapters via options.adapters.", { hasMcp: !!options.adapters?.mcp, hasSkill: !!options.adapters?.skill });
        }
        const resolved = resolvePaths(options);
        this.paths = resolved.paths;
        this.skillSyncMethod = resolved.skillSyncMethod;
        this.mcpAdapters = options.adapters.mcp;
        this.skillAdapters = options.adapters.skill;
    }
    async executeExclusive(operation) {
        const previous = this.operationQueue;
        let release;
        this.operationQueue = new Promise((resolve) => {
            release = resolve;
        });
        await previous;
        try {
            return await operation();
        }
        finally {
            release();
        }
    }
    async load() {
        this.state = await loadState(this.paths.stateFile);
    }
    async save() {
        await saveState(this.paths.stateFile, this.state);
    }
    getAiConfigPath(appId) {
        if (appId === "opencode") {
            return this.paths.opencodeConfigFile;
        }
        if (appId === "claudecode") {
            return this.paths.claudecodeConfigFile;
        }
        if (appId === "codex") {
            return this.paths.codexConfigFile;
        }
        return undefined;
    }
    getAiBackupPath(appId) {
        const extension = appId === "codex" ? "toml" : "json";
        return path.join(this.paths.backupDir, `${appId}.config.backup.${extension}`);
    }
    async backupAiConfigIfNeeded(appId) {
        const configPath = this.getAiConfigPath(appId);
        if (!configPath) {
            return;
        }
        const backupPath = this.getAiBackupPath(appId);
        if (await pathExists(backupPath)) {
            return;
        }
        if (await pathExists(configPath)) {
            await copyFile(configPath, backupPath);
        }
    }
    async backupBeforeMcpSyncByApps(apps) {
        const targets = ["opencode", "claudecode", "codex"];
        for (const appId of targets) {
            if (apps[appId]) {
                await this.backupAiConfigIfNeeded(appId);
            }
        }
    }
    async backupBeforeMcpSync(server) {
        await this.backupBeforeMcpSyncByApps(server.apps);
    }
    async backupAiConfigs(appIds = ["opencode", "claudecode", "codex"]) {
        for (const appId of appIds) {
            await this.backupAiConfigIfNeeded(appId);
        }
    }
    async restoreAiConfig(appId) {
        const configPath = this.getAiConfigPath(appId);
        if (!configPath) {
            return false;
        }
        const backupPath = this.getAiBackupPath(appId);
        if (!(await pathExists(backupPath))) {
            return false;
        }
        await copyFile(backupPath, configPath);
        return true;
    }
    async restoreAiConfigs(appIds = ["opencode", "claudecode", "codex"]) {
        const result = {};
        for (const appId of appIds) {
            result[appId] = await this.restoreAiConfig(appId);
        }
        return result;
    }
    getAllMcpServers() {
        return Object.values(this.state.mcpServers);
    }
    getMcpServerById(id) {
        return this.state.mcpServers[id];
    }
    async createMcpServer(input) {
        return this.executeExclusive(async () => {
            const server = buildMcpFromInput(input);
            this.state.mcpServers[server.id] = server;
            await this.backupBeforeMcpSync(server);
            await syncMcpServerToApps(server, this.mcpAdapters);
            await this.save();
            return server;
        });
    }
    async updateMcpServer(id, patch) {
        return this.executeExclusive(async () => {
            const current = this.state.mcpServers[id];
            if (!current) {
                throw new SdkError("MCP_NOT_FOUND", `MCP server not found: ${id}`, { id });
            }
            const updated = mergeMcp(current, patch);
            this.state.mcpServers[id] = updated;
            await this.backupBeforeMcpSync(updated);
            await syncMcpServerToApps(updated, this.mcpAdapters);
            await this.save();
            return updated;
        });
    }
    async deleteMcpServer(id) {
        return this.executeExclusive(async () => {
            const server = this.state.mcpServers[id];
            if (!server) {
                return;
            }
            const disabled = {
                ...server,
                apps: {
                    claude: false,
                    claudecode: false,
                    codex: false,
                    gemini: false,
                    opencode: false,
                    openclaw: false,
                },
            };
            await syncMcpServerToApps(disabled, this.mcpAdapters);
            delete this.state.mcpServers[id];
            await this.save();
        });
    }
    async toggleMcpApp(serverId, appId, enabled) {
        return this.executeExclusive(async () => {
            const current = this.state.mcpServers[serverId];
            if (!current) {
                throw new SdkError("MCP_NOT_FOUND", `MCP server not found: ${serverId}`, {
                    serverId,
                });
            }
            const updated = applyMcpAppFlag(current, appId, enabled);
            this.state.mcpServers[serverId] = updated;
            await this.backupBeforeMcpSyncByApps({ [appId]: enabled });
            await syncMcpServerToApps(updated, this.mcpAdapters);
            await this.save();
            return updated;
        });
    }
    async importMcpFromApps() {
        return this.executeExclusive(async () => {
            let count = 0;
            const apps = ["claude", "claudecode", "opencode", "codex", "gemini", "openclaw"];
            for (const appId of apps) {
                const imported = await this.mcpAdapters[appId].importServers();
                for (const imp of imported) {
                    const existing = this.findMcpServerByName(imp.name);
                    if (existing) {
                        const merged = mergeAppFlagOnImport(existing, appId);
                        this.state.mcpServers[existing.id] = merged;
                        continue;
                    }
                    const withFlag = this.createMcpWithAppFlag(imp, appId);
                    this.state.mcpServers[withFlag.id] = withFlag;
                    count++;
                }
            }
            await this.save();
            return count;
        });
    }
    /**
     * 从单个应用导入 MCP 服务器
     */
    async importMcpFromApp(appId) {
        let count = 0;
        const imported = await this.mcpAdapters[appId].importServers();
        for (const imp of imported) {
            const isNew = await this.processImportedMcp(imp, appId);
            if (isNew) {
                count++;
            }
        }
        return count;
    }
    /**
     * 处理导入的 MCP 服务器（合并或创建）
     * @returns 是否为新创建的 MCP
     */
    async processImportedMcp(imp, appId) {
        const existing = this.findMcpServerByName(imp.name);
        if (existing) {
            const merged = mergeAppFlagOnImport(existing, appId);
            this.state.mcpServers[existing.id] = merged;
            await syncMcpServerToApps(merged, this.mcpAdapters);
            return false;
        }
        const withFlag = this.createMcpWithAppFlag(imp, appId);
        this.state.mcpServers[withFlag.id] = withFlag;
        await syncMcpServerToApps(withFlag, this.mcpAdapters);
        return true;
    }
    /**
     * 根据名称查找 MCP 服务器
     */
    findMcpServerByName(name) {
        return Object.values(this.state.mcpServers).find((s) => s.name === name);
    }
    /**
     * 创建带有应用标记的 MCP 服务器
     */
    createMcpWithAppFlag(imp, appId) {
        const canonicalAppId = appId === "claude" ? "claudecode" : appId;
        return {
            ...imp,
            apps: {
                claude: false,
                claudecode: false,
                codex: false,
                gemini: false,
                opencode: false,
                openclaw: false,
                [canonicalAppId]: true,
            },
        };
    }
    getAllSkills() {
        return Object.values(this.state.skills);
    }
    getSkillById(id) {
        return this.state.skills[id];
    }
    async installSkill(input) {
        return this.executeExclusive(async () => {
            const ssotDir = this.paths.ssotSkillsDir;
            const skill = buildSkillFromInput(input);
            const targetSource = skillSourceDir(ssotDir, skill);
            await copyDir(input.sourceDir, targetSource);
            this.state.skills[skill.id] = skill;
            await syncSkillToApps(skill, targetSource, this.skillSyncMethod, this.skillAdapters);
            await this.save();
            return skill;
        });
    }
    async updateSkill(id, patch) {
        return this.executeExclusive(async () => {
            const current = this.state.skills[id];
            if (!current) {
                throw new SdkError("SKILL_NOT_FOUND", `Skill not found: ${id}`, { id });
            }
            const updated = mergeSkill(current, patch);
            this.state.skills[id] = updated;
            const source = skillSourceDir(this.paths.ssotSkillsDir, updated);
            await syncSkillToApps(updated, source, this.skillSyncMethod, this.skillAdapters);
            await this.save();
            return updated;
        });
    }
    async uninstallSkill(id) {
        return this.executeExclusive(async () => {
            const skill = this.state.skills[id];
            if (!skill) {
                return;
            }
            const disabled = {
                ...skill,
                apps: {
                    claude: false,
                    claudecode: false,
                    codex: false,
                    gemini: false,
                    opencode: false,
                    openclaw: false,
                },
            };
            const source = skillSourceDir(this.paths.ssotSkillsDir, skill);
            await syncSkillToApps(disabled, source, this.skillSyncMethod, this.skillAdapters);
            delete this.state.skills[id];
            const sourceDir = skillSourceDir(this.paths.ssotSkillsDir, skill);
            await removeIfExists(sourceDir);
            await this.save();
        });
    }
    async toggleSkillApp(skillId, appId, enabled) {
        return this.executeExclusive(async () => {
            const current = this.state.skills[skillId];
            if (!current) {
                throw new SdkError("SKILL_NOT_FOUND", `Skill not found: ${skillId}`, {
                    skillId,
                });
            }
            const updated = applySkillAppFlag(current, appId, enabled);
            this.state.skills[skillId] = updated;
            const source = skillSourceDir(this.paths.ssotSkillsDir, updated);
            await syncSkillToApps(updated, source, this.skillSyncMethod, this.skillAdapters);
            await this.save();
            return updated;
        });
    }
    async importSkillsFromApps() {
        return this.executeExclusive(async () => {
            let count = 0;
            const apps = ["claude", "claudecode", "opencode", "codex", "gemini", "openclaw"];
            for (const appId of apps) {
                const imported = await this.skillAdapters[appId].importSkills();
                for (const imp of imported) {
                    const existing = this.findSkillByName(imp.name);
                    if (existing) {
                        if (!(await pathExists(imp.directory))) {
                            continue;
                        }
                        const canonicalAppId = appId === "claude" ? "claudecode" : appId;
                        const merged = {
                            ...existing,
                            apps: { ...existing.apps, [canonicalAppId]: true, claude: false },
                        };
                        this.state.skills[existing.id] = merged;
                        continue;
                    }
                    const withFlag = this.createSkillWithAppFlag(imp, appId);
                    const source = skillSourceDir(this.paths.ssotSkillsDir, withFlag);
                    const copied = await copyDirIfExists(imp.directory, source);
                    if (!copied) {
                        continue;
                    }
                    this.state.skills[withFlag.id] = withFlag;
                    count++;
                }
            }
            await this.save();
            return count;
        });
    }
    /**
     * 从单个应用导入 Skills
     */
    async importSkillsFromApp(appId) {
        let count = 0;
        const imported = await this.skillAdapters[appId].importSkills();
        for (const imp of imported) {
            const isNew = await this.processImportedSkill(imp, appId);
            if (isNew) {
                count++;
            }
        }
        return count;
    }
    /**
     * 处理导入的 Skill（合并或创建）
     * @returns 是否为新创建的 Skill
     */
    async processImportedSkill(imp, appId) {
        const existing = this.findSkillByName(imp.name);
        if (existing) {
            await this.mergeExistingSkill(existing, appId);
            return false;
        }
        await this.createNewImportedSkill(imp, appId);
        return true;
    }
    /**
     * 根据名称查找 Skill
     */
    findSkillByName(name) {
        return Object.values(this.state.skills).find((s) => s.name === name);
    }
    /**
     * 合并已存在的 Skill
     */
    async mergeExistingSkill(existing, appId) {
        const canonicalAppId = appId === "claude" ? "claudecode" : appId;
        const merged = {
            ...existing,
            apps: { ...existing.apps, [canonicalAppId]: true, claude: false },
        };
        this.state.skills[existing.id] = merged;
        const source = skillSourceDir(this.paths.ssotSkillsDir, merged);
        await syncSkillToApps(merged, source, this.skillSyncMethod, this.skillAdapters);
    }
    /**
     * 创建新导入的 Skill
     */
    async createNewImportedSkill(imp, appId) {
        const withFlag = this.createSkillWithAppFlag(imp, appId);
        this.state.skills[withFlag.id] = withFlag;
        const source = skillSourceDir(this.paths.ssotSkillsDir, withFlag);
        await copyDir(imp.directory, source);
        await syncSkillToApps(withFlag, source, this.skillSyncMethod, this.skillAdapters);
    }
    /**
     * 创建带有应用标记的 Skill
     */
    createSkillWithAppFlag(imp, appId) {
        const canonicalAppId = appId === "claude" ? "claudecode" : appId;
        const now = Date.now();
        return {
            id: imp.id,
            name: imp.name,
            directory: imp.directory,
            description: imp.description,
            apps: {
                claude: false,
                claudecode: false,
                codex: false,
                gemini: false,
                opencode: false,
                openclaw: false,
                [canonicalAppId]: true,
            },
            installedAt: now,
        };
    }
    getPaths() {
        return this.paths;
    }
}
