import { group, register } from "../../../denops/@vimrc/lib/lambda/autocmd.ts";
import { Denops } from "jsr:@denops/std";
import * as autocmd from "jsr:@denops/std/autocmd";
import * as lambda from "jsr:@denops/std/lambda";
import * as mapping from "jsr:@denops/std/mapping";
import * as option from "jsr:@denops/std/option";
import { assert, is } from "jsr:@core/unknownutil";
import { Params as DduUiFFParams } from "jsr:@shougo/ddu-ui-ff";
import { BaseConfig, ConfigArguments } from "jsr:@shougo/ddu-vim/config";
import {
  ActionFlags,
  SourceOptions,
  UiActionArguments,
} from "jsr:@shougo/ddu-vim/types";
import { BaseParams } from "jsr:@shougo/ddu-vim/types";

type Filter = {
  matchers: SourceOptions["matchers"];
  sorters: SourceOptions["sorters"];
  converters: SourceOptions["converters"];
};

function updateFilter(args: UiActionArguments<BaseParams>, filter: Filter) {
  const sources = args.options.sources.map((s) => {
    if (is.String(s)) {
      s = { name: s };
    }
    return {
      ...s,
      options: {
        ...s.options,
        ...filter,
      },
    };
  });
  // args.ddu.updateOptions({
  //   sources,
  // });
}

const augroup = "vimrc#ddu-ui-ff";

async function onColorScheme(args: ConfigArguments) {
  const hlbg = false;
  const highlights: DduUiFFParams["highlights"] = {};
  if (await args.denops.eval("&background ==# 'light'")) {
    if (hlbg) {
      await args.denops.cmd("hi DduEnd guibg=#e0e0ff guifg=#e0e0ff");
      await args.denops.cmd("hi DduFloat guibg=#e0e0ff guifg=#6060ff");
    } else {
      await args.denops.cmd("hi DduEnd guifg=#e0e0ff");
      await args.denops.cmd("hi DduFloat guifg=#6060ff");
    }
    await args.denops.cmd("hi DduNormal guifg=#6060ff");
    await args.denops.cmd("hi DduBorder guibg=#f0f0ff guifg=#6060ff");
    await args.denops.cmd(
      "hi DduMatch ctermfg=205 ctermbg=225 guifg=#ff60c0 guibg=#ffd0ff cterm=NONE gui=NONE",
    );
    await args.denops.cmd(
      "hi DduCursorLine ctermfg=205 ctermbg=225 guifg=#ff6060 guibg=#ffe8e8 cterm=NONE gui=NONE",
    );
    highlights.floating = "DduFloat,EndOfBuffer:DduEnd";
    highlights.floatingCursorLine = "DduCursorLine";
  } else {
    await args.denops.cmd("hi def link DduNormal Normal");
    await args.denops.cmd("hi def link DduMatch Search");
    highlights.floating = "Normal,DduBorder:Normal,DduMatch:Search";
  }
  args.contextBuilder.patchGlobal({
    uiParams: {
      ff: {
        highlights,
      },
    },
  });
}

async function calculateUiSize(
  denops: Denops,
): Promise<[x: number, y: number, width: number, height: number]> {
  const columns = await option.columns.get(denops);
  const lines = await option.lines.get(denops);
  // -2は枠の分
  const width = columns - 8 - 2;
  const height = lines - 4 - 2;
  const x = 4;
  const y = 2;
  return [x, y, width, height];
}

async function setUiSize(args: ConfigArguments) {
  if (args.denops.meta.host === "vim") {
    args.contextBuilder.patchGlobal({
      uiParams: {
        ff: {
          previewWidth: Math.floor((await option.columns.get(args.denops)) / 2),
        },
      },
    });
  } else {
    const [winCol, winRow, winWidth, winHeight] = await calculateUiSize(
      args.denops,
    );
    const halfWidth = Math.floor(winWidth / 2);
    const pileBorder = true; // 外枠と重ねるか否か
    args.contextBuilder.patchGlobal({
      uiParams: {
        ff: {
          winCol,
          winRow,
          winWidth,
          winHeight,
          // fzf-previewやtelescopeみたいなpreviewの出し方をする
          previewWidth: halfWidth,
          previewCol: winCol + winWidth - halfWidth - (pileBorder ? 0 : 1),
          previewRow: winRow + (pileBorder ? 0 : 1),
          previewHeight: winHeight - (pileBorder ? 0 : 2),
        } satisfies Partial<DduUiFFParams>,
      },
    });
  }
}

// patchLocalしてるnameをマッピングテーブル用の定義に直すためのテーブル
// X<ddu-ui-ff-aliases>
// L<ddu-locals>
const aliases: Record<string, string> = {
  git_diff: "file:git_diff",
  line: "file",
  mrr: "file",
  mru: "file",
  mrw: "file",
};

async function setupFileTypeAutocmd(args: ConfigArguments) {
  const { denops } = args;
  const opt: mapping.MapOptions = {
    buffer: true,
    nowait: true,
  };
  const nno: mapping.MapOptions = {
    ...opt,
    mode: ["n"],
  };
  const action = (name: string, params?: unknown) => {
    return `<Cmd>call ddu#ui#do_action('${name}'${
      params != null ? ", " + JSON.stringify(params) : ""
    })<CR>`;
  };
  const itemAction = (name: string, params: unknown = {}) => {
    return action("itemAction", { name, params });
  };
  const actionL = (name: string, params: unknown = {}) => {
    return (denops: Denops) => {
      return denops.call("ddu#ui#do_action", name, params);
    };
  };
  const itemActionL = (name: string, params: unknown = {}) => {
    return actionL("itemAction", { name, params });
  };
  const setupTable: Record<string, lambda.Fn> = {
    _: async () => {
      await mapping.map(denops, "<CR>", action("itemAction"), nno);
      await mapping.map(denops, "a", action("inputAction"), nno);
      await mapping.map(denops, "A", action("toggleAutoAction"), nno);
      await mapping.map(
        denops,
        "i",
        "<Cmd>call p#ddu#ff#filter()<CR>",
        nno,
      );
      await mapping.map(denops, "q", action("quit"), nno);
      await mapping.map(
        denops,
        "s",
        action("toggleSelectItem") + action("cursorNext"),
        nno,
      );
      await mapping.map(
        denops,
        "x",
        action("toggleSelectItem") + action("cursorPrevious"),
        nno,
      );
      await mapping.map(denops, "uk", action("useKensaku"), nno);
    },
    git_diff: async () => {
      const p = lambda.add(denops, async () => {
        const view = await denops.call("winsaveview");
        await itemActionL("applyPatch")(denops);
        await denops.call("winrestview", view);
      });
      await mapping.map(denops, "p", `<Cmd>call ${p.request()}<CR>`, nno);
    },
    git_status: async () => {
      await mapping.map(denops, "c", itemAction("commit"), nno);
      await mapping.map(denops, "d", itemAction("diff"), nno);
      await mapping.map(denops, "h", itemAction("add"), nno);
      await mapping.map(denops, "l", itemAction("reset"), nno);
    },
  };
  const ddu_ff = register(denops, async (name: unknown) => {
    await setupTable["_"]?.();
    assert(name, is.String);
    const names = (aliases[name] ?? name).split(/:/g);
    for (const name of names) {
      await setupTable[name]?.();
    }
  }, { args: "b:ddu_ui_name" });
  await autocmd.group(denops, augroup, (helper) => {
    helper.define(
      "FileType",
      "ddu-ff",
      ddu_ff,
    );
  });
}

export class Config extends BaseConfig {
  async config(args: ConfigArguments) {
    // +-------------------+
    // | ASCII罫線はいいぞ |
    // +-------------------+
    const border = ["+", "-", "+", "|", "+", "-", "+", "|"]
      .map((c) => [c, "DduBorder"]);
    const nvim = args.denops.meta.host === "nvim";
    const floating = nvim;
    args.contextBuilder.patchGlobal({
      ui: "ff",
      uiParams: {
        ff: {
          autoAction: {
            name: "preview",
          },
          floatingBorder: border as any, // そのうち直す
          previewFloating: floating,
          previewFloatingBorder: border as any, // そのうち直す
          previewFloatingZindex: 100,
          previewSplit: "vertical",
          split: floating ? "floating" : "tab",
        } satisfies Partial<DduUiFFParams>,
      },
      uiOptions: {
        ff: {
          actions: {
            useKensaku: async (args) => {
              // L<dpp-lazy-kensaku_vim>
              await args.denops.call("dpp#source", ["vim-kensaku"]);
              updateFilter(args, {
                matchers: ["matcher_kensaku"],
                sorters: [],
                converters: [],
              });
              await args.denops.cmd("echomsg 'change to kensaku matcher'");
              return ActionFlags.Persist;
            },
          },
        },
      },
    });

    await autocmd.group(args.denops, augroup, (helper) => {
      helper.remove("*");
    });

    await group(args.denops, augroup, (helper) => {
      helper.define(
        "ColorScheme",
        "*",
        () => onColorScheme(args),
      );
      // previewとかfloatwinのサイズをセットするやつ
      helper.define(
        "VimResized",
        "*",
        () => setUiSize(args),
        { async: true },
      );
    });
    await onColorScheme(args);
    await setUiSize(args);
    await setupFileTypeAutocmd(args);
  }
}