/**
 * mcp-setup command — detect AI coding tools and configure the Flow Weaver MCP server.
 */
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import confirm from '@inquirer/confirm';
import { ExitPromptError } from '@inquirer/core';
import { isNonInteractive } from './init.js';
// ── Constants ────────────────────────────────────────────────────────────────
const MCP_COMMAND = 'npx';
const MCP_ARGS = ['@synergenius/flow-weaver@latest', 'mcp-server', '--stdio'];
const MCP_ENTRY = { command: MCP_COMMAND, args: [...MCP_ARGS] };
/** Tools that can be spawned as interactive CLI sessions */
export const CLI_TOOL_IDS = new Set(['claude', 'codex']);
/** Binary name to spawn for each CLI tool */
export const CLI_TOOL_BINARY = {
    claude: 'claude',
    codex: 'codex',
};
// ── Deps ─────────────────────────────────────────────────────────────────────
export function defaultDeps() {
    return {
        execCommand: async (cmd) => {
            try {
                const stdout = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
                return { stdout: stdout.trim(), exitCode: 0 };
            }
            catch {
                return { stdout: '', exitCode: 1 };
            }
        },
        fileExists: async (filePath) => {
            try {
                await fs.promises.access(filePath);
                return true;
            }
            catch {
                return false;
            }
        },
        readFile: async (filePath) => {
            try {
                return await fs.promises.readFile(filePath, 'utf8');
            }
            catch {
                return null;
            }
        },
        writeFile: async (filePath, content) => {
            await fs.promises.writeFile(filePath, content, 'utf8');
        },
        mkdir: async (dirPath) => {
            await fs.promises.mkdir(dirPath, { recursive: true });
        },
        cwd: () => process.cwd(),
        homedir: () => os.homedir(),
        log: (msg) => process.stdout.write(msg + '\n'),
    };
}
// ── Helpers ──────────────────────────────────────────────────────────────────
function whichCmd(binary) {
    return process.platform === 'win32' ? `where ${binary}` : `which ${binary}`;
}
async function binaryExists(binary, deps) {
    const result = await deps.execCommand(whichCmd(binary));
    return result.exitCode === 0;
}
export async function mergeJsonConfig(deps, filePath, rootKey) {
    const existing = await deps.readFile(filePath);
    if (existing === null) {
        // File doesn't exist: create it
        const dir = path.dirname(filePath);
        await deps.mkdir(dir);
        const config = { [rootKey]: { 'flow-weaver': MCP_ENTRY } };
        await deps.writeFile(filePath, JSON.stringify(config, null, 2) + '\n');
        return { action: 'created', detail: `created ${filePath}` };
    }
    // File exists: parse and merge
    let config;
    try {
        config = JSON.parse(existing);
    }
    catch {
        throw new Error(`invalid JSON in ${filePath}`);
    }
    if (!config[rootKey] || typeof config[rootKey] !== 'object') {
        config[rootKey] = {};
    }
    const servers = config[rootKey];
    if (servers['flow-weaver']) {
        return { action: 'already-configured', detail: `already in ${filePath}` };
    }
    servers['flow-weaver'] = MCP_ENTRY;
    await deps.writeFile(filePath, JSON.stringify(config, null, 2) + '\n');
    return { action: 'added', detail: `added to ${filePath}` };
}
// ── Tool Registry ────────────────────────────────────────────────────────────
export const TOOL_REGISTRY = [
    // Claude Code
    {
        id: 'claude',
        displayName: 'Claude Code',
        detect: (deps) => binaryExists('claude', deps),
        isConfigured: async (deps) => {
            const result = await deps.execCommand('claude mcp list');
            return result.exitCode === 0 && result.stdout.includes('flow-weaver');
        },
        configure: async (deps) => {
            const cmd = `claude mcp add --scope project flow-weaver -- ${MCP_COMMAND} ${MCP_ARGS.join(' ')}`;
            const result = await deps.execCommand(cmd);
            if (result.exitCode !== 0) {
                throw new Error('claude mcp add failed');
            }
            return 'registered via claude mcp add';
        },
    },
    // Cursor
    {
        id: 'cursor',
        displayName: 'Cursor',
        detect: async (deps) => {
            const dirExists = await deps.fileExists(path.join(deps.cwd(), '.cursor'));
            if (dirExists)
                return true;
            return binaryExists('cursor', deps);
        },
        isConfigured: async (deps) => {
            const filePath = path.join(deps.cwd(), '.cursor', 'mcp.json');
            const content = await deps.readFile(filePath);
            if (!content)
                return false;
            try {
                const config = JSON.parse(content);
                return !!config?.mcpServers?.['flow-weaver'];
            }
            catch {
                return false;
            }
        },
        configure: async (deps) => {
            const filePath = path.join(deps.cwd(), '.cursor', 'mcp.json');
            const result = await mergeJsonConfig(deps, filePath, 'mcpServers');
            return result.detail;
        },
    },
    // VS Code Copilot
    {
        id: 'vscode',
        displayName: 'VS Code Copilot',
        detect: (deps) => binaryExists('code', deps),
        isConfigured: async (deps) => {
            const filePath = path.join(deps.cwd(), '.vscode', 'mcp.json');
            const content = await deps.readFile(filePath);
            if (!content)
                return false;
            try {
                const config = JSON.parse(content);
                return !!config?.servers?.['flow-weaver'];
            }
            catch {
                return false;
            }
        },
        configure: async (deps) => {
            const filePath = path.join(deps.cwd(), '.vscode', 'mcp.json');
            const result = await mergeJsonConfig(deps, filePath, 'servers');
            return result.detail;
        },
    },
    // Windsurf
    {
        id: 'windsurf',
        displayName: 'Windsurf',
        detect: async (deps) => {
            const configDir = path.join(deps.homedir(), '.codeium', 'windsurf');
            const dirExists = await deps.fileExists(configDir);
            if (dirExists)
                return true;
            return binaryExists('windsurf', deps);
        },
        isConfigured: async (deps) => {
            const filePath = path.join(deps.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
            const content = await deps.readFile(filePath);
            if (!content)
                return false;
            try {
                const config = JSON.parse(content);
                return !!config?.mcpServers?.['flow-weaver'];
            }
            catch {
                return false;
            }
        },
        configure: async (deps) => {
            const filePath = path.join(deps.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
            const result = await mergeJsonConfig(deps, filePath, 'mcpServers');
            return result.detail;
        },
    },
    // Codex (OpenAI)
    {
        id: 'codex',
        displayName: 'Codex',
        detect: (deps) => binaryExists('codex', deps),
        isConfigured: async (deps) => {
            const result = await deps.execCommand('codex mcp list');
            return result.exitCode === 0 && result.stdout.includes('flow-weaver');
        },
        configure: async (deps) => {
            const cmd = `codex mcp add flow-weaver -- ${MCP_COMMAND} ${MCP_ARGS.join(' ')}`;
            const result = await deps.execCommand(cmd);
            if (result.exitCode !== 0) {
                throw new Error('codex mcp add failed');
            }
            return 'registered via codex mcp add';
        },
    },
    // OpenClaw
    {
        id: 'openclaw',
        displayName: 'OpenClaw',
        detect: async (deps) => {
            return deps.fileExists(path.join(deps.cwd(), 'openclaw.json'));
        },
        isConfigured: async (deps) => {
            const filePath = path.join(deps.cwd(), 'openclaw.json');
            const content = await deps.readFile(filePath);
            if (!content)
                return false;
            try {
                const config = JSON.parse(content);
                return !!config?.mcpServers?.['flow-weaver'];
            }
            catch {
                return false;
            }
        },
        configure: async (deps) => {
            const filePath = path.join(deps.cwd(), 'openclaw.json');
            const result = await mergeJsonConfig(deps, filePath, 'mcpServers');
            return result.detail;
        },
    },
];
// ── Detection ────────────────────────────────────────────────────────────────
export async function detectTools(deps) {
    const results = await Promise.all(TOOL_REGISTRY.map(async (tool) => {
        const detected = await tool.detect(deps);
        const configured = detected ? await tool.isConfigured(deps) : false;
        return { id: tool.id, displayName: tool.displayName, detected, configured };
    }));
    return results;
}
// ── Configuration ────────────────────────────────────────────────────────────
async function configureTool(tool, deps) {
    try {
        const already = await tool.isConfigured(deps);
        if (already) {
            return { id: tool.id, displayName: tool.displayName, action: 'already-configured', detail: 'already configured' };
        }
        const detail = await tool.configure(deps);
        return { id: tool.id, displayName: tool.displayName, action: 'configured', detail };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { id: tool.id, displayName: tool.displayName, action: 'failed', detail: msg };
    }
}
/**
 * Detect and configure all AI tools without interactive prompts.
 * Used by the init command after the user has already consented.
 */
export async function runMcpSetupFromInit(deps) {
    const d = deps ?? defaultDeps();
    const detected = await detectTools(d);
    const toConfig = detected.filter((t) => t.detected && !t.configured);
    const configured = [];
    const failed = [];
    const toolMap = new Map(TOOL_REGISTRY.map((t) => [t.id, t]));
    for (const t of toConfig) {
        const tool = toolMap.get(t.id);
        if (!tool)
            continue;
        const result = await configureTool(tool, d);
        if (result.action === 'configured') {
            configured.push(result.displayName);
        }
        else if (result.action === 'failed') {
            failed.push(result.displayName);
        }
    }
    // Include already-configured tools in the configured list
    for (const t of detected) {
        if (t.configured) {
            configured.push(t.displayName);
        }
    }
    // Classify configured/detected tools as CLI or GUI
    const allConfiguredIds = detected
        .filter((t) => t.detected && (t.configured || configured.includes(t.displayName)))
        .map((t) => t.id);
    const cliTools = allConfiguredIds.filter((id) => CLI_TOOL_IDS.has(id));
    const guiTools = allConfiguredIds.filter((id) => !CLI_TOOL_IDS.has(id));
    return { configured, failed, detected, cliTools, guiTools };
}
// ── Quick CLI tool detection (no config, just binary check) ──────────────────
/**
 * Check which CLI agent tools are available on PATH.
 * Lightweight: only runs `which` for known CLI binaries.
 */
export async function detectCliTools(deps) {
    const d = deps ?? defaultDeps();
    const results = [];
    for (const [id, binary] of Object.entries(CLI_TOOL_BINARY)) {
        if (binary && await binaryExists(binary, d)) {
            results.push(id);
        }
    }
    return results;
}
// ── Command ──────────────────────────────────────────────────────────────────
export async function mcpSetupCommand(options, deps) {
    const d = deps ?? defaultDeps();
    // Step 1: detect all tools
    const detected = await detectTools(d);
    // Step 2: --list mode
    if (options.list) {
        d.log('');
        for (const t of detected) {
            const status = t.detected
                ? (t.configured ? 'detected, configured' : 'detected')
                : 'not found';
            const icon = t.detected ? (t.configured ? '●' : '○') : '·';
            d.log(`  ${icon} ${t.displayName.padEnd(18)} ${status}`);
        }
        d.log('');
        return;
    }
    // Step 3: determine which tools to configure
    let toolIds;
    if (options.tool && options.tool.length > 0) {
        // Validate tool names
        const valid = new Set(TOOL_REGISTRY.map((t) => t.id));
        for (const name of options.tool) {
            if (!valid.has(name)) {
                d.log(`Unknown tool: "${name}". Valid tools: ${[...valid].join(', ')}`);
                return;
            }
        }
        toolIds = options.tool;
    }
    else if (options.all) {
        toolIds = detected.filter((t) => t.detected).map((t) => t.id);
    }
    else if (isNonInteractive()) {
        // Non-TTY: configure all detected tools
        toolIds = detected.filter((t) => t.detected).map((t) => t.id);
    }
    else {
        // Interactive: show detection results, then confirm each
        const detectedTools = detected.filter((t) => t.detected);
        if (detectedTools.length === 0) {
            d.log('No AI coding tools detected. You can specify tools manually with --tool.');
            return;
        }
        d.log('');
        d.log('Detected tools:');
        for (const t of detected) {
            const icon = t.detected ? '✓' : '✗';
            d.log(`  ${icon} ${t.displayName}`);
        }
        d.log('');
        toolIds = [];
        try {
            for (const t of detectedTools) {
                if (t.configured) {
                    d.log(`  ${t.displayName}: already configured, skipping`);
                    continue;
                }
                const yes = await confirm({
                    message: `Configure ${t.displayName}?`,
                    default: true,
                });
                if (yes)
                    toolIds.push(t.id);
            }
        }
        catch (err) {
            if (err instanceof ExitPromptError)
                return;
            throw err;
        }
        d.log('');
    }
    if (toolIds.length === 0) {
        const anyDetected = detected.some((t) => t.detected);
        if (!anyDetected) {
            d.log('No AI coding tools detected. You can specify tools manually with --tool.');
        }
        else {
            d.log('No tools selected.');
        }
        return;
    }
    // Step 4: configure selected tools
    const toolMap = new Map(TOOL_REGISTRY.map((t) => [t.id, t]));
    const results = [];
    for (const id of toolIds) {
        const tool = toolMap.get(id);
        const result = await configureTool(tool, d);
        results.push(result);
        const icon = result.action === 'configured' ? '✓'
            : result.action === 'already-configured' ? '●'
                : '✗';
        d.log(`${icon} ${result.displayName}: ${result.detail}`);
    }
    // Summary
    const configured = results.filter((r) => r.action === 'configured').length;
    const alreadyDone = results.filter((r) => r.action === 'already-configured').length;
    const failed = results.filter((r) => r.action === 'failed').length;
    const parts = [];
    if (configured > 0)
        parts.push(`${configured} configured`);
    if (alreadyDone > 0)
        parts.push(`${alreadyDone} already configured`);
    if (failed > 0)
        parts.push(`${failed} failed`);
    d.log('');
    d.log(`Done. ${parts.join(', ')}.`);
}
//# sourceMappingURL=mcp-setup.js.map