/**
 * InstanceService - Agent 实例初始化服务
 *
 * 负责 Agent 实例的生命周期管理：
 * - 实例根目录、workspace 路径管理
 * - 实例状态持久化
 * - Skill/MCP 包安装流程
 *
 * 注意：此服务属于 Agent 运行时层，不属于 cc-switch-sdk 配置管理层
 */
import path from "node:path";
import { promises as fs } from "node:fs";
/**
 * 生成唯一 ID
 */
function generateId(prefix = "inst") {
    const timestamp = Date.now().toString(36);
    const random = Math.random().toString(36).slice(2, 8);
    return `${prefix}_${timestamp}_${random}`;
}
/**
 * 检查路径是否存在
 */
async function pathExists(target) {
    try {
        await fs.access(target);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * 递归复制目录
 */
async function copyDir(source, target) {
    await fs.mkdir(target, { recursive: true });
    const entries = await fs.readdir(source, { withFileTypes: true });
    for (const entry of entries) {
        const srcPath = path.join(source, entry.name);
        const tgtPath = path.join(target, entry.name);
        if (entry.isDirectory()) {
            await copyDir(srcPath, tgtPath);
        }
        else {
            await fs.copyFile(srcPath, tgtPath);
        }
    }
}
/**
 * 删除路径（如果存在）
 */
async function removeIfExists(target) {
    try {
        await fs.rm(target, { recursive: true, force: true });
    }
    catch {
        // ignore
    }
}
export class InstanceService {
    constructor(sdk, instanceRoot, workspacePath) {
        this.sdk = sdk;
        this.instanceRoot = instanceRoot;
        this.workspacePath = workspacePath;
    }
    getPaths() {
        return {
            instanceRoot: this.instanceRoot,
            stateFile: path.join(this.instanceRoot, "instance-state.json"),
            skillsDir: path.join(this.instanceRoot, "skills"),
            mcpDir: path.join(this.instanceRoot, "mcp"),
        };
    }
    async loadState() {
        const paths = this.getPaths();
        try {
            const raw = await fs.readFile(paths.stateFile, "utf-8");
            const parsed = JSON.parse(raw);
            return parsed;
        }
        catch {
            return {
                initialized: false,
                skills: [],
                mcpServers: [],
                enabledApps: [],
                skillEnabled: true,
                mcpEnabled: true,
                updatedAt: null,
            };
        }
    }
    async saveState(state) {
        const paths = this.getPaths();
        state.updatedAt = new Date().toISOString();
        await fs.mkdir(path.dirname(paths.stateFile), { recursive: true });
        await fs.writeFile(paths.stateFile, JSON.stringify(state, null, 2), "utf-8");
        return state;
    }
    async ensureInstanceRoot() {
        await fs.mkdir(this.instanceRoot, { recursive: true });
    }
    toAppFlags(appIds) {
        return {
            acode: appIds.includes("acode"),
            claude: appIds.includes("claude"),
            claudecode: appIds.includes("claudecode"),
            codex: appIds.includes("codex"),
            gemini: appIds.includes("gemini"),
            opencode: appIds.includes("opencode"),
            openclaw: appIds.includes("openclaw"),
        };
    }
    async initialize(options = {}) {
        await this.ensureInstanceRoot();
        const paths = this.getPaths();
        const enabledApps = options.enabledApps ?? ["claudecode", "opencode"];
        const appFlags = this.toAppFlags(enabledApps);
        const result = {
            ok: true,
            instanceId: generateId(),
            workspacePath: this.workspacePath,
            instanceRoot: this.instanceRoot,
            enabledApps,
            skill: {
                enabled: options.skillEnabled ?? true,
                packagePresent: false,
            },
            mcp: {
                enabled: options.mcpEnabled ?? true,
            },
        };
        const state = await this.loadState();
        state.initialized = true;
        state.enabledApps = enabledApps;
        state.skillEnabled = result.skill.enabled;
        state.mcpEnabled = result.mcp.enabled;
        if (result.skill.enabled) {
            await fs.mkdir(paths.skillsDir, { recursive: true });
            const skillCandidates = Array.isArray(options.skillPackages) && options.skillPackages.length > 0
                ? options.skillPackages
                : options.skillPackage
                    ? [options.skillPackage]
                    : [];
            const installedSkills = [];
            for (const candidate of skillCandidates) {
                const installed = await this.installSkillPackage(candidate, paths.skillsDir, appFlags);
                if (installed) {
                    installedSkills.push(installed);
                }
            }
            if (installedSkills.length > 0) {
                result.skill.packagePresent = true;
                result.skill.installedCount = installedSkills.length;
                result.skill.installed = installedSkills;
                state.skills = installedSkills;
            }
        }
        if (result.mcp.enabled) {
            const mcpServers = Array.isArray(options.mcpServers)
                ? options.mcpServers
                : [];
            const installedMcps = [];
            for (const input of mcpServers) {
                const server = await this.upsertMcpServer(input, appFlags);
                if (server) {
                    installedMcps.push(server);
                }
            }
            if (installedMcps.length > 0) {
                result.mcp.installedCount = installedMcps.length;
                result.mcp.installed = installedMcps;
                state.mcpServers = installedMcps;
            }
        }
        await this.saveState(state);
        return result;
    }
    async installSkillPackage(pkg, targetDir, apps) {
        const sourceDir = pkg.localPath;
        if (!sourceDir || !(await pathExists(sourceDir))) {
            return null;
        }
        const skillName = pkg.skillName || path.basename(sourceDir).replace(/\.skill$/, "");
        const skillId = generateId("skill");
        const targetSkillDir = path.join(targetDir, skillName);
        await copyDir(sourceDir, targetSkillDir);
        const installed = await this.sdk.installSkill({
            sourceDir: targetSkillDir,
            name: skillName,
            apps,
        });
        return installed;
    }
    async upsertMcpServer(input, apps) {
        const name = String(input.name || "").trim();
        if (!name) {
            return null;
        }
        const existingMcps = this.sdk.getAllMcpServers();
        const existing = existingMcps.find((s) => s.name === name);
        const serverSpec = {
            type: input.transport || "stdio",
            command: input.command,
            url: input.url,
            args: input.args,
            env: input.env,
        };
        if (existing) {
            return this.sdk.updateMcpServer(existing.id, {
                name,
                server: serverSpec,
                apps,
            });
        }
        return this.sdk.createMcpServer({
            name,
            server: serverSpec,
            apps,
        });
    }
    async getState() {
        return this.loadState();
    }
    async updateState(partial) {
        const state = await this.loadState();
        Object.assign(state, partial);
        return this.saveState(state);
    }
    async reset() {
        const paths = this.getPaths();
        await removeIfExists(paths.stateFile);
        await removeIfExists(paths.skillsDir);
        await removeIfExists(paths.mcpDir);
    }
}
