import {
  BaseSource,
  GatherArguments,
  OnCompleteDoneArguments,
} from "jsr:@shougo/ddc-vim/source";
import { Item } from "jsr:@shougo/ddc-vim/types";
import * as lambda from "jsr:@denops/std/lambda";

type Snippet = {
  word: string;
  body: string | (() => Promise<string>);
};

let Thunks: (() => Promise<string>)[] = [];

type Never = Record<PropertyKey, never>;

// loader for lsp snippets

export class Source extends BaseSource<Never> {
  override async gather({ denops }: GatherArguments<Never>): Promise<Item[]> {
    Thunks.length = 0;

    const snippets: Snippet[] = [];

    const head = Number(await denops.call("line", ".")) === 1;
    const filetype = String(await denops.eval("&l:filetype"));

    if (head) {
      snippets.push({
        word: "bash",
        body: [
          "#!/usr/bin/env bash",
          "set -euo pipefail",
          "$0",
        ].join("\n"),
      });
    }

    if (filetype === "ai") {
      snippets.push({
        word: "translate",
        body:
          "以下の文章を日本語に翻訳してください。結果だけを出力してください",
      });
    }

    if (filetype === "nix") {
      if (head) {
        snippets.push({
          word: "mkshell",
          body: [
            "{",
            "\tpkgs ? import <nixpkgs> { },",
            "}:",
            "pkgs.mkShell {",
            "\tpackages = with pkgs; [",
            "\t\t$0",
            "\t];",
            "}",
          ].join("\n"),
        });
        snippets.push({
          word: "nixpkgs",
          body: [
            "{",
            "\tpkgs ? import <nixpkgs> { },",
            "}:",
            "$0",
          ].join("\n"),
        });
      }
    }

    if (filetype === "vim") {
      snippets.push({
        word: "autoload_function",
        body: async () => {
          const path = String(await denops.call("expand", "%:p"));
          const match = path.match(/autoload\/(.+?)\.vim$/);
          if (match === null) {
            return "";
          }
          const fn = match[1].replaceAll("/", "#");

          return [
            `function ${fn}#$0() abort`,
            "endfunction",
          ].join("\n");
        },
      });
    }

    Thunks = [];
    return snippets.map(({ word, body }) => {
      let snippet: number | string;
      if (typeof body === "function") {
        Thunks.push(body);
        snippet = Thunks.length - 1;
      } else {
        snippet = body;
      }
      return {
        word,
        user_data: {
          snippet,
        },
      };
    });
  }

  override async onCompleteDone(
    { denops, userData }: OnCompleteDoneArguments<Never>,
  ) {
    let { snippet } = userData as { snippet: number | string };
    if (typeof snippet === "number") {
      snippet = await Thunks[snippet]();
    }
    if (snippet === "") {
      return;
    }
    const lam = lambda.add(denops, async () => {
      try {
        if (denops.meta.host === "vim") {
          await denops.call("vsnip#anonymous", snippet);
        } else {
          await denops.call("luaeval", "vim.snippet.expand(_A)", snippet);
        }
      } finally {
        lam.dispose();
      }
    });
    await denops.eval(
      `feedkeys("\\<C-w>\\<Cmd>call ${lam.notify()}\\<CR>", "n")`,
    );
  }

  params(): Never {
    return {};
  }
}