import { spawn } from "node:child_process";
import { EventEmitter } from "node:events";
/**
 * LSPConnection - Handles JSON-RPC over stdio for a language server process.
 */
export class LSPConnection extends EventEmitter {
    process;
    idCounter = 0;
    pendingRequests = new Map();
    buffer = Buffer.alloc(0);
    constructor(command, args) {
        super();
        this.process = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
        this.process.on("error", (err) => {
            // If the language server isn't installed (ENOENT), silence the warning
            // as it's expected in many environments. For other errors, log it.
            // Silently swallow — ENOENT is expected; other errors are non-fatal
            void err;
        });
        this.process.stdout?.on("data", (data) => this.handleData(data));
        this.process.stderr?.on("data", () => {
            // Silently swallow LSP stderr — library must not write to consumer's console
        });
        this.process.on("exit", (code) => {
            this.emit("exit", code);
        });
    }
    async sendRequest(method, params) {
        const id = this.idCounter++;
        const message = {
            jsonrpc: "2.0",
            id,
            method,
            params,
        };
        return new Promise((resolve, reject) => {
            this.pendingRequests.set(id, { resolve, reject });
            this.writeMessage(message);
        });
    }
    sendNotification(method, params) {
        const message = {
            jsonrpc: "2.0",
            method,
            params,
        };
        this.writeMessage(message);
    }
    writeMessage(message) {
        const json = JSON.stringify(message);
        const contentLength = Buffer.byteLength(json, "utf-8");
        const header = `Content-Length: ${contentLength}\r\n\r\n`;
        this.process.stdin?.write(header + json);
    }
    handleData(data) {
        this.buffer = Buffer.concat([this.buffer, data]);
        while (true) {
            const content = this.buffer.toString("utf-8");
            const contentLengthMatch = content.match(/Content-Length: (\d+)\r\n\r\n/);
            if (!contentLengthMatch)
                break;
            const headerLength = contentLengthMatch[0].length;
            const contentLength = parseInt(contentLengthMatch[1], 10);
            if (this.buffer.length < headerLength + contentLength)
                break;
            const messageJson = this.buffer.toString("utf-8", headerLength, headerLength + contentLength);
            this.buffer = this.buffer.subarray(headerLength + contentLength);
            try {
                const message = JSON.parse(messageJson);
                this.handleMessage(message);
            }
            catch (_e) {
                // Silently swallow malformed LSP message
            }
        }
    }
    handleMessage(message) {
        if (message.id !== undefined) {
            const pending = this.pendingRequests.get(message.id);
            if (pending) {
                this.pendingRequests.delete(message.id);
                if (message.error) {
                    pending.reject(message.error);
                }
                else {
                    pending.resolve(message.result);
                }
            }
        }
        else {
            this.emit("notification", message);
        }
    }
    terminate() {
        this.process.kill();
    }
}
