import { App } from "../app";
import { Log } from "../util/log";
import { LSPClient } from "./client";
import path from "path";

export namespace LSP {
  const log = Log.create({ service: "lsp" });

  const state = App.state(
    "lsp",
    async () => {
      const clients = new Map<string, LSPClient.Info>();

      return {
        clients,
      };
    },
    async (state) => {
      for (const client of state.clients.values()) {
        await client.shutdown();
      }
    },
  );

  export async function file(input: string) {
    const extension = path.parse(input).ext;
    const s = await state();
    const matches = AUTO.filter((x) => x.extensions.includes(extension));
    for (const match of matches) {
      const existing = s.clients.get(match.id);
      if (existing) continue;
      const client = await LSPClient.create({
        cmd: match.command,
        serverID: match.id,
      });
      s.clients.set(match.id, client);
    }
    await run(async (client) => {
      const wait = client.waitForDiagnostics({ path: input });
      await client.notify.open({ path: input });
      return wait;
    });
  }

  export async function diagnostics() {
    const results: Record<string, LSPClient.Diagnostic[]> = {};
    for (const result of await run(async (client) => client.diagnostics)) {
      for (const [path, diagnostics] of result.entries()) {
        const arr = results[path] || [];
        arr.push(...diagnostics);
        results[path] = arr;
      }
    }
    return results;
  }

  async function run<T>(
    input: (client: LSPClient.Info) => Promise<T>,
  ): Promise<T[]> {
    const clients = await state().then((x) => [...x.clients.values()]);
    const tasks = clients.map((x) => input(x));
    return Promise.all(tasks);
  }

  const AUTO: {
    id: string;
    command: string[];
    extensions: string[];
    install?: () => Promise<void>;
  }[] = [
    {
      id: "typescript",
      command: ["bun", "x", "typescript-language-server", "--stdio"],
      extensions: [
        ".ts",
        ".tsx",
        ".js",
        ".jsx",
        ".mjs",
        ".cjs",
        ".mts",
        ".cts",
        ".mtsx",
        ".ctsx",
      ],
    },
    {
      id: "golang",
      command: ["gopls"],
      extensions: [".go"],
    },
  ];

  export namespace Diagnostic {
    export function pretty(diagnostic: LSPClient.Diagnostic) {
      const severityMap = {
        1: "ERROR",
        2: "WARN",
        3: "INFO",
        4: "HINT",
      };

      const severity = severityMap[diagnostic.severity || 1];
      const line = diagnostic.range.start.line + 1;
      const col = diagnostic.range.start.character + 1;

      return `${severity} [${line}:${col}] ${diagnostic.message}`;
    }
  }
}
