import { createInterface } from 'node:readline';
import type { Readable, Writable } from 'node:stream';

interface RpcRequest {
  jsonrpc: '2.0';
  id: number;
  method: string;
  params?: unknown;
}

interface RpcResponse {
  jsonrpc: '2.0';
  id: number;
  result?: unknown;
  error?: { code: number; message: string };
}

export class LspClient {
  private nextId = 1;
  private pending = new Map<number, { resolve: (v: RpcResponse) => void; reject: (e: Error) => void }>();
  private buffer = '';

  constructor(
    private stdin: Writable,
    private stdout: Readable,
  ) {
    const rl = createInterface({ input: stdout, crlfDelay: Infinity });
    rl.on('line', (line) => this.handleLine(line));
  }

  private handleLine(line: string): void {
    this.buffer += line + '\n';
    const match = this.buffer.match(/Content-Length: (\d+)\r?\n\r?\n/);
    if (!match || !match[1]) return;
    const len = parseInt(match[1], 10);
    const headerEnd = match.index! + match[0].length;
    const body = this.buffer.slice(headerEnd, headerEnd + len);
    if (body.length < len) return;
    this.buffer = this.buffer.slice(headerEnd + len);
    try {
      const msg: RpcResponse = JSON.parse(body);
      const cb = this.pending.get(msg.id);
      if (cb) {
        this.pending.delete(msg.id);
        cb.resolve(msg);
      }
    } catch {
      // Malformed JSON, ignore
    }
  }

  async request(method: string, params?: unknown): Promise<unknown> {
    const id = this.nextId++;
    const req: RpcRequest = { jsonrpc: '2.0', id, method, params };
    const body = JSON.stringify(req);
    const header = `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n`;

    return new Promise((resolve, reject) => {
      this.pending.set(id, {
        resolve: (resp) => {
          if (resp.error) reject(new Error(resp.error.message));
          else resolve(resp.result);
        },
        reject,
      });
      this.stdin.write(header + body);
    });
  }

  async notify(method: string, params?: unknown): Promise<void> {
    const body = JSON.stringify({ jsonrpc: '2.0', method, params });
    const header = `Content-Length: ${Buffer.byteLength(body, 'utf8')}\r\n\r\n`;
    this.stdin.write(header + body);
  }
}
