// packages/plugin-awf/src/commands/mcp.ts
// AWF MCP Command - Manage MCP (Model Context Protocol) server registration
// Refactored: registry → mcp-registry.ts, writers → mcp-writers.ts
import * as fs from "node:fs";
import * as path from "node:path";
import { getIDEByValue } from "../constants/ides.js";
import pc from "picocolors";
import * as prompts from "@clack/prompts";
import { MCP_CONFIG_REGISTRY, resolveMCPConfigPath, buildMCPServerEntry, isProjectLocalConfig, detectStaleConfigs, } from "./mcp-registry.js";
import { writeJSONConfig, writeYAMLConfig, writeTOMLConfig } from "./mcp-writers.js";
import { downloadHSA, getInstalledHSAVersion, checkHSAUpdate, getHSAInstallDir, getHSABundlePath } from "./install-hsa.js";
/** Escape special regex chars in a string for safe use in RegExp constructor */
function escapeRegexStr(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// ═══════════════════════════════════════════════════════════════════════════
// FLAGS
// ═══════════════════════════════════════════════════════════════════════════
export const mcpFlags = {
    ide: {
        type: "string",
        description: "Target IDE (cursor, claude, windsurf, gemini, cline, continue, roo, zed, all)",
        alias: "i",
        choices: [...Object.keys(MCP_CONFIG_REGISTRY), "all"],
    },
    force: {
        type: "boolean",
        description: "Overwrite existing MCP configuration",
        alias: "f",
        default: false,
    },
    verbose: {
        type: "boolean",
        description: "Show verbose output",
        alias: "V",
        default: false,
    },
};
// ═══════════════════════════════════════════════════════════════════════════
// MAIN COMMAND
// ═══════════════════════════════════════════════════════════════════════════
export async function mcpCommand(args, flags) {
    const [subcommand] = args;
    const verbose = flags.verbose;
    switch (subcommand) {
        case "install":
            await installMCPCommand(args.slice(1), flags);
            break;
        case "update":
            await updateMCPCommand(flags);
            break;
        case "uninstall":
            await uninstallMCPCommand(args.slice(1), flags);
            break;
        case "status":
            await statusMCPCommand(verbose);
            break;
        case "list":
            showSupportedIDEs();
            break;
        case undefined:
        case "help":
            showMCPHelp();
            break;
        default:
            console.log(pc.yellow(`Unknown subcommand: ${subcommand}`));
            showMCPHelp();
    }
}
// ═══════════════════════════════════════════════════════════════════════════
// INSTALL MCP COMMAND
// ═══════════════════════════════════════════════════════════════════════════
async function installMCPCommand(args, flags) {
    const spinner = prompts.spinner();
    const force = flags.force;
    const ideArg = (args[0] || flags.ide);
    const cwd = process.cwd();
    // Determine target IDEs
    let targetIDEs;
    if (ideArg === "all") {
        targetIDEs = Object.keys(MCP_CONFIG_REGISTRY);
    }
    else if (ideArg) {
        // Support comma-separated IDE values (e.g., "cursor,claude")
        const ideValues = ideArg.split(",").map(v => v.trim());
        targetIDEs = ideValues.filter(v => {
            if (!MCP_CONFIG_REGISTRY[v]) {
                console.log(pc.yellow(`  \u26a0 Unknown or unsupported IDE: ${v} (skipped)`));
                return false;
            }
            return true;
        });
    }
    else {
        // Interactive selection
        const selection = await prompts.multiselect({
            message: "Select IDEs to configure MCP:",
            options: Object.keys(MCP_CONFIG_REGISTRY).map((ide) => ({
                value: ide,
                label: getIDEDisplayName(ide),
            })),
        });
        if (prompts.isCancel(selection)) {
            prompts.cancel("Operation cancelled");
            return;
        }
        targetIDEs = selection;
    }
    // Step 0: Ensure HSA bundle exists in dedicated dir (~/.nockdev/hsa/)
    // FIX: Check bundle file (not just manifest) to avoid npx cache fallback
    const bundlePath = getHSABundlePath();
    if (!bundlePath) {
        spinner.start("Downloading HSA MCP server...");
        const dlResult = await downloadHSA();
        if (dlResult.success) {
            spinner.stop(pc.green(`✔ HSA v${dlResult.version} installed`));
        }
        else {
            spinner.stop(pc.yellow(`⚠ HSA download failed: ${dlResult.error} (falling back to npx)`));
        }
    }
    else {
        const existingVersion = getInstalledHSAVersion();
        console.log(pc.dim(`  HSA v${existingVersion} installed at ${getHSAInstallDir()}`));
    }
    spinner.start("Installing MCP configurations...");
    let success = 0;
    let failed = 0;
    for (const ide of targetIDEs) {
        // Global IDEs: use ${workspaceFolder} (IDE resolves per-window)
        // Project-local IDEs: use absolute CWD (config lives in project)
        const projectPath = isProjectLocalConfig(ide) ? cwd : undefined;
        const result = await installMCPConfig(ide, projectPath, force);
        if (result.success) {
            success++;
            if (flags.verbose) {
                console.log(pc.green(`  ✓ ${ide}: ${result.configPath}`));
            }
        }
        else {
            failed++;
            if (flags.verbose) {
                console.log(pc.yellow(`  ⚠ ${ide}: ${result.error}`));
            }
        }
    }
    spinner.stop(pc.green(`MCP configured: ${success} success, ${failed} skipped`));
    // Show next steps
    console.log(`
${pc.bold("Next steps:")}
  1. Restart your IDE(s) to load MCP configuration
  2. Verify with: ${pc.cyan("nock awf mcp status")}
`);
}
// ═══════════════════════════════════════════════════════════════════════════
// INSTALL MCP CONFIG - Core function ported from legacy CLI
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Install MCP configuration to IDE config files
 * Ported from domyh-awf-cli/src/commands/install.ts (P0-001, P0-002 Fix)
 */
export async function installMCPConfig(ide, projectPath, force = false) {
    const mcpConfig = MCP_CONFIG_REGISTRY[ide];
    if (!mcpConfig) {
        return { success: false, error: "MCP not supported for this IDE" };
    }
    // Resolve config path (cross-platform)
    const configPath = resolveMCPConfigPath(mcpConfig.configPath);
    try {
        // Ensure config directory exists
        const configDir = path.dirname(configPath);
        if (!fs.existsSync(configDir)) {
            fs.mkdirSync(configDir, { recursive: true });
        }
        // Build MCP server config entry
        // With dedicated install: { command: "node", args: ["~/.nockdev/hsa/hsa-cli.bundle.js"] }
        // Fallback (npx cache/global/npx): handled by getMCPServerCommand()
        // Global IDEs: projectPath is undefined → runtime uses MCP roots
        // Project-local IDEs: projectPath is absolute path to project
        const isZed = ide === "zed";
        const isOpenCode = ide === "opencode" || ide === "opencode-global";
        const baseEntry = buildMCPServerEntry(projectPath ? projectPath.replace(/\\/g, "/") : undefined, ide);
        let mcpServerEntry;
        if (isZed) {
            mcpServerEntry = {
                command: {
                    path: baseEntry.command,
                    args: baseEntry.args,
                    env: baseEntry.env,
                },
                settings: {},
            };
        }
        else if (isOpenCode) {
            // OpenCode requires: { type: "local", command: ["node","path"], environment: {...} }
            // Standard format: { command: "node", args: ["path"], env: {...} }
            mcpServerEntry = {
                type: "local",
                command: [baseEntry.command, ...baseEntry.args],
                environment: baseEntry.env,
            };
        }
        else {
            mcpServerEntry = baseEntry;
        }
        // Get the key name for MCP servers
        const mcpKeyName = mcpConfig.mcpKey || "mcpServers";
        // Handle TOML format (Codex CLI)
        if (mcpConfig.format === "toml") {
            return await writeTOMLConfig(configPath, mcpConfig.mcpKey || "mcp_servers", mcpConfig.serverName, mcpServerEntry, force);
        }
        // Handle YAML format (Continue.dev)
        if (mcpConfig.format === "yaml") {
            return await writeYAMLConfig(configPath, mcpKeyName, mcpConfig.serverName, mcpServerEntry, force);
        }
        // Handle JSON format (default)
        return await writeJSONConfig(configPath, mcpKeyName, mcpConfig.serverName, mcpServerEntry, force);
    }
    catch (error) {
        return {
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
// ═══════════════════════════════════════════════════════════════════════════
// UPDATE COMMAND
// ═══════════════════════════════════════════════════════════════════════════
async function updateMCPCommand(flags) {
    const spinner = prompts.spinner();
    const verbose = flags.verbose;
    const checkOnly = flags.check;
    // Check current vs latest
    spinner.start("Checking for HSA updates...");
    const update = await checkHSAUpdate();
    if (update.error) {
        spinner.stop(pc.yellow(`⚠ Failed to check: ${update.error}`));
        return;
    }
    if (!update.needsUpdate) {
        spinner.stop(pc.green(`✔ HSA v${update.installed} is up to date`));
        return;
    }
    spinner.stop(pc.cyan(`Update available: ${update.installed || "none"} → ${update.latest}`));
    if (checkOnly) {
        console.log(pc.dim("  Run 'nock awf mcp update' to install"));
        return;
    }
    // Download latest
    spinner.start(`Downloading HSA v${update.latest}...`);
    const result = await downloadHSA(update.latest);
    if (!result.success) {
        spinner.stop(pc.red(`✖ Download failed: ${result.error}`));
        return;
    }
    spinner.stop(pc.green(`✔ HSA v${result.version} installed`));
    // Regenerate all existing MCP configs to use new path
    // Also detects + fixes stale npx cache paths from older installations
    console.log(pc.dim("  Regenerating MCP configs..."));
    let updated = 0;
    for (const [ide, config] of Object.entries(MCP_CONFIG_REGISTRY)) {
        const configPath = resolveMCPConfigPath(config.configPath);
        if (fs.existsSync(configPath)) {
            // INST-04 FIX: Preserve existing projectPath for project-local configs
            let existingProjectPath;
            if (isProjectLocalConfig(ide)) {
                try {
                    const content = JSON.parse(fs.readFileSync(configPath, "utf-8"));
                    const mcpKey = config.mcpKey || "mcpServers";
                    existingProjectPath = content?.[mcpKey]?.[config.serverName]?.env?.HSA_PROJECT_PATH;
                }
                catch { /* use undefined — will regenerate without path */ }
            }
            const mcpResult = await installMCPConfig(ide, existingProjectPath, true);
            if (mcpResult.success)
                updated++;
        }
    }
    // Also fix configs that weren't regenerated but have stale npx cache paths
    const staleIDEs = detectStaleConfigs();
    if (staleIDEs.length > 0) {
        console.log(pc.cyan(`  Fixing ${staleIDEs.length} config(s) with stale npx cache paths...`));
        for (const ide of staleIDEs) {
            const projectPath = isProjectLocalConfig(ide) ? process.cwd() : undefined;
            const mcpResult = await installMCPConfig(ide, projectPath, true);
            if (mcpResult.success)
                updated++;
        }
    }
    console.log(pc.green(`  ✔ Updated ${updated} IDE config(s)`));
    console.log(`\n${pc.bold("Next step:")} Restart your IDE(s) to use HSA v${result.version}\n`);
}
// ═══════════════════════════════════════════════════════════════════════════
// OTHER COMMANDS
// ═══════════════════════════════════════════════════════════════════════════
async function uninstallMCPCommand(args, flags) {
    const ideArg = (args[0] || flags.ide);
    if (!ideArg || !MCP_CONFIG_REGISTRY[ideArg]) {
        console.log(pc.yellow("Specify IDE: nock awf mcp uninstall <ide>"));
        showSupportedIDEs();
        return;
    }
    const mcpConfig = MCP_CONFIG_REGISTRY[ideArg];
    const configPath = resolveMCPConfigPath(mcpConfig.configPath);
    if (!fs.existsSync(configPath)) {
        console.log(pc.yellow(`No MCP config found at: ${configPath}`));
        return;
    }
    try {
        const content = fs.readFileSync(configPath, "utf-8");
        const mcpKeyName = mcpConfig.mcpKey || "mcpServers";
        // Parse based on format
        if (mcpConfig.format === "toml") {
            // TOML uninstall: remove server section using regex
            const serverKey = mcpConfig.mcpKey || "mcp_servers";
            const sectionPattern = new RegExp(`\\[${escapeRegexStr(serverKey)}\\.${escapeRegexStr(mcpConfig.serverName)}[\\s\\S]*?(?=\\n\\[|$)`, "g");
            const cleaned = content.replace(sectionPattern, "").trim();
            if (cleaned !== content.trim()) {
                fs.writeFileSync(configPath, cleaned + "\n");
                console.log(pc.green(`\u2713 Removed ${mcpConfig.serverName} from ${ideArg} (TOML)`));
            }
            else {
                console.log(pc.yellow(`${mcpConfig.serverName} not found in ${ideArg} config`));
            }
            return;
        }
        let config;
        if (mcpConfig.format === "yaml") {
            config = (await import("yaml")).parse(content) || {};
        }
        else {
            config = JSON.parse(content);
        }
        const mcpData = config[mcpKeyName];
        if (Array.isArray(mcpData)) {
            // Continue.dev YAML: array format [{name: "...", ...}]
            const idx = mcpData.findIndex((s) => s.name === mcpConfig.serverName);
            if (idx !== -1) {
                mcpData.splice(idx, 1);
                const YAML = await import("yaml");
                fs.writeFileSync(configPath, YAML.stringify(config));
                console.log(pc.green(`✓ Removed ${mcpConfig.serverName} from ${ideArg}`));
            }
            else {
                console.log(pc.yellow(`${mcpConfig.serverName} not found in ${ideArg} config`));
            }
        }
        else if (mcpData?.[mcpConfig.serverName]) {
            delete mcpData[mcpConfig.serverName];
            fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
            console.log(pc.green(`✓ Removed ${mcpConfig.serverName} from ${ideArg}`));
        }
        else {
            console.log(pc.yellow(`${mcpConfig.serverName} not found in ${ideArg} config`));
        }
    }
    catch (error) {
        console.log(pc.red(`Failed to uninstall: ${error.message}`));
    }
}
async function statusMCPCommand(verbose) {
    console.log(pc.bold("\n🔌 MCP Status"));
    console.log(pc.gray("─".repeat(50)));
    for (const [ide, config] of Object.entries(MCP_CONFIG_REGISTRY)) {
        const configPath = resolveMCPConfigPath(config.configPath);
        const exists = fs.existsSync(configPath);
        let configured = false;
        if (exists) {
            try {
                const content = fs.readFileSync(configPath, "utf-8");
                if (config.format === "toml") {
                    // TOML: regex-based section detection
                    const serverKey = config.mcpKey || "mcp_servers";
                    const sectionRegex = new RegExp(`\\[${escapeRegexStr(serverKey)}\\.${escapeRegexStr(config.serverName)}\\]`);
                    configured = sectionRegex.test(content);
                }
                else {
                    const parsed = config.format === "yaml"
                        ? (await import("yaml")).parse(content)
                        : JSON.parse(content);
                    const mcpKeyName = config.mcpKey || "mcpServers";
                    const mcpData = parsed?.[mcpKeyName];
                    if (Array.isArray(mcpData)) {
                        // Continue.dev YAML: array format [{name: "...", ...}]
                        configured = mcpData.some((s) => s.name === config.serverName);
                    }
                    else {
                        configured = !!mcpData?.[config.serverName];
                    }
                }
            }
            catch {
                // Invalid config
            }
        }
        const status = configured
            ? pc.green("✓ Configured")
            : exists
                ? pc.yellow("○ Config exists, not configured")
                : pc.dim("○ Not installed");
        console.log(`  ${getIDEDisplayName(ide).padEnd(20)} ${status}`);
        if (verbose && exists) {
            console.log(pc.dim(`    → ${configPath}`));
        }
    }
    console.log();
}
function showSupportedIDEs() {
    console.log(pc.bold("\n📋 Supported IDEs for MCP:"));
    for (const [ide, config] of Object.entries(MCP_CONFIG_REGISTRY)) {
        console.log(`  ${pc.cyan(ide.padEnd(12))} → ${config.configPath}`);
    }
    console.log();
}
function showMCPHelp() {
    // Generate supported IDE list dynamically from registry
    const ideList = Object.keys(MCP_CONFIG_REGISTRY)
        .map(ide => pc.cyan(ide))
        .join(", ");
    const ideCount = Object.keys(MCP_CONFIG_REGISTRY).length;
    console.log(`
${pc.bold(pc.cyan("MCP - Model Context Protocol Manager"))}
Configure MCP server for AI-powered IDEs

${pc.bold("Commands:")}
  ${pc.green("install")}    Install/download HSA + configure IDE configs
  ${pc.green("update")}     Download latest HSA + regenerate configs
  ${pc.green("uninstall")}  Remove MCP config from IDE
  ${pc.green("status")}     Show MCP configuration status
  ${pc.green("list")}       List supported IDEs

${pc.bold(`Supported IDEs (${ideCount}):`)}
  ${ideList}

${pc.bold("Examples:")}
  nock awf mcp install cursor       ${pc.dim("# Configure Cursor")}
  nock awf mcp install --ide all    ${pc.dim(`# Configure all ${ideCount} IDEs`)}
  nock awf mcp update               ${pc.dim("# Download latest HSA")}
  nock awf mcp status               ${pc.dim("# Check status")}
  nock awf mcp uninstall gemini     ${pc.dim("# Remove from Gemini")}

${pc.bold("Flags:")}
  ${pc.cyan("-i, --ide")}      Target IDE
  ${pc.cyan("-f, --force")}    Overwrite existing config
  ${pc.cyan("-V, --verbose")}  Show detailed output
`);
}
function getIDEDisplayName(ide) {
    const ideConfig = getIDEByValue(ide);
    return ideConfig?.displayName || ide;
}
