{ config, lib, pkgs, ... }:

with lib;

{
  options = {
    neovim = {

      tabWidth = mkOption {
        type = types.int;
        description = "Width of tabes in the editor.";
        default = 4;
      };
      expandTab = mkOption {
        type = types.bool;
        description = "If tabs should be expanded to spaces.";
        default = false;
      };

      keys = {
        leader = mkOption {
          type = types.str;
          description = "NeoVIM leader key";
          default = " ";
        };
        noh = mkOption {
          type = types.str;
          description = "Keybind to remove active hilighted content.";
          default = "<leader>h";
        };

        menus = {
          # file browser
          browser = mkOption {
            type = types.str;
            description = "Keybind to open file browser.";
            default = "<leader>e";
          };
          # active buffers
          buffers = mkOption {
            type = types.str;
            description = "Keybind to show active buffers.";
            default = "<leader>fb";
          };
          # error list
          error = mkOption {
            type = types.str;
            description = "Keybind to show LSP errors.";
            default = "<leader>t";
          };
          # find files
          find = mkOption {
            type = types.str;
            description = "Keybind to search files in working directory.";
            default = "<leader>ff";
          };
          # grep files
          grep = mkOption {
            type = types.str;
            description = "Keybind to grep files in working directory.";
            default = "<leader>fg";
          };
          # help menu
          help = mkOption {
            type = types.str;
            description = "Keybind to search help menus.";
            default = "<leader>fh";
          };
          # undo tree
          undo = mkOption {
            type = types.str;
            description = "Keybind to view undo tree.";
            default = "<leader>u";
          };
        };

        # lsp actions
        lsp = {
          hover = mkOption {
            type = types.str;
            description = "Keybind to open LSP hover menu on a value.";
            default = "K";
          };
          action = mkOption {
            type = types.str;
            description = "Keybind to perform an LSP action on a value.";
            default = "<leader>la";
          };
          references = mkOption {
            type = types.str;
            description = "Keybind to view all references of a value.";
            default = "<leader>lr";
          };
          rename = mkOption {
            type = types.str;
            description = "Keybind to rename currrent and all references of a value.";
            default = "<leader>ln";
          };
        };

        # completion
        cmp = {
          prev = mkOption {
            type = types.str;
            description = "Keybind to select previous value in completion engine.";
            default = "<C-p>";
          };
          next = mkOption {
            type = types.str;
            description = "Keybind to select next value in completion engine.";
            default = "<C-n>";
          };
          confirm = mkOption {
            type = types.str;
            description = "Keybind to confirm current value in completion engine.";
            default = "<CR>";
          };
          complete = mkOption {
            type = types.str;
            description = "Keybind to auto complete using completion engine.";
            default = "<C-space>";
          };
        };
      };

      # active lsp servers
      lsps = mkOption {
        type = with types; listOf str;
        description = "List of lsp servers to load";
        default = ["clangd" "zls" "rust_analyzer"];
      };

    };
  };

  config = {
    environment.variables.EDITOR = "nvim";

    home-manager.users.${config.user} = {
      programs.neovim = {

        enable = true;
        viAlias = true;
        vimAlias = true;
        extraLuaConfig = ''

          --[[ VIM ]]--

          vim.opt.tabstop = ${toString config.neovim.tabWidth}
          vim.opt.softtabstop = ${toString config.neovim.tabWidth}
          vim.opt.shiftwidth = ${toString config.neovim.tabWidth}
          vim.opt.expandtab = ${boolToString config.neovim.expandTab}
          vim.opt.mouse = "a"
          vim.opt.clipboard = "unnamedplus"
          vim.opt.hlsearch = true
          vim.opt.autoindent = true
          vim.opt.ttyfast = true
          vim.opt.number = true
          vim.opt.relativenumber = true
          vim.opt.rnu = true
          vim.opt.swapfile = false
          vim.opt.fillchars = { eob = " "}


          --[[ BUF ]]--

          -- remove trailing whitespace on save
          vim.api.nvim_create_autocmd({ "BufWritePre" }, {
          	pattern = { "*" },
          	command = [[%s/\s\+$//e]],
          })

          --[[ KEYBINDS ]]--

          -- leader

          vim.g.mapleader = "${config.neovim.keys.leader}"
          vim.g.maplocalleader = "${config.neovim.keys.leader}"
          vim.keymap.set("", '<leader>', '<Nop>', { noremap = true, silent = true })

          -- bind helper function

          local function bind(key, action, opts)
          	opts = opts or {}
          	vim.keymap.set('n', key, action, opts)
          end

          -- lsp

          bind("${config.neovim.keys.noh}", vim.cmd.noh)

          vim.api.nvim_create_autocmd('LspAttach', {
          	desc = 'LSP actions',
          	callback = function(event)
          		local opts = {buffer = event.buf}
          		bind("${config.neovim.keys.lsp.hover}", function() vim.lsp.buf.hover() end, opts)
          		bind("${config.neovim.keys.lsp.action}", function() vim.lsp.buf.code_action() end, opts)
          		bind("${config.neovim.keys.lsp.references}", function() vim.lsp.buf.references() end, opts)
          		bind("${config.neovim.keys.lsp.rename}", function() vim.lsp.buf.rename() end, opts)
          	end
          })

        '';

        plugins = with pkgs.vimPlugins; [
          # Deoendencies
          vim-devicons
          nvim-web-devicons
          plenary-nvim
          # Colorscheme
          {
            plugin = base16-nvim;
            type = "lua";
            config = ''
              vim.opt.termguicolors = true
              vim.g.base16_transparent_background = 1

              local colorscheme = require('base16-colorscheme')
              colorscheme.setup({
                base00 = '#${config.theme.colors.base00}',
                base01 = '#${config.theme.colors.base01}',
                base02 = '#${config.theme.colors.base02}',
                base03 = '#${config.theme.colors.base03}',
                base04 = '#${config.theme.colors.base04}',
                base05 = '#${config.theme.colors.base05}',
                base06 = '#${config.theme.colors.base06}',
                base07 = '#${config.theme.colors.base07}',
                base08 = '#${config.theme.colors.base08}',
                base09 = '#${config.theme.colors.base09}',
                base0A = '#${config.theme.colors.base0A}',
                base0B = '#${config.theme.colors.base0B}',
                base0C = '#${config.theme.colors.base0C}',
                base0D = '#${config.theme.colors.base0D}',
                base0E = '#${config.theme.colors.base0E}',
                base0F = '#${config.theme.colors.base0F}',
              })

              -- make transparent background
              vim.api.nvim_set_hl(0, "Normal", { bg = "none" })
              vim.api.nvim_set_hl(0, "NormalFloat", { bg = "none" })
              vim.api.nvim_set_hl(0, 'EndOfBuffer', { bg = "none" })
              -- identifiers should not be colored
              vim.api.nvim_set_hl(0, "Identifier", { fg = "#${config.theme.colors.base05}" })
              vim.api.nvim_set_hl(0, "TSVariable", { fg = "#${config.theme.colors.base05}" })
              -- macro should be colored as a keyword
              vim.api.nvim_set_hl(0, "TSFuncMacro", { fg = "#${config.theme.colors.base0E}" })
              -- comments are too dark
              vim.api.nvim_set_hl(0, "Comment", { fg = "#${config.theme.colors.base07}" })
              vim.api.nvim_set_hl(0, "@comment", { link = "Comment" })
            '';
          }
          # Mode line
          {
            plugin = lualine-nvim;
            type = "lua";
            config = ''
              require('lualine').setup {
                options = {
                  theme = 'base16',
                  icons_enabled = true,
                  globalstatus = true,
                },
              };
            '';
          }
          # Buffer line
          {
            plugin = bufferline-nvim;
            type = "lua";
            config = ''
              require('bufferline').setup {}
            '';
          }
          # File browser
          {
            plugin = nvim-tree-lua;
            type = "lua";
            config = ''
              vim.g.loaded_netrw = 1
              vim.g.loaded_netrwPlugin = 1

              require('nvim-tree').setup {
                sort = {
                  sorter = "case_sensitive",
                },
                view = {
                  centralize_selection = true,
                  signcolumn = "yes",
                  float = {
                    enable = true,
                    quit_on_focus_loss = true,
                    open_win_config = {
                      relative = "editor",
                      border = "rounded",
                      width = 60,
                      height = 20,
                      row = 1,
                      col = 1,
                    },
                  };
                },
                renderer = {
                  group_empty = true,
                },
                actions = {
                  use_system_clipboard = true,
                  change_dir = {
                    enable = true,
                    global = false,
                    restrict_above_cwd = false,
                  },
                  expand_all = {
                    max_folder_discovery = 300,
                    exclude = {},
                  },
                  file_popup = {
		              	open_win_config = {
		              		col = 1,
		              		row = 1,
		              		relative = "cursor",
		              		border = "shadow",
		              		style = "minimal",
		              	},
		              },
		              open_file = {
                    quit_on_open = true,
		              	window_picker = {
		              		enable = false,
		              		picker = "default",
		              		chars = "abcdefghijklmnopqrstuvwxyz1234567890",
		              		exclude = {
		              			filetype = { "notify", "lazy", "qf", "diff", "fugitive", "fugitiveblame" },
		              			buftype = { "nofile", "terminal", "help" },
		              		},
		              	}
		              },
		              remove_file = {
		              	close_window = true,
		              },
                },
                filters = {
                  dotfiles = false,
                },
                tab = {
	              	sync = {
	              		open = false,
	              		close = false,
	              		ignore = {},
	              	},
	              },
                git = {
                  enable = false,
                },
	              update_cwd = true,
	              respect_buf_cwd = true,
	              update_focused_file = {
	              	enable = true,
	              	update_cwd = true
	              },
              };

              bind("${config.neovim.keys.menus.browser}", vim.cmd.NvimTreeToggle)
            '';
          }
          # Undo tree
          {
            plugin = undotree;
            type = "lua";
            config = ''
              bind("${config.neovim.keys.menus.undo}", vim.cmd.UndotreeToggle)
            '';
          }
          # Trouble (error menu)
          {
            plugin = trouble-nvim;
            type = "lua";
            config = ''
              bind("${config.neovim.keys.menus.error}", function() require('trouble').toggle() end)
            '';
          }
          # Telescope (buffers/find/grep/help)
          {
            plugin = telescope-nvim;
            type = "lua";
            config = ''
              local telescope = require('telescope.builtin')
              bind("${config.neovim.keys.menus.buffers}", telescope.buffers)
              bind("${config.neovim.keys.menus.find}", telescope.find_files)
              bind("${config.neovim.keys.menus.grep}", telescope.live_grep)
              bind("${config.neovim.keys.menus.help}", telescope.help_tags)
              vim.api.nvim_set_hl(0, 'TelescopeNormal', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopeBorder', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopeResultsTitle', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopePromptTitle', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopePreviewTitle', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopePromptNormal', { bg = "none" })
              vim.api.nvim_set_hl(0, 'TelescopePromptBorder', { bg = "none" })
            '';
          }
          # Snippets
          vim-vsnip
          vim-vsnip-integ
          friendly-snippets
          # Completion
          cmp-buffer
          cmp-nvim-lsp
          cmp-vsnip
          {
            plugin = nvim-cmp;
            type = "lua";
            config = ''
              local cmp = require('cmp');
              local cmp_select = {behavior = cmp.SelectBehavior.select}
              local cmp_mappings = cmp.mapping.preset.insert({
              	["${config.neovim.keys.cmp.prev}"] = cmp.mapping.select_prev_item(cmp_select),
              	["${config.neovim.keys.cmp.next}"] = cmp.mapping.select_next_item(cmp_select),
              	["${config.neovim.keys.cmp.confirm}"] = cmp.mapping.confirm({ select = true }),
              	["${config.neovim.keys.cmp.complete}"] = cmp.mapping.complete(),
              })

              cmp_mappings['<Tab>'] = nil
              cmp_mappings['<S-Tab>'] = nil

              cmp.setup {
                snippet = {
                  expand = function(args)
                    vim.fn["vsnip#anonymous"](args.body)
                  end,
                },
                sources = cmp.config.sources {
                  { name = 'nvim_lsp' },
                  { name = 'vsnip' },
                  { name = 'buffer' },
                },
                mapping = cmp_mappings,
              }
            '';
          }
          # Sourround delimiters
          {
            plugin = nvim-surround;
            type = "lua";
            config = ''
              require('nvim-surround').setup {}
            '';
          }
          # Comment functions
          nerdcommenter
          # Treesitter
          nvim-treesitter.withAllGrammars
          # Syntax hilighting
          {
            plugin = vim-illuminate;
            type = "lua";
            config = ''
              require('illuminate').configure {
                providers = {
                  'lsp',
                  'treesitter',
                  'regex',
                },
              }
            '';
          }
          # Todo comments
          {
            plugin = todo-comments-nvim;
            type = "lua";
            config = ''
              require('todo-comments').setup()
            '';
          }
          # Lsp server
          {
            plugin = nvim-lspconfig;
            type = "lua";
            config = let
              lspConfigs = map (server: ''
                lspconfig["${server}"].setup({
                  capabilities = capabilities
                })
              '') config.neovim.lsps;
            in ''
                local lspconfig = require('lspconfig')
                local capabilities = require'cmp_nvim_lsp'.default_capabilities()
                ${lib.concatStrings lspConfigs}
            '';
          }
          # Notifications
          {
            plugin = fidget-nvim;
            type = "lua";
            config = ''
              require("fidget").setup {
              	notification = {
              		window = {
              			winblend = 0,
              		},
              	},
              }
            '';
          }
          # Auto indentation
          {
            plugin = indent-o-matic;
            type = "lua";
            config = ''
              require('indent-o-matic').setup {
                max_lines = 2048,
                standard_widths = { 2, 4, 8 },
                skip_multiline = true,
              };
            '';
          }
          # 80 column width
          {
            plugin = virt-column-nvim;
            type = "lua";
            config = ''
              require('virt-column').setup {
              	enabled = true,
              	virtcolumn = "80"
              }
            '';
          }
        ];

      };
    };
  };
}