/**
 * GeminiRunner — Exécute des agents IA via @google/gemini-cli npm
 *
 * NOTE: "gemini" dans run_agent = runner utilisant @google/gemini-cli npm.
 *
 * Le CLI @google/gemini-cli est installé via `npm install -g @google/gemini-cli`.
 * Flags utilisés (headless):
 *   gemini -p "prompt" --approval-mode yolo --session-id <uuid> --acp [--model <mode>]
 *
 * Les 11 modes Antigravity sont passés via --model pour donner du contexte.
 * Le flag --acp active le mode agent (ACP protocol).
 *
 * Sorties:
 *   --output-format text  → texte lisible
 *   --output-format json  → JSON structuré (utilisé pour extraire session_id)
 */
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import { spawn } from 'child_process';
import { CONFIG, resolveConfigPath } from '../lib/config.js';
import { getLastSessionId, saveSessionId } from '../lib/sessions.js';
import { interpolateEnvVars } from '../lib/envUtils.js';
import { withSpan } from '../lib/telemetry.js';
import { loadEnvQuietly } from '../lib/loadEnv.js';
import pino from 'pino';
import { registerProcess, linkSessionToPid, appendOutput, updateProcessStatus, killProcessTree, } from '../lib/processRegistry.js';
const logger = pino({ name: 'GeminiRunner' });
// ============================================================================
// CHEMINS — @google/gemini-cli npm
// ============================================================================
/** CLI gemini (npm bin — @google/gemini-cli v0.43.0) */
const GEMINI_CLI = 'gemini';
// ============================================================================
// GEMINIRUNNER
// ============================================================================
export class GeminiRunner {
    config;
    timeoutMs;
    constructor() {
        this.config = CONFIG.CLAUDE;
        this.timeoutMs = CONFIG.TIMEOUT_MS || 900000; // 15 min default
    }
    /**
     * Vérifie si @google/gemini-cli est installé
     */
    async isInstalled() {
        return new Promise((resolve) => {
            const child = spawn(GEMINI_CLI, ['--version'], { shell: true, windowsHide: true });
            let output = '';
            child.stdout?.on('data', (d) => (output += d.toString()));
            child.on('close', (code) => resolve(code === 0 && output.includes('0.43')));
            child.on('error', () => resolve(false));
        });
    }
    async runAgent(options) {
        const cwd = options.cwd || process.cwd();
        loadEnvQuietly(path.join(cwd, '.env'));
        loadEnvQuietly(path.join(cwd, '../Workflow/.env'));
        const { prompt, agentName, autoResume, mode = 'GENERAL' } = options;
        let { sessionId } = options;
        const { PATHS } = this.config;
        // ========================================================================
        // VÉRIFICATION gemini CLI
        // ========================================================================
        const installed = await this.isInstalled();
        if (!installed) {
            return {
                result: '',
                error: 'GEMINI_CLI_NOT_INSTALLED: @google/gemini-cli non trouvé.\n' +
                    'Installez avec: npm install -g @google/gemini-cli\n' +
                    'Vérifiez avec: gemini --version',
            };
        }
        // ========================================================================
        // ENV + SESSION
        // ========================================================================
        const agentCustomEnv = {
            ...process.env,
            ...(agentName ? { OVERMIND_AGENT_NAME: agentName } : {}),
        };
        if (autoResume && agentName && !sessionId) {
            const lastId = await getLastSessionId(agentName, options.configPath, 'gemini');
            if (lastId)
                sessionId = lastId;
        }
        // Session ID: génère si non fourni
        if (!sessionId) {
            sessionId = crypto.randomUUID();
        }
        // ========================================================================
        // SYSTEM PROMPT LOADING
        // ========================================================================
        let finalPrompt = prompt;
        if (agentName) {
            try {
                const settingsDir = path.dirname(PATHS.SETTINGS);
                let agentPromptPath = resolveConfigPath(path.join(settingsDir, 'agents', `${agentName}.md`), options.configPath);
                if (!fs.existsSync(agentPromptPath)) {
                    agentPromptPath = resolveConfigPath(path.join(path.dirname(settingsDir), 'agents', `${agentName}.md`), options.configPath);
                }
                if (fs.existsSync(agentPromptPath)) {
                    const systemPrompt = fs.readFileSync(agentPromptPath, 'utf8');
                    finalPrompt = `${systemPrompt}\n\n[USER QUERY]:\n${prompt}`;
                }
            }
            catch (err) {
                logger.warn({ agentName, error: err }, 'Failed to load agent prompt, using raw prompt');
            }
        }
        // ========================================================================
        // MCP CONFIG
        // ========================================================================
        const agentConfigDir = path.join(options.configPath || cwd, '.antigravity', agentName ? `agent_${agentName}` : 'default');
        if (!fs.existsSync(agentConfigDir)) {
            fs.mkdirSync(agentConfigDir, { recursive: true });
        }
        const mcpPath = path.join(agentConfigDir, 'mcp.json');
        if (agentName) {
            const settingsDir = path.dirname(PATHS.SETTINGS);
            const agentSettingsPath = resolveConfigPath(path.join(settingsDir, `settings_${agentName}.json`), options.configPath);
            if (fs.existsSync(agentSettingsPath)) {
                let settings = JSON.parse(fs.readFileSync(agentSettingsPath, 'utf8'));
                settings = interpolateEnvVars(settings);
                if (settings.env) {
                    Object.assign(agentCustomEnv, settings.env);
                }
                const originalMcpPath = resolveConfigPath(PATHS.MCP, options.configPath);
                if (fs.existsSync(originalMcpPath)) {
                    const fullMcp = JSON.parse(fs.readFileSync(originalMcpPath, 'utf8'));
                    let mcpToUse = fullMcp;
                    if (settings.enableAllProjectMcpServers === false &&
                        Array.isArray(settings.enabledMcpjsonServers)) {
                        const filteredMcp = { mcpServers: {} };
                        for (const serverName of settings.enabledMcpjsonServers) {
                            if (fullMcp.mcpServers && fullMcp.mcpServers[serverName]) {
                                filteredMcp.mcpServers[serverName] = fullMcp.mcpServers[serverName];
                            }
                        }
                        mcpToUse = filteredMcp;
                    }
                    fs.writeFileSync(mcpPath, JSON.stringify(mcpToUse, null, 2));
                    logger.info({ mcpPath }, 'MCP configuration synchronized for Gemini');
                    if (!options.silent) {
                        process.stderr.write(`[GeminiRunner] MCP synchronisé: ${mcpPath}\n`);
                    }
                }
            }
        }
        // ========================================================================
        // CONSTRUCTION DES ARGS gemini CLI
        // ========================================================================
        //
        // Mode Antigravity → --model <mode> (pour donner du contexte au prompt)
        // ACP mode active le protocol agent (--acp)
        // --approval-mode yolo = auto-approve tous les outils
        // --output-format json = output structuré pour parser session_id
        // ========================================================================
        const argsSpawn = [];
        // Headless prompt (obligatoire pour non-interactive)
        argsSpawn.push('-p', finalPrompt);
        // Mode Antigravity → passe au model pour context
        argsSpawn.push('--model', `antigravity/${mode}`);
        // ACP mode (agent protocol)
        argsSpawn.push('--acp');
        // Auto-approve tous les outils
        argsSpawn.push('--approval-mode', 'yolo');
        // Session ID persistante
        argsSpawn.push('--session-id', sessionId);
        // Output JSON pour parser proprement
        argsSpawn.push('--output-format', 'json');
        // ========================================================================
        // SPAWN gemini CLI
        // ========================================================================
        const runImpl = async (span) => {
            span.setAttribute('agentName', agentName || '');
            span.setAttribute('runner', 'gemini');
            span.setAttribute('mode', mode);
            return new Promise((resolve) => {
                let resolved = false;
                const safeResolve = (value) => {
                    if (!resolved) {
                        resolved = true;
                        resolve(value);
                    }
                };
                const child = spawn(GEMINI_CLI, argsSpawn, {
                    cwd,
                    shell: true,
                    windowsHide: true,
                    env: agentCustomEnv,
                });
                if (child.pid) {
                    void registerProcess(child.pid, {
                        agentName: agentName || '',
                        runner: 'gemini',
                        configPath: options.configPath,
                    });
                }
                let stdout = '';
                let stderr = '';
                const MAX_BUF = 10 * 1024 * 1024;
                child.stdout?.on('data', (data) => {
                    const d = data.toString();
                    if (child.pid && d)
                        void appendOutput(child.pid, d, options.configPath);
                    stdout += d;
                    if (stdout.length > MAX_BUF)
                        stdout = stdout.slice(-MAX_BUF);
                });
                child.stderr?.on('data', (data) => {
                    const d = data.toString();
                    if (child.pid && d)
                        void appendOutput(child.pid, d, options.configPath);
                    stderr += d;
                    if (stderr.length > MAX_BUF)
                        stderr = stderr.slice(-MAX_BUF);
                });
                const timeout = setTimeout(async () => {
                    if (child.pid)
                        await killProcessTree(child.pid);
                    else
                        child.kill();
                    await new Promise((res) => setTimeout(res, 5000));
                    if (!child.killed) {
                        if (child.pid)
                            await killProcessTree(child.pid);
                        else
                            child.kill('SIGKILL');
                    }
                    if (child.pid) {
                        void updateProcessStatus(child.pid, 'failed', null, options.configPath);
                    }
                    safeResolve({ result: '', error: 'TIMEOUT', rawOutput: stdout + stderr });
                }, this.timeoutMs);
                child.on('error', (err) => {
                    clearTimeout(timeout);
                    safeResolve({ result: '', error: `SPAWN_ERROR: ${err.message}`, rawOutput: '' });
                });
                child.on('close', async (code) => {
                    clearTimeout(timeout);
                    if (code !== 0 && !stdout.trim()) {
                        return safeResolve({
                            result: '',
                            error: `EXIT_CODE_${code}`,
                            rawOutput: stderr,
                        });
                    }
                    try {
                        const trimmed = stdout.trim();
                        let jsonOutput = null;
                        // Parse JSON: d'abord ligne simple, puis extraire du milieu
                        try {
                            jsonOutput = JSON.parse(trimmed);
                        }
                        catch {
                            // Strip les warnings ANSI avant de parser (ex: "Warning: True color...")
                            const lines = trimmed.split('\n');
                            const jsonLines = [];
                            let inJson = false;
                            for (const line of lines) {
                                const trimmed2 = line.trim();
                                // Détecte début JSON
                                if (trimmed2 === '{' || trimmed2.startsWith('{')) {
                                    inJson = true;
                                }
                                if (inJson || trimmed2 === '}' || trimmed2.endsWith('}')) {
                                    inJson = true;
                                    jsonLines.push(trimmed2);
                                }
                            }
                            const cleaned = jsonLines.join('');
                            if (cleaned.startsWith('{')) {
                                try {
                                    jsonOutput = JSON.parse(cleaned);
                                }
                                catch {
                                    // pas de JSON → output brut
                                }
                            }
                        }
                        if (jsonOutput) {
                            const resultText = jsonOutput.reply ||
                                jsonOutput.result ||
                                jsonOutput.output ||
                                trimmed;
                            const newSessionId = jsonOutput.session_id || sessionId;
                            if (newSessionId && agentName) {
                                await saveSessionId(agentName, newSessionId, options.configPath, 'gemini');
                                if (child.pid) {
                                    void linkSessionToPid(newSessionId, child.pid, options.configPath);
                                }
                            }
                            return safeResolve({
                                result: resultText,
                                sessionId: newSessionId,
                                rawOutput: stdout,
                            });
                        }
                        // Fallback: texte brut (le mode --output-format text est utilisé si json échoue)
                        return safeResolve({
                            result: trimmed,
                            sessionId: sessionId,
                            rawOutput: stdout,
                        });
                    }
                    catch {
                        return safeResolve({
                            result: stdout.trim(),
                            sessionId: sessionId,
                            rawOutput: stdout,
                        });
                    }
                });
                if (child.stdin) {
                    child.stdin.end();
                }
            });
        };
        const result = await withSpan('gemini.runAgent', runImpl, {
            agentName: agentName || '',
            runner: 'gemini',
        });
        // Cleanup fichier MCP temporaire
        if (fs.existsSync(mcpPath)) {
            try {
                fs.unlinkSync(mcpPath);
            }
            catch {
                // ignore cleanup errors
            }
        }
        return result;
    }
}
//# sourceMappingURL=GeminiRunner.js.map