"use strict";

const assert = require("node:assert");
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
const { test } = require("node:test");

const root = path.resolve(__dirname, "../../..");
const server = path.join(root, "editors", "vscode", "src", "language-server.cjs");

function rpc(value) {
  const body = Buffer.from(JSON.stringify(value));
  return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`), body]);
}

function parseResponses(output) {
  const responses = [];
  let position = 0;
  while (position < output.length) {
    const headerEnd = output.indexOf(Buffer.from("\r\n\r\n"), position);
    if (headerEnd < 0) break;
    const header = output.subarray(position, headerEnd).toString();
    const length = Number(/Content-Length:\s*(\d+)/i.exec(header)?.[1] || 0);
    const start = headerEnd + 4;
    const end = start + length;
    if (!length || end > output.length) break;
    responses.push(JSON.parse(output.subarray(start, end).toString()));
    position = end;
  }
  return responses;
}

function run(messages) {
  const result = spawnSync(process.execPath, [server], {
    cwd: root,
    input: Buffer.concat(messages.map(rpc)),
    timeout: 10_000,
  });
  assert.equal(result.status, 0, (result.stderr || Buffer.alloc(0)).toString());
  return parseResponses(result.stdout || Buffer.alloc(0));
}

test("language server starts under Node and keeps official typed components clean", () => {
  const files = [
    path.join(root, "packages", "ui", "components", "AuthForm.wrn"),
    path.join(root, "packages", "ui", "components", "FeatureGrid.wrn"),
  ];
  const messages = [
    { jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
    { jsonrpc: "2.0", method: "initialized", params: {} },
  ];
  files.forEach((file, index) => {
    messages.push({
      jsonrpc: "2.0",
      method: "textDocument/didOpen",
      params: {
        textDocument: {
          uri: `file://${file}`,
          languageId: "wrn",
          version: index + 1,
          text: fs.readFileSync(file, "utf8"),
        },
      },
    });
  });
  messages.push({ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} });
  messages.push({ jsonrpc: "2.0", method: "exit", params: {} });

  const responses = run(messages);
  const initialization = responses.find((item) => item.id === 1)?.result;
  assert.equal(initialization?.serverInfo?.version, "0.8.3");
  assert.deepEqual(initialization?.capabilities?.textDocumentSync, {
    openClose: true,
    change: 1,
    save: true,
  });
  const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
  assert.equal(diagnostics.length, 2);
  assert.deepEqual(
    diagnostics.map((item) => item.params.diagnostics),
    [[], []],
  );
});

test("language server defers expensive type analysis while typing and restores it on save", () => {
  const uri = "file:///tmp/wrnexus-save-analysis.wrn";
  const valid = `component SaveAnalysis {
  outputs { save(payload: { id: string }) }
  functions { client function submit(): void { output.save({ id: "ok" }) } }
  view { <button @click="submit()"></button> }
}`;
  const invalid = valid.replace('id: "ok"', "id: 123");
  const responses = run([
    { jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
    { jsonrpc: "2.0", method: "initialized", params: {} },
    {
      jsonrpc: "2.0",
      method: "textDocument/didOpen",
      params: { textDocument: { uri, languageId: "wrn", version: 1, text: valid } },
    },
    {
      jsonrpc: "2.0",
      method: "textDocument/didChange",
      params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: invalid }] },
    },
    { jsonrpc: "2.0", method: "textDocument/didSave", params: { textDocument: { uri } } },
    { jsonrpc: "2.0", id: 2, method: "shutdown", params: {} },
    { jsonrpc: "2.0", method: "exit", params: {} },
  ]);
  const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
  assert.equal(diagnostics.length, 2);
  assert.ok(diagnostics.at(-1).params.diagnostics.some((item) => item.code === "WRN-TYPE-2322"));
});

test("language server formatter preserves code samples and compact sibling markup", () => {
  const source = `page Docs {
view {
<pre><code><span class="muted">// One project, one language</span></code></pre>
<div class="strip"><span>Page</span><b>→</b><span>API</span></div>
}
}\n`;
  const uri = "file:///tmp/wrnexus-docs.wrn";
  const responses = run([
    { jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
    { jsonrpc: "2.0", method: "initialized", params: {} },
    {
      jsonrpc: "2.0",
      method: "textDocument/didOpen",
      params: { textDocument: { uri, languageId: "wrn", version: 1, text: source } },
    },
    {
      jsonrpc: "2.0",
      id: 2,
      method: "textDocument/formatting",
      params: { textDocument: { uri }, options: { tabSize: 2, insertSpaces: true } },
    },
    { jsonrpc: "2.0", id: 3, method: "shutdown", params: {} },
    { jsonrpc: "2.0", method: "exit", params: {} },
  ]);
  const formatted = responses.find((item) => item.id === 2)?.result?.[0]?.newText;
  assert.ok(formatted);
  assert.match(formatted, /\/\/ One project, one language/);
  assert.match(formatted, /<span>Page<\/span><b>→<\/b><span>API<\/span>/);
});

test("language server runs TypeScript diagnostics with workspace standard libraries", () => {
  const uri = "file:///tmp/wrnexus-invalid-state.wrn";
  const source = `component InvalidState {
  outputs { save(payload: { id: string }) }
  functions {
    client function submit(value: string): void {
      output.save({ id: 123 })
    }
  }
  view { <button @click="submit('demo')"></button> }
}
`;
  const responses = run([
    { jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
    { jsonrpc: "2.0", method: "initialized", params: {} },
    {
      jsonrpc: "2.0",
      method: "textDocument/didOpen",
      params: { textDocument: { uri, languageId: "wrn", version: 1, text: source } },
    },
    { jsonrpc: "2.0", id: 2, method: "shutdown", params: {} },
    { jsonrpc: "2.0", method: "exit", params: {} },
  ]);
  const diagnostics = responses.find((item) => item.method === "textDocument/publishDiagnostics")
    ?.params?.diagnostics;
  assert.ok(Array.isArray(diagnostics));
  assert.ok(
    diagnostics.some((diagnostic) => diagnostic.code === "WRN-TYPE-2322"),
    JSON.stringify(diagnostics, null, 2),
  );
});
