/**
 * JSON-RPC Server for code-buddy
 *
 * Standalone server that communicates via stdin/stdout.
 * Can be used by any JSON-RPC client (FileCommander, VS Code, etc.)
 */
import * as readline from 'readline';
import { ErrorCodes, createResponse, createErrorResponse, createNotification, isRequest, } from './protocol.js';
// Import code-buddy internals (lazy loaded to reduce startup time)
let codebuddyClient = null;
let fcsRuntime = null;
export class JsonRpcServer {
    rl = null;
    initialized = false;
    clientName = 'unknown';
    clientVersion = 'unknown';
    conversationHistory = new Map();
    contextFiles = new Map();
    options;
    constructor(options = {}) {
        this.options = options;
    }
    /**
     * Start the server and listen for requests on stdin
     */
    async start() {
        this.rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
            terminal: false,
        });
        this.log('info', 'JSON-RPC server starting...');
        try {
            for await (const line of this.rl) {
                if (!line.trim())
                    continue;
                try {
                    const message = JSON.parse(line);
                    await this.handleMessage(message);
                }
                catch (error) {
                    this.sendError(null, ErrorCodes.PARSE_ERROR, `Failed to parse JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
                }
            }
        }
        finally {
            this.stop();
        }
        this.log('info', 'JSON-RPC server stopped');
    }
    /**
     * Stop the server
     */
    stop() {
        if (this.rl) {
            this.rl.close();
            this.rl = null;
        }
    }
    /**
     * Handle incoming message
     */
    async handleMessage(message) {
        if (!isRequest(message)) {
            this.sendError(null, ErrorCodes.INVALID_REQUEST, 'Invalid JSON-RPC request');
            return;
        }
        const request = message;
        this.log('debug', `Received: ${request.method}`);
        try {
            const result = await this.dispatch(request);
            this.send(createResponse(request.id, result));
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            this.sendError(request.id, ErrorCodes.INTERNAL_ERROR, errorMessage);
        }
    }
    /**
     * Validate required string field in params
     */
    requireString(params, field, method) {
        const value = params[field];
        if (typeof value !== 'string' || value.trim().length === 0) {
            throw new Error(`${method}: '${field}' is required and must be a non-empty string`);
        }
        return value;
    }
    /**
     * Cast validated params to a specific type.
     * Only call after requireString/field validation confirms required fields.
     */
    checkedParams(params) {
        return params;
    }
    /**
     * Dispatch request to appropriate handler
     */
    async dispatch(request) {
        const params = (request.params || {});
        // Initialize is always allowed
        if (request.method === 'initialize') {
            this.requireString(params, 'clientName', 'initialize');
            this.requireString(params, 'clientVersion', 'initialize');
            return this.handleInitialize(this.checkedParams(params));
        }
        // Shutdown is always allowed
        if (request.method === 'shutdown') {
            return this.handleShutdown();
        }
        // Other methods require initialization
        if (!this.initialized) {
            throw new Error('Server not initialized. Call "initialize" first.');
        }
        switch (request.method) {
            // AI methods
            case 'ai/complete':
                this.requireString(params, 'prompt', 'ai/complete');
                return this.handleAiComplete(this.checkedParams(params));
            case 'ai/chat':
                this.requireString(params, 'message', 'ai/chat');
                return this.handleAiChat(this.checkedParams(params));
            case 'ai/clearHistory':
                return this.handleAiClearHistory(params);
            // Tool methods
            case 'tools/list':
                return this.handleToolsList();
            case 'tools/call':
                this.requireString(params, 'name', 'tools/call');
                if (typeof params.arguments !== 'object' || params.arguments === null || Array.isArray(params.arguments)) {
                    throw new Error("tools/call: 'arguments' is required and must be an object");
                }
                return this.handleToolsCall(this.checkedParams(params));
            // FCS methods
            case 'fcs/execute':
                this.requireString(params, 'script', 'fcs/execute');
                return this.handleFcsExecute(this.checkedParams(params));
            case 'fcs/parse':
                this.requireString(params, 'script', 'fcs/parse');
                return this.handleFcsParse(this.checkedParams(params));
            // Context methods
            case 'context/add':
                if (!Array.isArray(params.files)) {
                    throw new Error("context/add: 'files' is required and must be an array");
                }
                return this.handleContextAdd(this.checkedParams(params));
            case 'context/list':
                return this.handleContextList();
            case 'context/clear':
                return this.handleContextClear();
            // Git methods
            case 'git/status':
                return this.handleGitStatus();
            case 'git/diff':
                return this.handleGitDiff(params);
            default:
                throw new Error(`Method not found: ${request.method}`);
        }
    }
    // ============================================
    // Method Handlers
    // ============================================
    async handleInitialize(params) {
        this.clientName = params.clientName;
        this.clientVersion = params.clientVersion;
        this.initialized = true;
        this.log('info', `Client connected: ${this.clientName} v${this.clientVersion}`);
        const capabilities = {
            version: '1.0.0',
            methods: [
                'initialize', 'shutdown',
                'ai/complete', 'ai/chat', 'ai/clearHistory',
                'tools/list', 'tools/call',
                'fcs/execute', 'fcs/parse',
                'context/add', 'context/list', 'context/clear',
                'git/status', 'git/diff',
            ],
            features: {
                ai: true,
                tools: true,
                fcs: true,
                streaming: false, // Not yet implemented
            },
        };
        return {
            serverName: 'code-buddy',
            serverVersion: '1.0.0',
            capabilities,
        };
    }
    async handleShutdown() {
        this.log('info', 'Shutdown requested');
        setImmediate(() => this.stop());
        return null;
    }
    async handleAiComplete(params) {
        // Lazy load grok client
        if (!codebuddyClient) {
            const { CodeBuddyClient } = await import('../../codebuddy/index.js');
            const apiKey = this.options.apiKey || process.env.GROK_API_KEY || '';
            codebuddyClient = new CodeBuddyClient(apiKey);
        }
        const client = codebuddyClient;
        const systemPrompt = params.context?.language
            ? `You are a code completion assistant for ${params.context.language}. Provide only the code to complete, no explanations.`
            : 'You are a helpful AI assistant.';
        const userPrompt = params.context?.prefix
            ? `Complete this code:\n\`\`\`${params.context.language || ''}\n${params.context.prefix}[CURSOR]${params.context.suffix || ''}\n\`\`\`\n\nProvide only the code to insert at [CURSOR]:`
            : params.prompt;
        const messages = [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: userPrompt },
        ];
        const response = await client.chat(messages);
        const content = response.choices?.[0]?.message?.content || '';
        return {
            text: content,
            model: params.options?.model || 'grok-2',
            usage: response.usage ? {
                promptTokens: response.usage.prompt_tokens,
                completionTokens: response.usage.completion_tokens,
                totalTokens: response.usage.total_tokens,
            } : undefined,
        };
    }
    async handleAiChat(params) {
        const conversationId = params.conversationId || `conv_${Date.now()}`;
        // Get or create conversation history
        if (!this.conversationHistory.has(conversationId)) {
            this.conversationHistory.set(conversationId, []);
        }
        const history = this.conversationHistory.get(conversationId);
        // Add user message
        history.push({ role: 'user', content: params.message });
        // Lazy load grok client
        if (!codebuddyClient) {
            const { CodeBuddyClient } = await import('../../codebuddy/index.js');
            const apiKey = this.options.apiKey || process.env.GROK_API_KEY || '';
            codebuddyClient = new CodeBuddyClient(apiKey);
        }
        const client = codebuddyClient;
        // Build context from files
        let contextPrompt = '';
        if (params.context && params.context.length > 0) {
            contextPrompt = 'Context files:\n';
            for (const file of params.context) {
                const fileData = this.contextFiles.get(file);
                if (fileData) {
                    contextPrompt += `\n--- ${file} ---\n${fileData.content}\n`;
                }
            }
            contextPrompt += '\n---\n\n';
        }
        // Call API
        const messages = contextPrompt
            ? [{ role: 'system', content: contextPrompt }, ...history]
            : history;
        const response = await client.chat(messages);
        const content = response.choices?.[0]?.message?.content || '';
        // Add assistant response to history
        history.push({ role: 'assistant', content });
        return {
            response: content,
            conversationId,
            model: 'grok-2',
        };
    }
    async handleAiClearHistory(params) {
        if (params.conversationId) {
            this.conversationHistory.delete(params.conversationId);
        }
        else {
            this.conversationHistory.clear();
        }
        return { cleared: true };
    }
    async handleToolsList() {
        const tools = [
            {
                name: 'read_file',
                description: 'Read the contents of a file',
                parameters: {
                    type: 'object',
                    properties: {
                        path: { type: 'string', description: 'Path to the file' },
                    },
                    required: ['path'],
                },
            },
            {
                name: 'write_file',
                description: 'Write content to a file',
                parameters: {
                    type: 'object',
                    properties: {
                        path: { type: 'string', description: 'Path to the file' },
                        content: { type: 'string', description: 'Content to write' },
                    },
                    required: ['path', 'content'],
                },
            },
            {
                name: 'list_directory',
                description: 'List files in a directory',
                parameters: {
                    type: 'object',
                    properties: {
                        path: { type: 'string', description: 'Path to the directory' },
                        pattern: { type: 'string', description: 'Glob pattern to filter' },
                    },
                    required: ['path'],
                },
            },
            {
                name: 'search_files',
                description: 'Search for files matching a pattern',
                parameters: {
                    type: 'object',
                    properties: {
                        pattern: { type: 'string', description: 'Search pattern (regex)' },
                        path: { type: 'string', description: 'Directory to search in' },
                    },
                    required: ['pattern'],
                },
            },
            {
                name: 'execute_command',
                description: 'Execute a shell command',
                parameters: {
                    type: 'object',
                    properties: {
                        command: { type: 'string', description: 'Command to execute' },
                        cwd: { type: 'string', description: 'Working directory' },
                    },
                    required: ['command'],
                },
            },
        ];
        return { tools };
    }
    async handleToolsCall(params) {
        const fs = await import('fs/promises');
        const path = await import('path');
        const { exec } = await import('child_process');
        const { promisify } = await import('util');
        const execAsync = promisify(exec);
        try {
            switch (params.name) {
                case 'read_file': {
                    const filePath = params.arguments.path;
                    const content = await fs.readFile(filePath, 'utf-8');
                    return { success: true, result: content };
                }
                case 'write_file': {
                    const filePath = params.arguments.path;
                    const content = params.arguments.content;
                    await fs.writeFile(filePath, content, 'utf-8');
                    return { success: true, result: { written: true, path: filePath } };
                }
                case 'list_directory': {
                    const dirPath = params.arguments.path;
                    const entries = await fs.readdir(dirPath, { withFileTypes: true });
                    const result = entries.map(e => ({
                        name: e.name,
                        isDirectory: e.isDirectory(),
                        path: path.join(dirPath, e.name),
                    }));
                    return { success: true, result };
                }
                case 'search_files': {
                    const pattern = params.arguments.pattern;
                    const searchPath = params.arguments.path || '.';
                    const { stdout } = await execAsync(`grep -r -l "${pattern}" "${searchPath}" 2>/dev/null || true`);
                    const files = stdout.trim().split('\n').filter(Boolean);
                    return { success: true, result: files };
                }
                case 'execute_command': {
                    const command = params.arguments.command;
                    const cwd = params.arguments.cwd || this.options.workdir || process.cwd();
                    const { stdout, stderr } = await execAsync(command, { cwd });
                    return { success: true, result: { stdout, stderr } };
                }
                default:
                    return { success: false, error: `Unknown tool: ${params.name}` };
            }
        }
        catch (error) {
            return {
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error',
            };
        }
    }
    async handleFcsExecute(params) {
        // Lazy load FCS runtime
        if (!fcsRuntime) {
            const { FCSRuntime } = await import('../../scripting/runtime.js');
            fcsRuntime = FCSRuntime;
        }
        const Runtime = fcsRuntime;
        const { tokenize } = await import('../../scripting/lexer.js');
        const { parse } = await import('../../scripting/parser.js');
        const startTime = Date.now();
        try {
            const tokens = tokenize(params.script);
            const ast = parse(tokens);
            const runtime = new Runtime({
                workdir: params.config?.workdir || this.options.workdir || process.cwd(),
                timeout: params.config?.timeout || 30000,
                dryRun: params.config?.dryRun || false,
            });
            const result = await runtime.execute(ast);
            return {
                success: result.success,
                output: result.output,
                returnValue: result.returnValue,
                error: result.error,
                duration: Date.now() - startTime,
            };
        }
        catch (error) {
            return {
                success: false,
                output: [],
                error: error instanceof Error ? error.message : 'Unknown error',
                duration: Date.now() - startTime,
            };
        }
    }
    async handleFcsParse(params) {
        const { tokenize } = await import('../../scripting/lexer.js');
        const { parse } = await import('../../scripting/parser.js');
        try {
            const tokens = tokenize(params.script);
            const ast = parse(tokens);
            return { valid: true, ast };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : 'Unknown error';
            // Try to extract line/column from error message
            const match = message.match(/line (\d+), column (\d+)/i);
            return {
                valid: false,
                errors: [{
                        line: match ? parseInt(match[1]) : 1,
                        column: match ? parseInt(match[2]) : 1,
                        message,
                    }],
            };
        }
    }
    async handleContextAdd(params) {
        const fs = await import('fs/promises');
        const added = [];
        const failed = [];
        let totalSize = 0;
        for (const file of params.files) {
            try {
                const content = await fs.readFile(file, 'utf-8');
                const size = Buffer.byteLength(content, 'utf-8');
                this.contextFiles.set(file, { content, size });
                added.push(file);
                totalSize += size;
            }
            catch (error) {
                failed.push({
                    file,
                    reason: error instanceof Error ? error.message : 'Unknown error',
                });
            }
        }
        return { added, failed, totalSize };
    }
    async handleContextList() {
        const files = [];
        let totalSize = 0;
        for (const [path, data] of this.contextFiles) {
            files.push({ path, size: data.size });
            totalSize += data.size;
        }
        return { files, totalSize };
    }
    async handleContextClear() {
        const count = this.contextFiles.size;
        this.contextFiles.clear();
        return { cleared: count };
    }
    async handleGitStatus() {
        const { exec } = await import('child_process');
        const { promisify } = await import('util');
        const execAsync = promisify(exec);
        const cwd = this.options.workdir || process.cwd();
        const { stdout: status } = await execAsync('git status --porcelain', { cwd });
        const { stdout: branch } = await execAsync('git branch --show-current', { cwd });
        return {
            status: status.trim(),
            branch: branch.trim(),
        };
    }
    async handleGitDiff(params) {
        const { exec } = await import('child_process');
        const { promisify } = await import('util');
        const execAsync = promisify(exec);
        const cwd = this.options.workdir || process.cwd();
        const cmd = params.staged ? 'git diff --staged' : 'git diff';
        const { stdout } = await execAsync(cmd, { cwd });
        return { diff: stdout };
    }
    // ============================================
    // Utility Methods
    // ============================================
    send(message) {
        // console.log is intentional here - JSON-RPC protocol uses stdout
        console.log(JSON.stringify(message));
    }
    sendError(id, code, message, data) {
        this.send(createErrorResponse(id ?? 0, code, message, data));
    }
    sendNotification(method, params) {
        this.send(createNotification(method, params));
    }
    log(level, message) {
        if (this.options.verbose || level !== 'debug') {
            this.sendNotification('$/log', {
                level,
                message,
                timestamp: new Date().toISOString(),
            });
        }
    }
}
// Export factory function
export function createJsonRpcServer(options) {
    return new JsonRpcServer(options);
}
//# sourceMappingURL=server.js.map