// import { Params as BufParams } from "../../../denops/@ddc-sources/buf.ts";
import { Params as ListParams } from "../../../denops/@ddc-sources/list.ts";
import { map } from "../../../denops/@vimrc/lib/lambda/map.ts";
import { Denops } from "jsr:@denops/std";
import * as mapping from "jsr:@denops/std/mapping";
import * as option from "jsr:@denops/std/option";
import { BaseConfig, ConfigArguments } from "jsr:@shougo/ddc-vim/config";
import { DdcOptions } from "jsr:@shougo/ddc-vim/types";

/*
X<ddc-config-manual_complete>
欲しい機能
  Cを押すと補完ソース選びの補完ソースに切り替わり、マニュアル補完が走る
    確定すると一時的にソースが切り替わりマニュアル補完が走る
  Lを押すとマニュアル補完
  Rを押すかノーマルモードに抜けると解除
    ノーマルモード抜けは未来の課題とする
    意外と面倒なのを忘れていた
*/

async function echomsg(denops: Denops, msg: string) {
  await denops.cmd("echomsg msg", { msg }).catch(console.log);
}

const filters = {
  sorter_fzf: {
    matchers: [],
    sorters: ["sorter_fzf"],
    converters: [],
  },
};

const configSet: Record<
  string,
  (denops: Denops) => Promise<Partial<DdcOptions>>
> = {
  //  "buf:filetype": () =>
  //    Promise.resolve({
  //      sources: [{
  //        name: "buf",
  //        params: {
  //          finder: async (denops) => {
  //            const bufnrs = await denops.eval(
  //              `getbufinfo()->filter('v:val.bufnr != bufnr() && !empty(v:val.windows) && getbufvar(v:val.bufnr, "&filetype") ==# &l:filetype')->map('v:val.bufnr')`,
  //            );
  //            return u.ensure(bufnrs, u.isArrayOf(u.isNumber));
  //          },
  //        } satisfies BufParams,
  //      }],
  //    }),
  file: () =>
    Promise.resolve({
      sources: ["file"],
    }),
  lsp: async (denops) => {
    const lspEngine = String(
      await denops.eval(
        "get(b:, 'ddu_source_lsp_clientName', get(g:, 'ddu_source_lsp_clientName', 'nvim-lsp'))",
      ),
    );
    return {
      sources: [{
        name: "lsp",
        options: {
          minAutoCompleteLength: 1,
        },
        params: {
          lspEngine,
        },
      }],
    };
  },
  snippet: async (denops) => {
    return {
      sources: ["lsnippet"],
    };
  },
  yank: () =>
    Promise.resolve({
      sources: [{
        name: "yank",
        options: {
          ...filters.sorter_fzf,
        },
      }],
    }),
};

async function setConfig(args: ConfigArguments, name: string) {
  const bufnr = Number(await args.denops.call("bufnr"));
  const set = configSet[name];
  if (set == null) {
    await resetConfig(args);
    return;
  }
  args.contextBuilder.setBuffer(bufnr, await set(args.denops));
  args.contextBuilder.patchBuffer(bufnr, {
    specialBufferCompletion: true,
  });
  await args.denops.call("ddc#map#manual_complete");
}

async function resetConfig(args: ConfigArguments) {
  const config = args.contextBuilder.getBuffer();
  for (const _bufnr of Object.keys(config)) {
    const bufnr = Number(_bufnr);
    delete config[bufnr];
  }
  await args.denops.call("ddc#hide");
}

const configMap: Record<string, string> = {
  F: "file",
  S: "lsp",
  T: "snippet",
  Y: "yank",
  y: "yank",
};

// configSetを指定させてマッピングする
async function inputConfigSet(args: ConfigArguments) {
  const bufnr = Number(await args.denops.call("bufnr"));
  args.contextBuilder.setBuffer(bufnr, {
    cmdlineSources: [{
      name: "list",
      options: {
        minAutoCompleteLength: 0,
      },
      params: {
        candidates: Object.keys(configSet),
      } satisfies ListParams,
    }],
    specialBufferCompletion: true,
  });
  const ve = await option.virtualedit.getLocal(args.denops);
  try {
    // 末尾にカーソルあるとinput後に動くんで上書き
    await option.virtualedit.setLocal(args.denops, "onemore");
    const name = String(await args.denops.call("input", "name?"));
    args.contextBuilder.setBuffer(bufnr, { ui: "none" });
    const key = String(await args.denops.call("input", "key?"));
    if (configSet[name] == null) {
      delete configMap[key];
      await resetConfig(args);
    } else {
      configMap[key] = name;
      await setConfig(args, key);
    }
  } catch (e) {
    console.log(e);
    await resetConfig(args);
  } finally {
    await option.virtualedit.setLocal(args.denops, ve);
  }
}

export class Config extends BaseConfig {
  override async config(args: ConfigArguments): Promise<void> {
    const ino: mapping.MapOptions = {
      mode: "i",
      noremap: true,
      nowait: true,
    };
    // XXでconfig指定、X{key}で使えるようにする
    await map(args.denops, "X", async () => {
      const key = String(await args.denops.call("getcharstr"));
      if (key == "X") {
        await inputConfigSet(args);
      }
      const name = configMap[key];
      if (name != null) {
        await setConfig(args, name);
        await echomsg(args.denops, `set to ${name}`);
      }
    }, ino);
    await map(args.denops, "L", async () => {
      await args.denops.call("ddc#map#manual_complete");
    }, ino);
    await map(args.denops, "R", async () => {
      await resetConfig(args);
      await echomsg(args.denops, "restore buffer config");
    }, ino);
  }
}