#!/usr/bin/env node
import { strict as assert } from "node:assert";
import { spawn } from "node:child_process";
import { join } from "node:path";
import { after, describe, test } from "node:test";
class LSPSmokeTest {
    serverProcess;
    messageId = 0;
    pendingRequests = new Map();
    buffer = "";
    constructor(serverPath) {
        this.serverProcess = spawn(serverPath, ["--stdio"], {
            cwd: process.cwd(),
            stdio: ["pipe", "pipe", "pipe"],
        });
        this.serverProcess.stderr?.on("data", (data) => {
            console.error(`[Server stderr] ${data.toString()}`);
        });
        this.serverProcess.stdout?.on("data", (data) => {
            this.handleServerOutput(data);
        });
        this.serverProcess.on("error", error => {
            console.error(`Failed to start server: ${error.message}`);
        });
        this.serverProcess.on("exit", (code, signal) => {
            if (code !== null && code !== 0) {
                console.error(`Server exited with code ${code}`);
            }
            if (signal) {
                console.error(`Server was killed with signal ${signal}`);
            }
        });
    }
    handleServerOutput(data) {
        this.buffer += data.toString();
        while (this.buffer.length > 0) {
            const headerMatch = this.buffer.match(/Content-Length: (\d+)\r?\n\r?\n/);
            if (!headerMatch) {
                break;
            }
            const contentLength = parseInt(headerMatch[1], 10);
            const headerEnd = headerMatch[0].length;
            const messageStart = headerEnd;
            const messageEnd = messageStart + contentLength;
            if (this.buffer.length < messageEnd) {
                break;
            }
            const jsonStr = this.buffer.substring(messageStart, messageEnd);
            this.buffer = this.buffer.substring(messageEnd);
            try {
                const message = JSON.parse(jsonStr);
                this.handleMessage(message);
            }
            catch (error) {
                console.error(`Failed to parse message: ${error instanceof Error ? error.message : String(error)}`);
                console.error(`Message: ${jsonStr}`);
            }
        }
    }
    handleMessage(message) {
        if (message.id !== undefined && this.pendingRequests.has(message.id)) {
            const pending = this.pendingRequests.get(message.id);
            clearTimeout(pending.timeout);
            if (message.error) {
                pending.reject(new Error(`Server error: ${message.error.message} (code: ${message.error.code})`));
            }
            else {
                pending.resolve(message.result);
            }
            this.pendingRequests.delete(message.id);
        }
    }
    sendMessage(message) {
        const id = ++this.messageId;
        const fullMessage = {
            jsonrpc: "2.0",
            id,
            ...message,
        };
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                this.pendingRequests.delete(id);
                reject(new Error(`Request ${id} (${message.method}) timed out after 5 seconds`));
            }, 5000);
            this.pendingRequests.set(id, { resolve, reject, timeout });
            const jsonStr = JSON.stringify(fullMessage);
            const contentLength = Buffer.byteLength(jsonStr, "utf8");
            const messageStr = `Content-Length: ${contentLength}\r\n\r\n${jsonStr}`;
            this.serverProcess.stdin?.write(messageStr, "utf8", error => {
                if (error) {
                    clearTimeout(timeout);
                    this.pendingRequests.delete(id);
                    reject(error);
                }
            });
        });
    }
    async testInitialize() {
        const result = (await this.sendMessage({
            method: "initialize",
            params: {
                processId: process.pid,
                clientInfo: {
                    name: "smoketest",
                    version: "1.0.0",
                },
                locale: "en",
                rootPath: null,
                rootUri: null,
                capabilities: {},
                workspaceFolders: null,
            },
        }));
        return result;
    }
    async testInitialized() {
        const notification = {
            jsonrpc: "2.0",
            method: "initialized",
        };
        const jsonStr = JSON.stringify(notification);
        const contentLength = Buffer.byteLength(jsonStr, "utf8");
        const messageStr = `Content-Length: ${contentLength}\r\n\r\n${jsonStr}`;
        this.serverProcess.stdin?.write(messageStr, "utf8");
    }
    async shutdown() {
        try {
            await this.sendMessage({
                method: "shutdown",
            });
            const exitMessage = {
                jsonrpc: "2.0",
                method: "exit",
            };
            const jsonStr = JSON.stringify(exitMessage);
            const contentLength = Buffer.byteLength(jsonStr, "utf8");
            const messageStr = `Content-Length: ${contentLength}\r\n\r\n${jsonStr}`;
            this.serverProcess.stdin?.write(messageStr, "utf8");
            await new Promise(resolve => setTimeout(resolve, 500));
            if (!this.serverProcess.killed && this.serverProcess.exitCode === null) {
                this.serverProcess.kill();
            }
        }
        catch (error) {
            this.serverProcess.kill();
            throw error;
        }
    }
}
const serverPath = join(process.cwd(), "dist", "linux-x64", "dot-language-server");
let lspTest;
void describe("LSP smoketest", () => {
    void test("server starts", async () => {
        lspTest = new LSPSmokeTest(serverPath);
        await new Promise(resolve => setTimeout(resolve, 500));
    });
    void test("initialize request", async () => {
        const result = await lspTest.testInitialize();
        assert.ok(result, "Initialize should return a result");
        assert.ok(typeof result === "object", "Result should be an object");
        assert.ok("capabilities" in result, "Result should have capabilities");
    });
    void test("initialized notification", async () => {
        await lspTest.testInitialized();
    });
    void test("shutdown", async () => {
        await lspTest.shutdown();
    });
    after(async () => {
        if (lspTest) {
            try {
                await lspTest.shutdown();
            }
            catch {
            }
        }
    });
});
