// packages/plugin-awf/src/commands/mcp-registry.ts
// MCP Config Registry and path resolution utilities, extracted from mcp.ts
import * as fs from "node:fs";
import * as path from "node:path";
import { execSync } from "node:child_process";
import { homedir } from "node:os";
import { getHSABundlePath } from "./install-hsa.js";
// ═══════════════════════════════════════════════════════════════════════════
// VS CODE VARIANT DETECTION
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Detect VS Code variant from existing globalStorage directories.
 * Extensions (Cline, Roo Code) store MCP settings in VS Code's globalStorage,
 * which differs per variant: "Code", "Code - Insiders", "VSCodium".
 * Returns the base user-data directory name.
 */
function detectVSCodeVariant() {
    if (process.platform === "win32") {
        const appData = process.env.APPDATA || path.join(homedir(), "AppData", "Roaming");
        // Prioritize Insiders (more likely for dev users), then Stable, then VSCodium
        const variants = ["Code - Insiders", "Code", "VSCodium"];
        for (const variant of variants) {
            const gsDir = path.join(appData, variant, "User", "globalStorage");
            if (fs.existsSync(gsDir))
                return variant;
        }
        return "Code";
    }
    // macOS / Linux: check ~/.config/ variants
    const configBase = process.platform === "darwin"
        ? path.join(homedir(), "Library", "Application Support")
        : path.join(homedir(), ".config");
    const variants = ["Code - Insiders", "Code", "VSCodium"];
    for (const variant of variants) {
        const gsDir = path.join(configBase, variant, "User", "globalStorage");
        if (fs.existsSync(gsDir))
            return variant;
    }
    return "Code";
}
/** Cached variant to avoid repeated fs.existsSync calls */
let _cachedVariant = null;
function getVSCodeVariant() {
    if (_cachedVariant === null)
        _cachedVariant = detectVSCodeVariant();
    return _cachedVariant;
}
/**
 * Build globalStorage-based config path for a VS Code extension.
 * Handles platform differences and VS Code variant detection.
 */
function vsCodeExtConfigPath(extensionId, fileName, subfolder) {
    const variant = getVSCodeVariant();
    const relPath = subfolder
        ? `${variant}/User/globalStorage/${extensionId}/${subfolder}/${fileName}`
        : `${variant}/User/globalStorage/${extensionId}/${fileName}`;
    if (process.platform === "win32") {
        return `%APPDATA%/${relPath}`;
    }
    if (process.platform === "darwin") {
        return `~/Library/Application Support/${relPath}`;
    }
    return `~/.config/${relPath}`;
}
// ═══════════════════════════════════════════════════════════════════════════
// MCP CONFIG REGISTRY - Per-IDE MCP Configuration Paths
// ═══════════════════════════════════════════════════════════════════════════
export const MCP_CONFIG_REGISTRY = {
    // ══════════════════════════════════════════════════════════════
    // Tier 0: Just Launched (Feb 2026)
    // ══════════════════════════════════════════════════════════════
    codex: {
        configPath: "~/.codex/config.toml",
        format: "toml",
        serverName: "domyh-hsa",
        mcpKey: "mcp_servers",
    },
    // ══════════════════════════════════════════════════════════════
    // Tier 1: Major IDEs
    // ══════════════════════════════════════════════════════════════
    cursor: {
        configPath: "~/.cursor/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    claude: {
        configPath: process.platform === "win32"
            ? "%APPDATA%/Claude/claude_desktop_config.json"
            : "~/.config/Claude/claude_desktop_config.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    windsurf: {
        configPath: "~/.codeium/windsurf/mcp_config.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    gemini: {
        configPath: "~/.gemini/settings.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    // ══════════════════════════════════════════════════════════════
    // Tier 2: Popular Agents
    // ══════════════════════════════════════════════════════════════
    cline: {
        // VS Code variant-aware: auto-detects Code / Code - Insiders / VSCodium
        configPath: vsCodeExtConfigPath("saoudrizwan.claude-dev", "cline_mcp_settings.json", "settings"),
        format: "json",
        serverName: "domyh-hsa",
    },
    continue: {
        configPath: "~/.continue/config.yaml",
        format: "yaml",
        serverName: "domyh-hsa",
    },
    roo: {
        configPath: ".roo/mcp.json", // Deep Audit v3: project-local, NOT global (~/.roo/)
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    // Roo Code global MCP settings (stored in VS Code globalStorage)
    // docs.roocode.com: "Global: mcp_settings.json, accessible via VS Code settings"
    "roo-global": {
        configPath: vsCodeExtConfigPath("rooveterinaryinc.roo-cline", "mcp_settings.json", "settings"),
        format: "json",
        serverName: "domyh-hsa",
    },
    jetbrains: {
        configPath: ".junie/mcp/mcp.json", // Deep Audit v3: was servers.json, fixed to mcp.json
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    kiro: {
        configPath: ".kiro/settings/mcp.json", // Deep Audit v3: was .kiro/mcp.json, fixed
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    zed: {
        configPath: "~/.config/zed/settings.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "context_servers",
    },
    tabnine: {
        configPath: ".tabnine/mcp_servers.json",
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    trae: {
        configPath: ".trae/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    // Trae global MCP settings (stored in AppData/Roaming/Trae/)
    "trae-global": {
        configPath: process.platform === "win32"
            ? "%APPDATA%/Trae/User/mcp.json"
            : process.platform === "darwin"
                ? "~/Library/Application Support/Trae/User/mcp.json"
                : "~/.config/Trae/User/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    // Blackbox AI project-local MCP
    blackbox: {
        configPath: ".blackbox/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    // Blackbox AI extension MCP (stored in VS Code globalStorage)
    "blackbox-global": {
        configPath: vsCodeExtConfigPath("blackboxapp.blackboxagent", "blackbox_mcp_settings.json", "settings"),
        format: "json",
        serverName: "domyh-hsa",
    },
    // ══════════════════════════════════════════════════════════════
    // Tier 3: Extended
    // ══════════════════════════════════════════════════════════════
    augment: {
        configPath: "~/.augment/settings.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "mcp", // Augment uses "mcp" key
    },
    // ══════════════════════════════════════════════════════════════
    // Deep Audit v3: Added missing IDEs to align with IDE_REGISTRY SSoT
    // ══════════════════════════════════════════════════════════════
    vscode: {
        configPath: ".vscode/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    amazonq: {
        configPath: ".amazonq/mcp.json",
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    amp: {
        configPath: ".vscode/mcp.json", // Amp uses VS Code MCP format
        format: "json",
        serverName: "domyh-hsa",
        projectLocal: true,
    },
    opencode: {
        configPath: "opencode.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "mcp", // OpenCode uses "mcp" key in opencode.json
        projectLocal: true,
    },
    // OpenCode global MCP (stored in ~/.config/opencode/opencode.json)
    // Docs: "Global config in ~/.config/opencode/opencode.json"
    "opencode-global": {
        configPath: "~/.config/opencode/opencode.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "mcp",
    },
    // OpenClaw project-local MCP
    openclaw: {
        configPath: "openclaw.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "mcp", // Same MCP key as OpenCode
        projectLocal: true,
    },
    // OpenClaw global MCP (stored in ~/.openclaw/openclaw.json)
    "openclaw-global": {
        configPath: process.platform === "win32"
            ? "%USERPROFILE%/.openclaw/openclaw.json"
            : "~/.openclaw/openclaw.json",
        format: "json",
        serverName: "domyh-hsa",
        mcpKey: "mcp",
    },
    // Removed: void (development paused Jan 2026)
    cody: {
        configPath: process.platform === "win32"
            ? "%USERPROFILE%/.config/cody/mcp_servers.json"
            : "~/.config/cody/mcp_servers.json",
        format: "json",
        serverName: "domyh-hsa",
    },
    antigravity: {
        configPath: "~/.gemini/antigravity/mcp_config.json",
        format: "json",
        serverName: "domyh-hsa",
    },
};
// ═══════════════════════════════════════════════════════════════════════════
// PATH RESOLUTION UTILITIES
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Resolve MCP config path with cross-platform support
 */
export function resolveMCPConfigPath(configPath) {
    // Replace ~ with home directory
    let resolved = configPath.replace(/^~/, homedir());
    // Replace %APPDATA% on Windows
    if (process.platform === "win32" && resolved.includes("%APPDATA%")) {
        const appData = process.env.APPDATA;
        if (!appData) {
            throw new Error("APPDATA environment variable is not set. Cannot resolve MCP config path.");
        }
        resolved = resolved.replace("%APPDATA%", appData);
    }
    // Handle %USERPROFILE% (used by Cody on Windows)
    if (process.platform === "win32" && resolved.includes("%USERPROFILE%")) {
        const userProfile = process.env.USERPROFILE || homedir();
        resolved = resolved.replace("%USERPROFILE%", userProfile);
    }
    return path.normalize(resolved);
}
// ═══════════════════════════════════════════════════════════════════════════
// NPX MCP SERVER COMMAND (v2.0 — replaces path resolution)
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Get MCP server command — prefers direct node invocation to avoid npx wrapper.
 *
 * npx creates an unnecessary wrapper node.exe process (~40 MB) that stays alive.
 * By resolving the bundle path from npx cache or global install, we run
 * `node hsa-cli.bundle.js` directly — 1 process instead of 2 per IDE window.
 *
 * If bundle not found, auto-installs `@nockdev/hsa` globally (one-time).
 * Falls back to npx ONLY if global install also fails.
 *
 * @example
 * // If cached/installed: { command: "node", args: ["C:\\...\\hsa-cli.bundle.js"] }
 * // If all fails:        { command: "npx", args: ["-y", "-p", "@nockdev/hsa@latest", "nock-hsa"] }
 */
export function getMCPServerCommand() {
    // Strategy 0: Check dedicated install dir (~/.nockdev/hsa/)
    const dedicatedPath = getHSABundlePath();
    if (dedicatedPath) {
        return { command: "node", args: [dedicatedPath] };
    }
    // Strategy 1+2: Check npx cache or global node_modules (legacy fallback)
    let bundlePath = resolveHsaBundlePath();
    if (bundlePath) {
        return { command: "node", args: [bundlePath] };
    }
    // Strategy 3 removed (INST-03 FIX): Silent global install violated SAFE_001.
    // With dedicated install (~/.nockdev/hsa/) as Strategy 0, global install is unnecessary.
    // Use `nock awf mcp install` to download HSA explicitly.
    // Final fallback: npx will download and cache on first run
    return {
        command: "npx",
        args: ["-y", "-p", "@nockdev/hsa@latest", "nock-hsa"],
    };
}
/**
 * Resolve the hsa-cli.bundle.js path from npx cache or global node_modules.
 * Returns null if not found (first install — npx needed).
 */
function resolveHsaBundlePath() {
    const bundleRelPath = path.join("node_modules", "@nockdev", "hsa", "dist", "hsa-cli.bundle.js");
    // Strategy 1: Check npx cache (_npx directory inside npm cache)
    try {
        const npmCache = execSync("npm config get cache", {
            encoding: "utf8",
            timeout: 5_000,
            stdio: ["ignore", "pipe", "ignore"],
        }).trim();
        const npxDir = path.join(npmCache, "_npx");
        if (fs.existsSync(npxDir)) {
            for (const dir of fs.readdirSync(npxDir)) {
                const bundlePath = path.join(npxDir, dir, bundleRelPath);
                if (fs.existsSync(bundlePath)) {
                    return bundlePath;
                }
            }
        }
    }
    catch {
        // npm not found or cache not accessible — try next strategy
    }
    // Strategy 2: Check global node_modules
    try {
        const globalPrefix = execSync("npm prefix -g", {
            encoding: "utf8",
            timeout: 5_000,
            stdio: ["ignore", "pipe", "ignore"],
        }).trim();
        // npm global on Windows: prefix/node_modules, on Unix: prefix/lib/node_modules
        const globalPaths = [
            path.join(globalPrefix, "node_modules", "@nockdev", "hsa", "dist", "hsa-cli.bundle.js"),
            path.join(globalPrefix, "lib", "node_modules", "@nockdev", "hsa", "dist", "hsa-cli.bundle.js"),
        ];
        for (const p of globalPaths) {
            if (fs.existsSync(p))
                return p;
        }
    }
    catch {
        // Global prefix not accessible
    }
    return null;
}
/**
 * Check if an IDE uses project-local MCP config.
 * Project-local configs (`.vscode/mcp.json`, `.roo/mcp.json`, etc.) live
 * inside the project dir → need absolute path.
 * Global configs (`~/.cursor/mcp.json`, etc.) are shared across projects
 * → use `${workspaceFolder}` variable (IDE resolves per-window).
 */
export function isProjectLocalConfig(ide) {
    return MCP_CONFIG_REGISTRY[ide]?.projectLocal === true;
}
/**
 * Detect MCP configs with stale npx cache paths.
 * Scans existing JSON configs for "npm-cache" or "_npx" patterns
 * which indicate the config was created before HSA was installed
 * in the dedicated ~/.nockdev/hsa/ directory.
 * Returns list of IDE names that need re-install.
 */
export function detectStaleConfigs() {
    const stale = [];
    for (const [ide, config] of Object.entries(MCP_CONFIG_REGISTRY)) {
        // Only check JSON configs (TOML/YAML have different detection needs)
        if (config.format !== "json")
            continue;
        try {
            const configPath = resolveMCPConfigPath(config.configPath);
            if (!fs.existsSync(configPath))
                continue;
            const content = fs.readFileSync(configPath, "utf-8");
            // Detect npx cache paths (e.g., AppData/Local/npm-cache/_npx/...)
            if (content.includes("npm-cache") || content.includes("_npx")) {
                stale.push(ide);
            }
        }
        catch {
            // Skip unreadable configs
        }
    }
    return stale;
}
// ═══════════════════════════════════════════════════════════════════════════
// SHELL DETECTION — Detect IDE's default terminal shell
// ═══════════════════════════════════════════════════════════════════════════
/** VS Code fork settings.json paths per IDE */
const VSCODE_SETTINGS_PATHS = {
    vscode: process.platform === "win32"
        ? path.join(homedir(), "AppData", "Roaming", "Code", "User", "settings.json")
        : path.join(homedir(), ".config", "Code", "User", "settings.json"),
    cursor: path.join(homedir(), ".cursor", "User", "settings.json"),
    windsurf: path.join(homedir(), ".codeium", "windsurf", "User", "settings.json"),
    antigravity: path.join(homedir(), ".gemini", "antigravity", "settings.json"),
    trae: path.join(homedir(), ".trae", "User", "settings.json"),
    kiro: path.join(homedir(), ".kiro", "User", "settings.json"),
    amp: process.platform === "win32"
        ? path.join(homedir(), "AppData", "Roaming", "Code", "User", "settings.json")
        : path.join(homedir(), ".config", "Code", "User", "settings.json"),
};
/**
 * Normalize a shell profile name to a canonical shell type.
 * VS Code profile names vary: "Git Bash", "Command Prompt", "PowerShell", etc.
 */
function normalizeShellName(profile) {
    const lower = profile.toLowerCase();
    if (lower.includes("bash") || lower.includes("git bash"))
        return "bash";
    if (lower.includes("pwsh") || lower.includes("powershell"))
        return "powershell";
    if (lower.includes("cmd") || lower.includes("command prompt"))
        return "cmd";
    if (lower.includes("zsh"))
        return "zsh";
    if (lower.includes("fish"))
        return "fish";
    return lower;
}
/**
 * Normalize a shell executable path to a canonical shell type.
 * e.g. "/bin/zsh" → "zsh", "C:\\...\\bash.exe" → "bash"
 */
function normalizeShellPath(shellPath) {
    return normalizeShellName(path.basename(shellPath).replace(/\.exe$/i, ""));
}
/**
 * Detect the IDE's default terminal shell.
 *
 * Strategy:
 * 1. VS Code forks → read `settings.json` → `terminal.integrated.defaultProfile.*`
 * 2. JetBrains → read `options/terminal.xml` → `myShellPath`
 * 3. Zed → read `settings.json` → `terminal.shell.program`
 * 4. VS Code extensions → fallback to host VS Code settings
 * 5. System shell → `COMSPEC` on Windows, `$SHELL` on Unix
 *
 * @returns Normalized shell name: "bash", "cmd", "powershell", "zsh", "fish"
 */
export function detectIDEShell(ide) {
    const platform = process.platform;
    // 1. VS Code forks: read settings.json for terminal profile
    const settingsPath = VSCODE_SETTINGS_PATHS[ide];
    if (settingsPath && fs.existsSync(settingsPath)) {
        try {
            const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
            const profileKey = `terminal.integrated.defaultProfile.${platform === "win32" ? "windows" : platform === "darwin" ? "osx" : "linux"}`;
            const profile = settings[profileKey];
            if (profile && typeof profile === "string") {
                return normalizeShellName(profile);
            }
        }
        catch { /* ignore parse errors — fall through */ }
    }
    // 2. JetBrains: read options/terminal.xml
    if (ide === "jetbrains") {
        const home = homedir();
        const appData = platform === "win32"
            ? (process.env.APPDATA || path.join(home, "AppData", "Roaming"))
            : platform === "darwin"
                ? path.join(home, "Library", "Application Support")
                : path.join(home, ".config");
        const jbDir = path.join(appData, "JetBrains");
        if (fs.existsSync(jbDir)) {
            try {
                const products = fs.readdirSync(jbDir)
                    .filter(d => {
                    try {
                        return fs.statSync(path.join(jbDir, d)).isDirectory();
                    }
                    catch {
                        return false;
                    }
                })
                    .sort().reverse();
                for (const product of products) {
                    const terminalXml = path.join(jbDir, product, "options", "terminal.xml");
                    if (fs.existsSync(terminalXml)) {
                        const content = fs.readFileSync(terminalXml, "utf-8");
                        const match = content.match(/name="myShellPath"\s+value="([^"]+)"/);
                        if (match?.[1])
                            return normalizeShellPath(match[1]);
                    }
                }
            }
            catch { /* ignore */ }
        }
    }
    // 3. Zed: read settings.json → terminal.shell.program
    if (ide === "zed") {
        const zedSettings = path.join(homedir(), ".config", "zed", "settings.json");
        if (fs.existsSync(zedSettings)) {
            try {
                const settings = JSON.parse(fs.readFileSync(zedSettings, "utf-8"));
                const shellProgram = settings?.terminal?.shell?.program;
                if (shellProgram && typeof shellProgram === "string") {
                    return normalizeShellPath(shellProgram);
                }
            }
            catch { /* ignore */ }
        }
    }
    // 4. VS Code extensions: fallback to host VS Code settings
    if (["cline", "roo", "amazonq", "tabnine", "continue", "cody"].includes(ide)) {
        const hostPath = VSCODE_SETTINGS_PATHS["vscode"];
        if (hostPath && fs.existsSync(hostPath)) {
            try {
                const settings = JSON.parse(fs.readFileSync(hostPath, "utf-8"));
                const profileKey = `terminal.integrated.defaultProfile.${platform === "win32" ? "windows" : platform === "darwin" ? "osx" : "linux"}`;
                const profile = settings[profileKey];
                if (profile && typeof profile === "string") {
                    return normalizeShellName(profile);
                }
            }
            catch { /* ignore */ }
        }
    }
    // 5. System default fallback
    if (platform === "win32") {
        const comspec = (process.env.COMSPEC || "").toLowerCase();
        if (comspec.includes("powershell") || comspec.includes("pwsh"))
            return "powershell";
        return "cmd";
    }
    return process.env.SHELL?.split("/").pop() || "bash";
}
// ═══════════════════════════════════════════════════════════════════════════
// MCP SERVER ENTRY BUILDER
// ═══════════════════════════════════════════════════════════════════════════
/**
 * Build the MCP server config entry for IDE config files.
 *
 * @param projectPath - For project-local IDEs: absolute project path.
 *   For global IDEs: pass `undefined` → HSA_PROJECT_PATH is OMITTED.
 *   Runtime deferred init uses MCP roots/list to get the correct workspace.
 * @param ide - IDE identifier (e.g. "cursor", "antigravity") for shell detection.
 *
 * @example
 * buildMCPServerEntry();                         // Global IDE, no path
 * buildMCPServerEntry("/my/proj", "vscode");     // Project-local with shell detect
 */
export function buildMCPServerEntry(projectPath, ide) {
    const { command, args } = getMCPServerCommand();
    // Only standard MCP keys: command, args, env
    // Non-standard keys (auto_start, description) removed — most IDEs reject them
    // HSA_DASHBOARD disabled by default to save ~20MB RSS per engine.
    // NOTE: Do NOT set NODE_OPTIONS here — V8 flags blocked by Node.js security.
    const env = {
        HSA_DASHBOARD: "false",
        HSA_MAX_TOKENS: "8000",
    };
    // Only set HSA_PROJECT_PATH for project-local configs with known path
    // Global configs: omit → runtime uses MCP roots/list for correct workspace
    if (projectPath) {
        env.HSA_PROJECT_PATH = projectPath;
    }
    // SHELL-DETECT: Inject IDE's default terminal shell so agent knows
    // which command syntax to use (bash vs cmd vs powershell)
    // IDE-HINT: Pass IDE identity so HSA can skip process tree detection
    // (critical for extensions like Blackbox that share VS Code Insiders process)
    if (ide) {
        env.HSA_IDE_SHELL = detectIDEShell(ide);
        // Strip "-global" suffix for IDE hint (e.g. "blackbox-global" → "blackbox")
        env.HSA_IDE = ide.replace(/-global$/, "");
    }
    return {
        command,
        args,
        env,
    };
}
