local M = {}

-- lsp_signature >>>
M.signature = function()
   require "lsp_signature".setup({
      toggle_key = '<M-x>'
   })
end -- <<<

-- lspinstall >>>
M.lspinstall = function() --
   require("nvim-lsp-installer").setup()
end -- <<<

-- treesitter >>>
M.treesitter = function()
   require'nvim-treesitter.configs'.setup {
      -- install the necessary parsers
      ensure_installed = {
         "bash",
         "c",
         "cmake",
         "comment",
         "cpp",
         "css",
         "dockerfile",
         "html",
         "javascript",
         "json",
         "jsonc",
         "lua",
         "nix",
         "python",
         "r",
         "regex",
         "rst",
         "rust",
         "toml",
         "vim",
         "yaml",
      },
      --ignore_install = { "javascript", "java" }, -- List of parsers to ignore installing
      highlight = {
         enable = true, -- false will disable the whole extension
      },
      -- indent = {
      --    enable = true
      -- },
      incremental_selection = {
         enable = true,
         keymaps = {
            init_selection = 'gnn',
            node_incremental = 'grn',
            scope_incremental = 'grc',
            node_decremental = 'grm',
         },
      },
      textobjects = {
         select = {
            enable = true,
            lookahead = true, -- Automatically jump forward to textobj, similar to targets.vim
            keymaps = {
               -- You can use the capture groups defined in textobjects.scm
               ['af'] = '@function.outer',
               ['if'] = '@function.inner',
               ['ac'] = '@class.outer',
               ['ic'] = '@class.inner',
            },
         },
         move = {
            enable = true,
            set_jumps = true, -- whether to set jumps in the jumplist
            goto_next_start = {
               [']m'] = '@function.outer',
               [']]'] = '@class.outer',
            },
            goto_next_end = {
               [']M'] = '@function.outer',
               [']['] = '@class.outer',
            },
            goto_previous_start = {
               ['[m'] = '@function.outer',
               ['[['] = '@class.outer',
            },
            goto_previous_end = {
               ['[M'] = '@function.outer',
               ['[]'] = '@class.outer',
            },
         },
      },
   }
end -- <<<

-- cmp >>>
M.cmp = function()
   -- nvim-cmp supports additional completion capabilities
   local capabilities = require("cmp_nvim_lsp").default_capabilities()
   -- capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)
   -- luasnip setup
   local luasnip = require 'luasnip'
   -- If you want insert `(` after select function or method item
   local cmp_autopairs = require('nvim-autopairs.completion.cmp')
   local cmp = require('cmp')
   cmp.event:on( 'confirm_done', cmp_autopairs.on_confirm_done({  map_char = { tex = '' } }))
   cmp.setup {
      snippet = {
         expand = function(args)
            luasnip.lsp_expand(args.body)
         end,
      },
      mapping = cmp.mapping.preset.insert {
         ['<C-p>'] = cmp.mapping.select_prev_item(),
         ['<C-n>'] = cmp.mapping.select_next_item(),
         ['<C-d>'] = cmp.mapping.scroll_docs(-4),
         ['<C-f>'] = cmp.mapping.scroll_docs(4),
         ['<C-Space>'] = cmp.mapping.complete(),
         ['<C-e>'] = cmp.mapping.close(),
         ['<CR>'] = cmp.mapping.confirm {
            behavior = cmp.ConfirmBehavior.Replace,
            select = false,
         },
         ['<Tab>'] = function(fallback)
            if cmp.visible() then
               cmp.select_next_item()
            elseif luasnip.expand_or_jumpable() then
               luasnip.expand_or_jump()
            else
               fallback()
            end
         end,
         ['<S-Tab>'] = function(fallback)
            if cmp.visible() then
               cmp.select_prev_item()
            elseif luasnip.jumpable(-1) then
               luasnip.jump(-1)
            else
               fallback()
            end
         end,
      },
      sources = {
         { name = 'nvim_lsp' },
         { name = 'nvim_lua' },
         { name = 'otter' },
         { name = 'pandoc_references' },
         { name = 'luasnip' },
         { name = 'treesitter' },
         { name = 'calc' },
         { name = 'spell' },
         { name = 'emoji' },
         { name = 'buffer' },
         { name = 'path' },
      },
      formatting = {
         fields = { "abbr", "kind", "menu" },
         format = function(entry, vim_item)
            -- fancy icons and a name of kind
            local kind = require("lspkind").cmp_format({ mode = "symbol", maxwidth = 50 })(entry, vim_item)
            -- local strings = vim.split(kind.kind, "%s", { trimempty = true })
            -- set a name for each source
            vim_item.kind = " " .. kind.kind
            vim_item.menu = ({
               path              = "[path]",
               emoji             = "[emoji]",
               nvim_lua          = "[lua]",
               nvim_lsp          = "[lsp]",
               treesitter        = "[ts]",
               calc              = "[calc]",
               spell             = "[spell]",
               cmdline           = "[cmd]",
               buffer            = "[buf]",
               luasnip           = "[snip]",
               pandoc_references = "[pandoc]",
               otter             = "[quarto]",
            })[entry.source.name] or "unknown type"
            return vim_item
         end,
      },
      window = {
         completion = {
            winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,Search:None",
         },
         documentation = {
            border = { "╭", "─", "╮", "│", "╯", "─", "╰", "│" },
         },
      },
      experimental = {
         ghost_text = true,
      },
   }
   -- Thanks to iwataka on github for this bit
   local search_config = {
      mapping = cmp.mapping.preset.cmdline(),
      sources = {
         { name = 'buffer' },
      }
   }
   -- Use buffer source for `/` and `?`
   cmp.setup.cmdline('/', search_config)
   cmp.setup.cmdline('?', search_config)
    -- `:` cmdline setup.
   cmp.setup.cmdline(':', {
      mapping = cmp.mapping.preset.cmdline(),
      sources = cmp.config.sources({
         { name = 'path' },
         { name = 'cmdline' },
      })
   })
end -- <<<

-- lspconfig >>>
M.lspconfig = function()
   local nvim_lsp = require('lspconfig')
   -- Keybinds >>>
   -- commit before telescope keybinds: 5d63069
   -- Use an on_attach function to only map the following keys
   -- after the language server attaches to the current buffer
   local opts = { noremap=true, silent=true }

   local map = vim.api.nvim_set_keymap
   local buffmap = vim.api.nvim_buf_set_keymap
   local buffopt = vim.api.nvim_buf_set_option

   map('n',  '<leader>e',  '<cmd>lua vim.diagnostic.open_float()<CR>',                 opts)
   map('n',  '[d',         '<cmd>lua vim.diagnostic.goto_prev()<CR>',                  opts)
   map('n',  ']d',         '<cmd>lua vim.diagnostic.goto_next()<CR>',                  opts)
   map('n',  '<leader>q',  '<cmd>lua require("telescope.builtin").diagnostics()<CR>',  opts)  -- Telescope

   -- Use an on_attach function to only map the following keys
   -- after the language server attaches to the current buffer
   local on_attach = function(client, bufnr)
      -- Enable completion triggered by <c-x><c-o>
      buffopt(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

      -- Mappings.
      -- See `:help vim.lsp.*` for documentation on any of the below functions
      buffmap(bufnr,  'n',  'gD',          '<cmd>lua vim.lsp.buf.declaration()<CR>',                                 opts)
      buffmap(bufnr,  'n',  'gd',          '<cmd>lua require("telescope.builtin").lsp_definitions()<CR>',            opts)  -- Telescope
      buffmap(bufnr,  'n',  'K',           '<cmd>lua vim.lsp.buf.hover()<CR>',                                       opts)
      buffmap(bufnr,  'n',  'gI',          '<cmd>lua require("telescope.builtin").lsp_implementations()<CR>',        opts)  -- Telescope
      buffmap(bufnr,  'n',  '<M-k>',       '<cmd>lua vim.lsp.buf.signature_help()<CR>',                              opts)
      buffmap(bufnr,  'n',  '<leader>wa',  '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>',                        opts)
      buffmap(bufnr,  'n',  '<leader>wr',  '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>',                     opts)
      buffmap(bufnr,  'n',  '<leader>wl',  '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>',  opts)
      buffmap(bufnr,  'n',  '<leader>D',   '<cmd>lua vim.lsp.buf.type_definition()<CR>',                             opts)
      buffmap(bufnr,  'n',  '<leader>rn',  '<cmd>lua vim.lsp.buf.rename()<CR>',                                      opts)
      buffmap(bufnr,  'n',  '<leader>ca',  '<cmd>lua vim.lsp.buf.code_action()<CR>',                                 opts)
      buffmap(bufnr,  'n',  'gR',          '<cmd>lua require("telescope.builtin").lsp_references()<CR>',             opts)  -- Telescope
      buffmap(bufnr,  'n',  '<leader>f',   '<cmd>lua vim.lsp.buf.format { async = true }<CR>',                       opts)
      buffmap(bufnr,  'n',  '<leader>so',  '<cmd>lua require("telescope.builtin").lsp_document_symbols()<CR>',       opts)
   end -- <<<
   -- cmp things >>>
   local capabilities
   if packer_plugins["nvim-cmp"] and packer_plugins["nvim-cmp"].loaded then -- only load this if cmp is loaded
      capabilities = require("cmp_nvim_lsp").default_capabilities()
   end
   -- <<<
   -- Load servers >>>
   -- General servers >>>
   -- Use a loop to conveniently call 'setup' on multiple servers and
   -- map buffer local keybindings when the language server attaches
   local lsp_servers = {
      'bashls',
      'clangd',
      'cssls',
      'dockerls',
      'eslint',
      'html',
      'jsonls',
      'kotlin_language_server',
      'marksman',
      'nil_ls',
      'nil_ls',
      'rust_analyzer',
      'yamlls',
   }
   for _, lsp in pairs(lsp_servers) do
         nvim_lsp[lsp].setup {
            on_attach = on_attach,
            flags = {
               debounce_text_changes = 150,
            }
         }
   end
   -- <<<

   -- Custom servers:
   -- lua >>>
   local sumneko_root_path
   local sumneko_binary
   if (vim.fn.exepath('lua-language-server') ~= '') then
      -- If you installed this with your package manager
      sumneko_root_path = '/usr/lib/lua-language-server/bin/Linux'
      sumneko_binary = vim.fn.exepath('lua-language-server')
   else
      -- If you installed this with lspinstall
      sumneko_root_path = vim.fn.getenv 'HOME' .. '/.local/share/nvim/lspinstall/lua/sumneko-lua/extension/server'
      sumneko_binary = sumneko_root_path .. '/lua-language-server'
   end

   -- From kickstart.nvim:
   if (vim.fn.executable(sumneko_binary) == 1) then
      -- Make runtime files discoverable to the server
      local runtime_path = vim.split(package.path, ';')
      table.insert(runtime_path, 'lua/?.lua')
      table.insert(runtime_path, 'lua/?/init.lua')

      nvim_lsp.lua_ls.setup {
         on_attach = on_attach,
         capabilities = capabilities,
         settings = {
            Lua = {
               runtime = {
                  -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
                  version = 'LuaJIT',
                  -- Setup your lua path
                  path = runtime_path,
               },
               diagnostics = {
                  -- Get the language server to recognize the `vim` global
                  globals = { 'vim' },
               },
               workspace = {
                  -- Make the server aware of Neovim runtime files
                  library = vim.api.nvim_get_runtime_file('', true),
               },
               -- Do not send telemetry data containing a randomized but unique identifier
               telemetry = {
                  enable = false,
               },
            },
         },
      }
   end
   -- lua <<<
   -- <<<
end -- <<<

return M

-- vim:fdm=marker:fmr=>>>,<<<:expandtab:tabstop=3:sw=3