/**
 * Claude Code Handler
 *
 * Handles Claude CLI streaming for web clients.
 */
import { WebSocket } from 'ws';
import { spawn, exec } from 'child_process';
import { homedir, tmpdir } from 'os';
import { promises as fs } from 'fs';
import * as path from 'path';
// Active Claude processes
const activeClaudeProcesses = new Map();
// Check Claude CLI availability
function checkClaudeCLI() {
    return new Promise((resolve) => {
        const isWindows = process.platform === 'win32';
        if (isWindows) {
            exec('where claude', { shell: process.env.ComSpec || 'cmd.exe' }, (error, stdout) => {
                if (!error && stdout.trim()) {
                    resolve(true);
                }
                else {
                    exec('where claude-code', { shell: process.env.ComSpec || 'cmd.exe' }, (error2, stdout2) => {
                        resolve(!error2 && !!stdout2.trim());
                    });
                }
            });
        }
        else {
            exec('which claude', (error, stdout) => {
                if (!error && stdout.trim()) {
                    resolve(true);
                }
                else {
                    exec('which claude-code', (error2) => {
                        resolve(!error2);
                    });
                }
            });
        }
    });
}
// Get Claude command
async function getClaudeCommand() {
    const isWindows = process.platform === 'win32';
    return new Promise((resolve) => {
        if (isWindows) {
            exec('where claude', { shell: process.env.ComSpec || 'cmd.exe' }, (error, stdout) => {
                if (!error && stdout.trim()) {
                    resolve(stdout.trim().split('\n')[0].trim());
                }
                else {
                    exec('where claude-code', { shell: process.env.ComSpec || 'cmd.exe' }, (error2, stdout2) => {
                        if (!error2 && stdout2.trim()) {
                            resolve(stdout2.trim().split('\n')[0].trim());
                        }
                        else {
                            resolve('claude');
                        }
                    });
                }
            });
        }
        else {
            exec('which claude', (error, stdout) => {
                if (!error && stdout.trim()) {
                    resolve('claude');
                }
                else {
                    exec('which claude-code', (error2) => {
                        resolve(!error2 ? 'claude-code' : 'claude');
                    });
                }
            });
        }
    });
}
// Clean CLI output
function cleanCliOutput(output) {
    if (!output || !output.includes('\x1B'))
        return output;
    return output
        .replace(/\x1B\[[0-9;]{0,10}m/g, '')
        .replace(/\r\n/g, '\n');
}
// Claude Code handler
export function setupClaudeCodeHandler(ws, sessionId) {
    return (msg) => {
        const { type, id, requestId, method, args } = msg;
        if (method === 'claudeCode:streamMessage') {
            handleStreamMessage(ws, requestId, args);
            return;
        }
        if (method === 'claudeCode:cancelStream') {
            handleCancelStream(ws, id, requestId);
            return;
        }
        if (method === 'claudeCode:checkAvailability') {
            handleCheckAvailability(ws, id);
            return;
        }
        if (method === 'claudeCode:getContext') {
            handleGetContext(ws, id, args);
            return;
        }
        if (method === 'claudeCode:saveImage') {
            handleSaveImage(ws, id, args);
            return;
        }
        ws.send(JSON.stringify({ type: 'error', id, error: `Unknown claudeCode method: ${method}` }));
    };
}
async function handleStreamMessage(ws, requestId, args) {
    const options = args[0] || {};
    const { message, files = [], images = [], cwd } = options;
    const workingDir = cwd || homedir();
    if (!message || !message.trim()) {
        ws.send(JSON.stringify({ type: 'streamError', requestId, error: '消息内容为空' }));
        return;
    }
    const isAvailable = await checkClaudeCLI();
    if (!isAvailable) {
        ws.send(JSON.stringify({
            type: 'streamError',
            requestId,
            error: 'Claude CLI 未安装。请运行 `npm install -g @anthropic-ai/claude-code` 安装。'
        }));
        return;
    }
    const command = await getClaudeCommand();
    const isWindows = process.platform === 'win32';
    // Save base64 images to temp files
    const tempImagePaths = [];
    for (let i = 0; i < images.length; i++) {
        try {
            const base64Data = images[i].replace(/^data:image\/\w+;base64,/, '');
            const buffer = Buffer.from(base64Data, 'base64');
            const ext = images[i].match(/data:image\/(\w+);/)?.[1] || 'png';
            const tempPath = path.join(tmpdir(), `claude-image-${Date.now()}-${i}.${ext}`);
            await fs.writeFile(tempPath, buffer);
            tempImagePaths.push(tempPath);
        }
        catch (err) {
            console.error('[ClaudeCode] Failed to save image:', err);
        }
    }
    // Separate image paths from other files
    const localImagePaths = [];
    const otherFiles = [];
    for (const file of files) {
        const ext = path.extname(file).toLowerCase();
        if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'].includes(ext)) {
            localImagePaths.push(file);
        }
        else {
            otherFiles.push(file);
        }
    }
    // Build message
    let fullMessage = message;
    if (otherFiles.length > 0) {
        fullMessage += '\n\n[系统：用户附加了以下文件路径，请使用 Read 工具读取这些文件]';
        otherFiles.forEach(file => {
            fullMessage += `\n- ${file}`;
        });
    }
    if (localImagePaths.length > 0) {
        fullMessage += '\n\n[系统：用户附加了以下图片路径，请使用 Read 工具查看这些图片]';
        localImagePaths.forEach(imgPath => {
            fullMessage += `\n- ${imgPath}`;
        });
    }
    // Build args
    const claudeArgs = ['-p', fullMessage, '--dangerously-skip-permissions'];
    // Send start event
    ws.send(JSON.stringify({ type: 'streamStart', requestId }));
    // Spawn Claude CLI
    let child;
    if (isWindows) {
        const resolvedCwd = path.resolve(workingDir);
        child = spawn(command, claudeArgs, {
            cwd: resolvedCwd,
            shell: true,
            stdio: ['pipe', 'pipe', 'pipe'],
            env: {
                ...process.env,
                FORCE_COLOR: '0',
                CLAUDE_DISABLE_TTY: '1',
                CI: '1',
            },
        });
    }
    else {
        child = spawn(command, claudeArgs, {
            cwd: workingDir,
            env: {
                ...process.env,
                FORCE_COLOR: '0',
                CLAUDE_DISABLE_TTY: '1',
                CI: '1',
            },
        });
    }
    activeClaudeProcesses.set(requestId, child);
    // Close stdin
    try {
        if (child.stdin && child.stdin.writable) {
            child.stdin.end();
        }
    }
    catch (e) {
        // Ignore
    }
    let hasOutput = false;
    const timeout = setTimeout(() => {
        child.kill('SIGTERM');
        ws.send(JSON.stringify({ type: 'streamError', requestId, error: 'Claude CLI 响应超时（3分钟）' }));
    }, 180000);
    // Handle stdout
    child.stdout.on('data', (data) => {
        const chunk = data.toString();
        hasOutput = true;
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: 'streamData', requestId, data: chunk }));
        }
    });
    // Handle stderr
    let errorOutput = '';
    child.stderr.on('data', (data) => {
        const chunk = data.toString();
        errorOutput += chunk;
        hasOutput = true;
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: 'streamData', requestId, data: chunk }));
        }
    });
    // Handle close
    child.on('close', async (code) => {
        clearTimeout(timeout);
        activeClaudeProcesses.delete(requestId);
        // Cleanup temp images
        for (const tempPath of tempImagePaths) {
            try {
                await fs.unlink(tempPath);
            }
            catch { /* ignore */ }
        }
        await new Promise(r => setTimeout(r, 100));
        if (ws.readyState === WebSocket.OPEN) {
            if (code === 0 || code === null || hasOutput) {
                ws.send(JSON.stringify({ type: 'streamEnd', requestId }));
            }
            else {
                ws.send(JSON.stringify({ type: 'streamError', requestId, error: cleanCliOutput(errorOutput) || 'Claude CLI 异常退出' }));
            }
        }
    });
    // Handle error
    child.on('error', (error) => {
        clearTimeout(timeout);
        activeClaudeProcesses.delete(requestId);
        // Cleanup temp images
        for (const tempPath of tempImagePaths) {
            try {
                fs.unlink(tempPath);
            }
            catch { /* ignore */ }
        }
        if (ws.readyState === WebSocket.OPEN) {
            if (error.message?.includes('ENOENT')) {
                ws.send(JSON.stringify({
                    type: 'streamError',
                    requestId,
                    error: `找不到 Claude CLI 命令: ${command}\n\n请确保已安装并添加到 PATH 环境变量。\n安装命令: npm install -g @anthropic-ai/claude-code`
                }));
            }
            else {
                ws.send(JSON.stringify({ type: 'streamError', requestId, error: `启动 Claude CLI 失败: ${error.message}` }));
            }
        }
    });
}
function handleCancelStream(ws, id, requestId) {
    const child = activeClaudeProcesses.get(requestId);
    if (child) {
        child.kill('SIGTERM');
        activeClaudeProcesses.delete(requestId);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    else {
        ws.send(JSON.stringify({ type: 'response', id, result: { success: false, error: 'No active stream' } }));
    }
}
async function handleCheckAvailability(ws, id) {
    const available = await checkClaudeCLI();
    ws.send(JSON.stringify({ type: 'response', id, result: { available } }));
}
async function handleGetContext(ws, id, args) {
    const cwd = args[0] || homedir();
    const isAvailable = await checkClaudeCLI();
    if (!isAvailable) {
        ws.send(JSON.stringify({
            type: 'response',
            id,
            result: { success: false, error: 'Claude CLI 未安装' }
        }));
        return;
    }
    // Return basic context info
    ws.send(JSON.stringify({
        type: 'response',
        id,
        result: { success: true, context: { cwd } }
    }));
}
async function handleSaveImage(ws, id, args) {
    const options = args[0] || {};
    const { base64Data, filename, cwd } = options;
    const workingDir = cwd || homedir();
    try {
        const imageDir = path.join(workingDir, '.ai-images');
        await fs.mkdir(imageDir, { recursive: true });
        const timestamp = Date.now();
        const safeFilename = `${timestamp}-${filename}`;
        const imagePath = path.join(imageDir, safeFilename);
        const data = base64Data.replace(/^data:image\/\w+;base64,/, '');
        const buffer = Buffer.from(data, 'base64');
        await fs.writeFile(imagePath, buffer);
        const relativePath = path.relative(workingDir, imagePath);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true, path: relativePath, fullPath: imagePath } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'response', id, result: { success: false, error: String(error) } }));
    }
}
//# sourceMappingURL=claude-code.js.map