"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.installSkill = installSkill;
exports.locateBundledSkill = locateBundledSkill;
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const cliUtil_1 = require("@mablhq-internal/runtime/core/cliUtil");
const chromeCompatibility_1 = require("@mablhq-internal/runtime/util/chromeCompatibility");
const constants_1 = require("../../constants");
const debugConstants_1 = require("@mablhq-internal/runtime/debug/debugConstants");
const agentInstallRegistry_1 = require("../../../util/agentInstallRegistry");
const shared_1 = require("../shared");
const SKILL_NAMES = ['mabl-debug', 'mabl-plan-test'];
const SKILL_FILENAME = 'SKILL.md';
const REFERENCES_DIRNAME = 'references';
const COMMAND_REFERENCE_FILENAME = 'command-reference.md';
const SNIPPET_FILENAME = 'agents-md-snippet.md';
const SNIPPET_MARKER = '## Testing: mabl';
const INSTRUCTION_FILENAME = {
    Claude: 'CLAUDE.md',
    Copilot: 'copilot-instructions.md',
    Cursor: '.cursorrules',
    AgentsMd: 'AGENTS.md',
};
var McpRootKey;
(function (McpRootKey) {
    McpRootKey["Standard"] = "mcpServers";
    McpRootKey["VsCode"] = "servers";
})(McpRootKey || (McpRootKey = {}));
var McpEntryStatus;
(function (McpEntryStatus) {
    McpEntryStatus["Added"] = "added";
    McpEntryStatus["AlreadyPresent"] = "already-present";
    McpEntryStatus["Replaced"] = "replaced";
})(McpEntryStatus || (McpEntryStatus = {}));
var SnippetStatus;
(function (SnippetStatus) {
    SnippetStatus["AlreadyPresent"] = "already-present";
    SnippetStatus["Appended"] = "appended";
    SnippetStatus["Created"] = "created";
})(SnippetStatus || (SnippetStatus = {}));
const CHROME_FOR_MABL_ENTRY = {
    name: debugConstants_1.CHROME_FOR_MABL_MCP_ENTRY_NAME,
    config: (0, debugConstants_1.buildChromeForMablMcpConfig)(),
};
const MABL_HOSTED_ENTRY = {
    name: 'mabl',
    config: {
        type: 'http',
        url: 'https://mcp.mabl.com/mcp',
    },
};
const MCP_ENTRIES = [
    CHROME_FOR_MABL_ENTRY,
    MABL_HOSTED_ENTRY,
];
exports.command = 'install <target>';
exports.describe =
    'Install the mabl-debug skill (plus chrome-for-mabl and mabl MCP entries on supported targets) into an AI tool';
exports.builder = (yargs) => {
    yargs
        .positional(constants_1.CommandArgTarget, {
        describe: `AI tool to install into (supported: ${shared_1.SUPPORTED_TARGETS.join(', ')})`,
        type: 'string',
        demandOption: true,
    })
        .option(constants_1.CommandArgScope, {
        describe: "Install scope: 'user' (in your home directory) or 'project' (in the current directory). 'agents-md' is project-only.",
        type: 'string',
        choices: shared_1.SUPPORTED_SCOPES,
        default: 'user',
    })
        .option(constants_1.CommandArgForce, {
        describe: 'Overwrite an existing skill file and replace any existing chrome-for-mabl / mabl MCP entries',
        type: 'boolean',
        default: false,
    })
        .option(constants_1.CommandArgSkipMcp, {
        describe: 'Install the skill only; do not touch the target MCP configuration',
        type: 'boolean',
        default: false,
    })
        .option(constants_1.CommandArgAppendSnippet, {
        describe: 'Also append the mabl agent debug snippet to the agent instruction file (CLAUDE.md, .cursorrules, AGENTS.md, …)',
        type: 'boolean',
        default: false,
    });
};
exports.handler = (0, cliUtil_1.failWrapper)(installSkill);
async function installSkill(parsed, options = {}) {
    const target = parsed[constants_1.CommandArgTarget];
    if (!isSupportedTarget(target)) {
        throw new Error(`Unsupported install target: '${target}'. Supported targets: ${shared_1.SUPPORTED_TARGETS.join(', ')}`);
    }
    const scope = parsed[constants_1.CommandArgScope];
    if (!isSupportedScope(scope)) {
        throw new Error(`Unsupported scope: '${scope}'. Supported scopes: ${shared_1.SUPPORTED_SCOPES.join(', ')}`);
    }
    if (target === shared_1.SupportedTarget.AgentsMd && scope === shared_1.SupportedScope.User) {
        throw new Error(`The '${shared_1.SupportedTarget.AgentsMd}' target only supports --scope ${shared_1.SupportedScope.Project} (the ${INSTRUCTION_FILENAME.AgentsMd} format is project-relative).`);
    }
    if (!options.quiet) {
        await (0, chromeCompatibility_1.checkChromeCompatibility)();
    }
    const skills = [];
    for (const skillName of SKILL_NAMES) {
        const skillPaths = resolveInstallPaths(target, scope, skillName);
        skills.push(installSkillBundle(skillPaths.skillDir, skillName, parsed[constants_1.CommandArgForce]));
    }
    const paths = resolveInstallPaths(target, scope, SKILL_NAMES[0]);
    const mcp = parsed[constants_1.CommandArgSkipMcp] || !paths.mcpConfigPath || !paths.mcpRootKey
        ? undefined
        : installMcpEntries(paths.mcpConfigPath, paths.mcpRootKey, MCP_ENTRIES, parsed[constants_1.CommandArgForce]);
    const snippet = parsed[constants_1.CommandArgAppendSnippet] && paths.snippetTargetPath
        ? appendInstructionSnippet(paths.snippetTargetPath)
        : undefined;
    const report = { target, scope, skills, mcp, snippet };
    process.stdout.write(renderInstallReport(report));
    recordInstallReport(report);
    return report;
}
function recordInstallReport(report) {
    try {
        const projectPath = report.scope === shared_1.SupportedScope.Project ? process.cwd() : undefined;
        const artifacts = report.skills.map((skill) => ({
            type: agentInstallRegistry_1.ArtifactType.Skill,
            path: skill.destination,
        }));
        if (report.mcp) {
            for (const entry of report.mcp) {
                artifacts.push({
                    type: agentInstallRegistry_1.ArtifactType.McpEntry,
                    path: entry.configPath,
                    mcpRootKey: entry.rootKey,
                    entryName: entry.entryName,
                });
            }
        }
        if (report.snippet) {
            artifacts.push({
                type: agentInstallRegistry_1.ArtifactType.Snippet,
                path: report.snippet.targetPath,
            });
        }
        const entry = {
            id: (0, agentInstallRegistry_1.buildEntryId)(report.target, report.scope, projectPath),
            target: report.target,
            scope: report.scope,
            projectPath,
            cliVersion: (0, agentInstallRegistry_1.readCliVersion)() ?? 'unknown',
            installedAt: new Date().toISOString(),
            artifacts,
        };
        (0, agentInstallRegistry_1.recordInstallation)(entry);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        process.stderr.write(`  (registry note: failed to record installation — ${message})\n`);
    }
}
function renderInstallReport(report) {
    const lines = [];
    lines.push('');
    lines.push(`mabl skills installed for ${report.target} (scope: ${report.scope})`);
    lines.push('');
    for (const skill of report.skills) {
        lines.push(`  Skill        ${(0, shared_1.homeRelative)(skill.destination)}${skill.alreadyExisted ? ' (overwritten)' : ''}`);
    }
    if (report.mcp === undefined) {
        lines.push('  MCP config   skipped');
    }
    else if (report.mcp.length === 0) {
        lines.push('  MCP config   no entries');
    }
    else {
        lines.push(`  MCP config   ${(0, shared_1.homeRelative)(report.mcp[0].configPath)}`);
        const widest = Math.max(...report.mcp.map((m) => m.entryName.length));
        for (const entry of report.mcp) {
            const padded = entry.entryName.padEnd(widest);
            lines.push(`                 ${padded}  ${entry.status}`);
        }
    }
    if (report.snippet === undefined) {
        lines.push('  Snippet      not appended (use --append-snippet to add a "Testing: mabl" section)');
    }
    else {
        lines.push(`  Snippet      ${(0, shared_1.homeRelative)(report.snippet.targetPath)} (${report.snippet.status})`);
    }
    lines.push('');
    return lines.join('\n');
}
function isSupportedTarget(value) {
    return shared_1.SUPPORTED_TARGETS.some((target) => target === value);
}
function isSupportedScope(value) {
    return shared_1.SUPPORTED_SCOPES.some((scope) => scope === value);
}
function resolveInstallPaths(target, scope, skillName = SKILL_NAMES[0]) {
    return scope === shared_1.SupportedScope.User
        ? userScopePaths(target, skillName)
        : projectScopePaths(target, skillName);
}
function userScopePaths(target, skillName) {
    const home = os.homedir();
    switch (target) {
        case shared_1.SupportedTarget.Claude:
            return {
                skillDir: path.join(home, '.claude', 'skills', skillName),
                mcpConfigPath: path.join(home, '.claude.json'),
                mcpRootKey: McpRootKey.Standard,
                snippetTargetPath: path.join(home, '.claude', INSTRUCTION_FILENAME.Claude),
            };
        case shared_1.SupportedTarget.VsCode:
        case shared_1.SupportedTarget.Copilot:
            return {
                skillDir: path.join(home, '.copilot', 'skills', skillName),
                mcpConfigPath: vscodeUserMcpConfigPath(home),
                mcpRootKey: McpRootKey.VsCode,
                snippetTargetPath: path.join(home, '.github', INSTRUCTION_FILENAME.Copilot),
            };
        case shared_1.SupportedTarget.Cursor:
            return {
                skillDir: path.join(home, '.cursor', 'skills', skillName),
                mcpConfigPath: path.join(home, '.cursor', 'mcp.json'),
                mcpRootKey: McpRootKey.Standard,
                snippetTargetPath: path.join(home, INSTRUCTION_FILENAME.Cursor),
            };
        case shared_1.SupportedTarget.AgentsMd:
            throw new Error(`The '${shared_1.SupportedTarget.AgentsMd}' target only supports --scope ${shared_1.SupportedScope.Project}.`);
    }
}
function projectScopePaths(target, skillName) {
    const cwd = process.cwd();
    switch (target) {
        case shared_1.SupportedTarget.Claude:
            return {
                skillDir: path.join(cwd, '.claude', 'skills', skillName),
                mcpConfigPath: path.join(cwd, '.mcp.json'),
                mcpRootKey: McpRootKey.Standard,
                snippetTargetPath: path.join(cwd, '.claude', INSTRUCTION_FILENAME.Claude),
            };
        case shared_1.SupportedTarget.VsCode:
        case shared_1.SupportedTarget.Copilot:
            return {
                skillDir: path.join(cwd, '.github', 'skills', skillName),
                mcpConfigPath: path.join(cwd, '.vscode', 'mcp.json'),
                mcpRootKey: McpRootKey.VsCode,
                snippetTargetPath: path.join(cwd, '.github', INSTRUCTION_FILENAME.Copilot),
            };
        case shared_1.SupportedTarget.Cursor:
            return {
                skillDir: path.join(cwd, '.cursor', 'skills', skillName),
                mcpConfigPath: path.join(cwd, '.cursor', 'mcp.json'),
                mcpRootKey: McpRootKey.Standard,
                snippetTargetPath: path.join(cwd, INSTRUCTION_FILENAME.Cursor),
            };
        case shared_1.SupportedTarget.AgentsMd:
            return {
                skillDir: path.join(cwd, '.agents', 'skills', skillName),
                snippetTargetPath: path.join(cwd, INSTRUCTION_FILENAME.AgentsMd),
            };
    }
}
function vscodeUserMcpConfigPath(home) {
    if (process.platform === 'darwin') {
        return path.join(home, 'Library', 'Application Support', 'Code', 'User', 'mcp.json');
    }
    if (process.platform === 'win32') {
        const appData = process.env.APPDATA ?? path.join(home, 'AppData', 'Roaming');
        return path.join(appData, 'Code', 'User', 'mcp.json');
    }
    return path.join(home, '.config', 'Code', 'User', 'mcp.json');
}
function installSkillBundle(skillDir, skillName, force) {
    const skillSource = locateBundledResource(skillName, SKILL_FILENAME);
    const destination = path.join(skillDir, SKILL_FILENAME);
    const alreadyExisted = fs.existsSync(destination);
    if (alreadyExisted && !force) {
        throw new Error(`Skill already installed at ${destination}. Re-run with --force to overwrite.`);
    }
    fs.mkdirSync(skillDir, { recursive: true });
    fs.copyFileSync(skillSource, destination);
    const referencesSource = locateBundledResourceMaybe(skillName, path.join(REFERENCES_DIRNAME, COMMAND_REFERENCE_FILENAME));
    if (referencesSource) {
        const referencesDir = path.join(skillDir, REFERENCES_DIRNAME);
        fs.mkdirSync(referencesDir, { recursive: true });
        fs.copyFileSync(referencesSource, path.join(referencesDir, COMMAND_REFERENCE_FILENAME));
    }
    return { source: skillSource, destination, alreadyExisted };
}
function installMcpEntries(configPath, rootKey, entries, force) {
    fs.mkdirSync(path.dirname(configPath), { recursive: true });
    const config = readJsonFile(configPath) ?? {};
    if (typeof config !== 'object' || Array.isArray(config)) {
        throw new Error(`Cannot install MCP entries: ${configPath} is not a JSON object.`);
    }
    const configRecord = config;
    const existing = configRecord[rootKey];
    if (existing !== undefined &&
        (typeof existing !== 'object' || !existing || Array.isArray(existing))) {
        throw new Error(`Cannot install MCP entries: ${configPath}.${rootKey} exists but is not an object.`);
    }
    const servers = existing ?? {};
    const results = [];
    let mutated = false;
    for (const entry of entries) {
        const alreadyPresent = Object.prototype.hasOwnProperty.call(servers, entry.name);
        if (alreadyPresent && !force) {
            results.push({
                configPath,
                rootKey,
                entryName: entry.name,
                status: McpEntryStatus.AlreadyPresent,
            });
            continue;
        }
        servers[entry.name] = entry.config;
        mutated = true;
        results.push({
            configPath,
            rootKey,
            entryName: entry.name,
            status: alreadyPresent ? McpEntryStatus.Replaced : McpEntryStatus.Added,
        });
    }
    if (mutated) {
        configRecord[rootKey] = servers;
        writeJsonFileAtomic(configPath, configRecord);
    }
    return results;
}
function appendInstructionSnippet(targetPath) {
    const snippetSource = locateBundledResourceMaybe(SKILL_NAMES[0], SNIPPET_FILENAME);
    if (!snippetSource) {
        throw new Error(`Could not find bundled ${SNIPPET_FILENAME}. The CLI build may be missing resource files.`);
    }
    const snippet = fs.readFileSync(snippetSource, 'utf8');
    const existed = fs.existsSync(targetPath);
    const existing = existed ? fs.readFileSync(targetPath, 'utf8') : '';
    if (existing.includes(SNIPPET_MARKER)) {
        return { targetPath, status: SnippetStatus.AlreadyPresent };
    }
    const separator = existing.length === 0 ? '' : existing.endsWith('\n') ? '\n' : '\n\n';
    fs.mkdirSync(path.dirname(targetPath), { recursive: true });
    fs.writeFileSync(targetPath, existing + separator + snippet);
    return {
        targetPath,
        status: existed ? SnippetStatus.Appended : SnippetStatus.Created,
    };
}
function readJsonFile(filePath) {
    if (!fs.existsSync(filePath)) {
        return undefined;
    }
    const raw = fs.readFileSync(filePath, 'utf8');
    if (raw.trim().length === 0) {
        return undefined;
    }
    try {
        return JSON.parse(raw);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to parse ${filePath} as JSON: ${message}`);
    }
}
function writeJsonFileAtomic(filePath, data) {
    const tempPath = `${filePath}.tmp-${process.pid}`;
    fs.writeFileSync(tempPath, JSON.stringify(data, undefined, 2) + '\n', 'utf8');
    fs.renameSync(tempPath, filePath);
}
function locateBundledSkill() {
    return locateBundledResource(SKILL_NAMES[0], SKILL_FILENAME);
}
function locateBundledResource(skillName, relative) {
    const found = locateBundledResourceMaybe(skillName, relative);
    if (!found) {
        throw new Error(`Could not locate bundled skill resource '${skillName}/${relative}'. The CLI build may be missing resource files.`);
    }
    return found;
}
function locateBundledResourceMaybe(skillName, relative) {
    const subPath = path.join('resources', 'skills', skillName, relative);
    let dir = __dirname;
    for (let i = 0; i < 10; i++) {
        const candidate = path.join(dir, subPath);
        if (fs.existsSync(candidate)) {
            return candidate;
        }
        const parent = path.dirname(dir);
        if (parent === dir) {
            break;
        }
        dir = parent;
    }
    return undefined;
}
