/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { spawn } from 'node:child_process';
import { randomBytes, randomUUID } from 'node:crypto';
import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';
const SENSITIVE_ENV_VARS = new Set([
    'AWS_ACCESS_KEY_ID',
    'AWS_SECRET_ACCESS_KEY',
    'AWS_SESSION_TOKEN',
    'GITHUB_TOKEN',
    'GH_TOKEN',
    'GITHUB_PAT',
    'SONAR_TOKEN',
    'ANTHROPIC_API_KEY',
    'OPENAI_API_KEY',
    'CLAUDE_API_KEY',
    'SLACK_TOKEN',
    'SLACK_BOT_TOKEN',
    'DATABASE_URL',
    'DB_PASSWORD',
    'REDIS_URL',
    'CF_ACCESS_CLIENT_ID',
    'CF_ACCESS_CLIENT_SECRET',
    'NPM_TOKEN',
    'DOCKER_PASSWORD',
]);
function buildSandboxEnv() {
    const env = {};
    for (const [key, value] of Object.entries(process.env)) {
        if (!SENSITIVE_ENV_VARS.has(key)) {
            env[key] = value;
        }
    }
    env.SETUP_AGENTS_SANDBOXED = '1';
    return env;
}
function chatDebugLog(workspace, entry) {
    try {
        const ts = new Date().toISOString();
        const line = JSON.stringify({ ts, ...entry });
        // Write to disk
        const dir = join(workspace, '.setup-agents');
        if (!existsSync(dir))
            mkdirSync(dir, { recursive: true });
        appendFileSync(join(dir, 'chat-debug.jsonl'), line + '\n');
        // Write to stdout for terminal visibility
        const event = entry.event;
        if (event === 'spawn') {
            process.stdout.write(`[chat] ${ts} → ${entry.runtime} session=${entry.sessionId} prompt=${String(entry.promptLen)} chars\n`);
        }
        else if (event === 'close') {
            const code = entry.exitCode;
            const status = code === 0 ? 'ok' : `exit=${String(code)}`;
            process.stdout.write(`[chat] ${ts} ← ${status} response=${String(entry.responseLen)} chars\n`);
            if (entry.stderr && entry.stderr.length > 0) {
                process.stdout.write(`[chat]   stderr: ${entry.stderr.slice(0, 200)}\n`);
            }
        }
    }
    catch {
        // non-critical — never fail the chat flow for logging
    }
}
const sessions = new Map();
const IDLE_TIMEOUT_MS = 900_000;
const REAP_INTERVAL_MS = 30_000;
let reaperInterval = null;
function startReaper() {
    if (reaperInterval)
        return;
    reaperInterval = setInterval(() => {
        const now = Date.now();
        for (const [id, session] of sessions) {
            const idleMs = now - session.lastActivity.getTime();
            if (idleMs > IDLE_TIMEOUT_MS) {
                kill(id);
                sessions.delete(id);
            }
        }
    }, REAP_INTERVAL_MS);
    reaperInterval.unref();
}
export function generateSessionId() {
    return randomBytes(12).toString('hex');
}
function getAllowedToolsForMode(mode) {
    switch (mode) {
        case 'ask':
            return 'Read,Glob,Grep';
        case 'plan':
            return 'Read,Glob,Grep,Bash(git *),Bash(sf setup-agents *)';
        case 'agent':
        default:
            return 'Edit,Write,Read,Glob,Grep,Bash(sf setup-agents *),Bash(git *)';
    }
}
export function spawnChat(sessionId, message, workspace, runtime = 'claude', context, model, chatMode) {
    const existing = sessions.get(sessionId);
    if (existing) {
        if (existing.process.exitCode !== null) {
            return resumeSession(sessionId, existing, message);
        }
        kill(sessionId);
    }
    const prompt = context ? `${context}\n\n${message}` : message;
    const claudeSessionId = randomUUID();
    chatDebugLog(workspace, {
        event: 'spawn',
        sessionId,
        claudeSessionId,
        runtime,
        messageLen: message.length,
        promptLen: prompt.length,
        hasContext: !!context,
    });
    const sandboxEnv = buildSandboxEnv();
    let proc;
    if (runtime === 'claude') {
        const allowedTools = getAllowedToolsForMode(chatMode);
        const args = ['--print', '--session-id', claudeSessionId, '--output-format', 'stream-json', '--verbose'];
        if (allowedTools) {
            args.push('--allowedTools', allowedTools);
        }
        if (model) {
            args.push('--model', model);
        }
        proc = spawn('claude', args, {
            cwd: workspace,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: sandboxEnv,
        });
    }
    else {
        proc = spawn('codex', ['--print'], {
            cwd: workspace,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: sandboxEnv,
        });
    }
    if (proc.stdin) {
        proc.stdin.write(prompt);
        proc.stdin.end();
    }
    const session = {
        process: proc,
        lastActivity: new Date(),
        workspace,
        runtime,
        claudeSessionId,
        history: [{ role: 'user', content: message }],
        outputBuffer: '',
    };
    let stderrBuffer = '';
    let rawBuffer = '';
    proc.stdout?.on('data', (chunk) => {
        rawBuffer += chunk.toString('utf8');
    });
    proc.stderr?.on('data', (chunk) => {
        stderrBuffer += chunk.toString('utf8');
    });
    proc.on('close', (exitCode) => {
        // Extract text content from stream-json output for history
        const textParts = [];
        for (const line of rawBuffer.split('\n')) {
            if (!line.trim())
                continue;
            try {
                const evt = JSON.parse(line);
                if (evt.type === 'assistant' && evt.message?.content) {
                    for (const block of evt.message.content) {
                        if (block.type === 'text' && block.text)
                            textParts.push(block.text);
                    }
                }
                else if (evt.type === 'result' && evt.result) {
                    if (textParts.length === 0)
                        textParts.push(evt.result);
                }
            }
            catch {
                /* skip non-JSON lines */
            }
        }
        session.outputBuffer = textParts.join('');
        if (session.outputBuffer.length > 0) {
            session.history.push({ role: 'assistant', content: session.outputBuffer });
        }
        chatDebugLog(workspace, {
            event: 'close',
            sessionId,
            exitCode,
            responseLen: session.outputBuffer.length,
            responseTruncated: session.outputBuffer.slice(0, 500),
            stderr: stderrBuffer.slice(0, 500),
        });
    });
    sessions.set(sessionId, session);
    startReaper();
    return session;
}
function resumeSession(sessionId, existing, message) {
    existing.history.push({ role: 'user', content: message });
    const sandboxEnv = buildSandboxEnv();
    let proc;
    if (existing.runtime === 'claude' && existing.claudeSessionId) {
        proc = spawn('claude', [
            '--print',
            '--resume',
            existing.claudeSessionId,
            '--output-format',
            'stream-json',
            '--verbose',
            '--allowedTools',
            'Edit,Write,Bash(git *)',
        ], {
            cwd: existing.workspace,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: sandboxEnv,
        });
    }
    else if (existing.runtime === 'codex') {
        proc = spawn('codex', ['--print'], {
            cwd: existing.workspace,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: sandboxEnv,
        });
        // Codex doesn't support --resume, so pass history as context
        const recentHistory = existing.history.slice(-10);
        const historyPrompt = recentHistory
            .map((h) => `[${h.role === 'user' ? 'Human' : 'Assistant'}]: ${h.content}`)
            .join('\n\n');
        message = `Continue this conversation:\n\n${historyPrompt}`;
    }
    else {
        // Fallback: no claudeSessionId available, pass history as context
        proc = spawn('claude', ['--print', '--allowedTools', 'Edit,Write,Bash(git *)'], {
            cwd: existing.workspace,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: sandboxEnv,
        });
        const recentHistory = existing.history.slice(-10);
        const historyPrompt = recentHistory
            .map((h) => `[${h.role === 'user' ? 'Human' : 'Assistant'}]: ${h.content}`)
            .join('\n\n');
        message = `Continue this conversation:\n\n${historyPrompt}`;
    }
    if (proc.stdin) {
        proc.stdin.write(message);
        proc.stdin.end();
    }
    chatDebugLog(existing.workspace, {
        event: 'resume',
        sessionId,
        claudeSessionId: existing.claudeSessionId,
        runtime: existing.runtime,
        messageLen: message.length,
        historyTurns: existing.history.length,
    });
    existing.process = proc;
    existing.lastActivity = new Date();
    existing.outputBuffer = '';
    proc.stdout?.on('data', (chunk) => {
        existing.outputBuffer += chunk.toString('utf8');
    });
    proc.on('close', () => {
        if (existing.outputBuffer.length > 0) {
            existing.history.push({ role: 'assistant', content: existing.outputBuffer });
        }
    });
    return existing;
}
export function getSession(sessionId) {
    return sessions.get(sessionId);
}
export function getSessionHistory(sessionId) {
    return sessions.get(sessionId)?.history ?? [];
}
export function touchSession(sessionId) {
    const session = sessions.get(sessionId);
    if (session) {
        session.lastActivity = new Date();
    }
}
export function kill(sessionId) {
    const session = sessions.get(sessionId);
    if (!session)
        return false;
    try {
        session.process.kill('SIGTERM');
    }
    catch {
        // process already exited
    }
    sessions.delete(sessionId);
    return true;
}
export function cleanup() {
    for (const [id] of sessions) {
        kill(id);
    }
    if (reaperInterval) {
        clearInterval(reaperInterval);
        reaperInterval = null;
    }
}
export function getActiveSessionCount() {
    return sessions.size;
}
//# sourceMappingURL=chat-process-manager.js.map