require("impatient")

require("nvim-treesitter.configs").setup({
  sync_install = false,
  auto_install = false,
  highlight = {
    enable = true,
    additional_vim_regex_highlighting = false,
  },
})

-- load cmp on InsertEnter
vim.api.nvim_create_autocmd("InsertEnter", {
  callback = function()
    local cmp = require("cmp")
    local luasnip = require("luasnip")
    require("copilot_cmp").setup()

    cmp.setup({
      formatting = {
        format = require("lspkind").cmp_format({
          mode = "symbol", -- show only symbol annotations
          maxwidth = 50, -- prevent the popup from showing more than provided characters
          ellipsis_char = "...", -- when popup menu exceed maxwidth, the truncated part would show ellipsis_char instead
          symbol_map = {
            Copilot = "",
          },
        }),
      },
      snippet = {
        -- REQUIRED - you must specify a snippet engine
        expand = function(args)
          require("luasnip").lsp_expand(args.body)
        end,
      },
      mapping = cmp.mapping.preset.insert({
        ["<C-b>"] = cmp.mapping.scroll_docs(-4),
        ["<C-f>"] = cmp.mapping.scroll_docs(4),
        ["<C-Space>"] = cmp.mapping.complete(),
        ["<C-e>"] = cmp.mapping.abort(),
        ["<CR>"] = cmp.mapping.confirm({ select = true }),
        ["<Tab>"] = cmp.mapping(function(fallback)
          if cmp.visible() then
            cmp.select_next_item()
          elseif luasnip.expand_or_jumpable() then
            luasnip.expand_or_jump()
          else
            fallback()
          end
        end, { "i", "s" }),
        ["<S-Tab>"] = cmp.mapping(function(fallback)
          if cmp.visible() then
            cmp.select_prev_item()
          elseif luasnip.jumpable(-1) then
            luasnip.jump(-1)
          else
            fallback()
          end
        end, { "i", "s" }),
      }),
      sources = {
        { name = "buffer", priority = 1 },
        { name = "copilot", priority = 8 },
        { name = "luasnip", priority = 7 },
        { name = "nvim_lsp", priority = 9 },
        { name = "orgmode", priority = 9 },
      },
    })
  end,
})

---merge tables
---@param ... table[]
---@return table
local function table_merge(...)
  local tables_to_merge = { ... }
  assert(#tables_to_merge > 1, "There should be at least two tables to merge them")

  for k, t in ipairs(tables_to_merge) do
    assert(type(t) == "table", string.format("Expected a table as function parameter %d", k))
  end

  local result = tables_to_merge[1]

  for i = 2, #tables_to_merge do
    local from = tables_to_merge[i]
    for k, v in pairs(from) do
      if type(v) == "table" then
        result[k] = result[k] or {}
        result[k] = table_merge(result[k], v)
      else
        result[k] = v
      end
    end
  end

  return result
end

local lsp_lines = require("lsp_lines")
lsp_lines.setup()
-- Disable virtual_text since it's redundant due to lsp_lines.
vim.diagnostic.config({
  virtual_text = false,
})

-- The nvim-cmp almost supports LSP's capabilities so You should advertise it to LSP servers..
local capabilities = require("cmp_nvim_lsp").default_capabilities()

vim.o.foldcolumn = "1" -- '0' is not bad
vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value
vim.o.foldlevelstart = 99
vim.o.foldenable = true
vim.o.fillchars = [[eob: ,fold: ,foldopen:,foldsep: ,foldclose:]]
vim.o.statuscolumn = "%= "
  -- FIXME: figure out how to put on the other side without having to do a lot of shifting
  .. "%s" -- sign column
  .. "%{%" -- evaluate this, and then evaluate what it returns
  .. "&number ?"
  .. "(v:relnum ?"
  -- when showing relative numbers, make sure to pad so things don't shift as you move the cursor
  .. 'printf("%"..len(line("$")).."s", v:relnum)'
  .. ":"
  .. "v:lnum"
  .. ")"
  .. ":"
  .. '""'
  .. " " -- space between lines and fold
  .. "%}"
  .. "%= "
  .. "%#FoldColumn#" -- highlight group for fold
  .. "%{" -- expression for showing fold expand/colapse
  .. "foldlevel(v:lnum) > foldlevel(v:lnum - 1)" -- any folds?
  .. "? (foldclosed(v:lnum) == -1" -- currently open?
  .. '? ""' -- point down
  .. ':  ""' -- point to right
  .. ")"
  .. ': " "' -- blank for no fold, or inside fold
  .. "}"
  .. "%= " -- spacing between end of column and start of text

-- Using ufo provider need remap `zR` and `zM`. If Neovim is 0.6.1, remap yourself
require("which-key").register({
  z = {
    R = { require("ufo").openAllFolds, "Open all folds" },
    M = { require("ufo").closeAllFolds, "Close all folds" },
  },
})
-- Tell the server the capability of foldingRange,
-- Neovim hasn't added foldingRange to default capabilities, users must add it manually
capabilities.textDocument.foldingRange = {
  dynamicRegistration = false,
  lineFoldingOnly = true,
}
require("ufo").setup()

require("lspsaga").setup({
  symbol_in_winbar = {
    enable = false,
  },
  lightbulb = {
    enable = true,
    enable_in_insert = true,
    sign = true,
    sign_priority = 40,
    virtual_text = false,
  },
})

local lspconfig = require("lspconfig")
local on_attach_def = function(_, bufnr)
  require("which-key").register({
    K = { "<cmd>Lspsaga hover_doc ++quiet<cr>", "show info" },
    ["<leader>"] = {
      l = {
        name = "lsp",
        d = { "<cmd>Lspsaga show_cursor_diagnostics<cr>", "open diagnostic window" },
        n = { "<cmd>Lspsaga diagnostic_jump_next<CR>", "next error" },
        p = { "<cmd>Lspsaga diagnostic_jump_prev<CR>", "prev error" },
        c = { "<cmd>Lspsaga code_action<cr>", "code action" },
        r = { "<cmd>Lspsaga rename<cr>", "rename" },
        i = { "<cmd>Lspsaga hover_doc ++keep<cr>", "show info (sticky)" },
        f = {
          function()
            vim.lsp.buf.format({ async = true })
          end,
          "format (lsp)",
          mode = { "n", "v" },
        },
      },
      w = {
        name = "workspace",
        a = { vim.lsp.buf.add_workspace_folder, "add workspace folder" },
        r = { vim.lsp.buf.remove_workspace_folder, "remove workspace folder" },
        l = {
          function()
            print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
          end,
          "list workspace folders",
        },
      },
      t = {
        name = "toggle",
        l = { lsp_lines.toggle, "lsp lines" },
      },
    },
    g = {
      name = "goto",
      d = { "<cmd>Lspsaga peek_definition<cr>", "definition" },
      t = { "<cmd>Lspsaga peek_type_definition<cr>", "type defininition" },
      h = { "<cmd>Lspsaga lsp_finder<CR>", "lsp finder" },
    },
  }, { buffer = bufnr, silent = true })
end

local lspconfig_default_options = {
  on_attach = on_attach_def,
  capabilities = capabilities,
  flags = {
    debounce_text_changes = 100,
  },
}

---function to add default options to lspconfig
---@param lsp string
---@param options table
---@return nil
local function lspconfig_setup(lsp, options)
  local final_options = table_merge(lspconfig_default_options, options)
  lspconfig[lsp].setup(final_options)
end

local servers = {
  "bashls",
  "nil_ls",
  "pylsp",
  "ruff_lsp",
  "rust_analyzer",
}
for _, lsp in ipairs(servers) do
  lspconfig_setup(lsp, {})
end

lspconfig_setup("lua_ls", {
  settings = {
    Lua = {
      runtime = {
        -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
        version = "LuaJIT",
      },
      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),
        checkThirdParty = false,
      },
      -- Do not send telemetry data containing a randomized but unique identifier
      telemetry = {
        enable = false,
      },
      format = {
        enable = false,
      },
    },
  },
})
