local function map(mode, l, r, opts)
  opts = opts or {}
  vim.keymap.set(mode, l, r, opts)
end

-- Remap for dealing with visual line wraps
map("n", "k", "v:count == 0 ? 'gk' : 'k'", { expr = true })
map("n", "j", "v:count == 0 ? 'gj' : 'j'", { expr = true })

-- Keep selection while indenting
map("v", "<", "<gv")
map("v", ">", ">gv")

-- Cancel search highlighting with ESC
map({ "i", "n" }, "<esc>", "<cmd>noh<cr><esc>", { desc = "Clear hlsearch and ESC" })

-- window
map("n", "<leader>ws", "<cmd>split<cr>", { desc = "Horizontal split" })
map("n", "<leader>wv", "<cmd>vsplit<cr>", { desc = "Vertical split" })
map("n", "<leader>wc", "<cmd>close<cr>", { desc = "Close" })
map("n", "<leader>wT", "<cmd>wincmd T<cr>", { desc = "Move window to new tab" })
map("n", "<leader>wr", "<cmd>wincmd r<cr>", { desc = "rotate down/right" })
map("n", "<leader>wR", "<cmd>wincmd R<cr>", { desc = "rotate up/left" })
map("n", "<leader>wH", "<cmd>wincmd H<cr>", { desc = "Move left" })
map("n", "<leader>wJ", "<cmd>wincmd J<cr>", { desc = "Move down" })
map("n", "<leader>wK", "<cmd>wincmd K<cr>", { desc = "Move up" })
map("n", "<leader>wL", "<cmd>wincmd L<cr>", { desc = "Move right" })
map("n", "<leader>w=", "<cmd>wincmd =<cr>", { desc = "Equalize size" })
map("n", "<leader>wk", "<cmd>resize +5<cr>", { desc = "Up" })
map("n", "<leader>wj", "<cmd>resize -5<cr>", { desc = "Down" })
map("n", "<leader>wh", "<cmd>vertical resize +3<cr>", { desc = "Left" })
map("n", "<leader>wl", "<cmd>vertical resize -3<cr>", { desc = "Right" })

-- buffers
map("n", "<tab>", "<cmd>bnext<cr>", { desc = "Next buffer" })
map("n", "<S-tab>", "<cmd>bprevious<cr>", { desc = "Prev buffer" })
map("n", "<leader>bD", "<cmd>%bd|e#|bd#<cr>", { desc = "Close all but the current buffer" })

-- new file
map("n", "<leader>fn", "<cmd>enew<cr>", { desc = "New file" })
-- save file
map("n", "<leader>fs", "<cmd>w<cr>", { desc = "Save file" })
-- open path under cursor
map("n", "<leader>fo", "gf", { desc = "Open path under cursor" })

-- terminal mappings
map("t", "<C-n>", "<cmd>close<cr>", { desc = "Hide Terminal" })

-- quickfix mappings
map("n", "<leader>j", "<cmd>cnext<cr>", { desc = "Qickfix next" })
map("n", "<leader>k", "<cmd>cprevious<cr>", { desc = "Qickfix prev" })

-- diffmode mappings
vim.api.nvim_create_autocmd({ "BufWinEnter", "WinEnter" }, {
  callback = function(args)
    if vim.wo.diff then
      vim.keymap.set("n", "<leader>j", "]c", { buffer = args.buf, desc = "Next change" })
      vim.keymap.set("n", "<leader>k", "[c", { buffer = args.buf, desc = "Prev change" })
      vim.keymap.set("n", "<leader>co", "do", { buffer = args.buf, desc = "Change from other (obtain)" })
      vim.keymap.set("n", "<leader>cp", "dp", { buffer = args.buf, desc = "Change to other (put)" })
    end
  end,
})

-- move over a closing element in insert mode
map("i", "<C-l>", function()
  local closers = { ")", "]", "}", ">", "'", '"', "`", "," }
  local line = vim.api.nvim_get_current_line()
  local row, col = unpack(vim.api.nvim_win_get_cursor(0))
  local after = line:sub(col + 1, -1)
  local closer_col = #after + 1
  local closer_i = nil
  for i, closer in ipairs(closers) do
    local cur_index, _ = after:find(closer)
    if cur_index and (cur_index < closer_col) then
      closer_col = cur_index
      closer_i = i
    end
  end
  if closer_i then
    vim.api.nvim_win_set_cursor(0, { row, col + closer_col })
  else
    vim.api.nvim_win_set_cursor(0, { row, col + 1 })
  end
end, { desc = "move over a closing element" })

-- Disable default mappings to declutter which-key
vim.api.nvim_del_keymap("n", "gra") -- vim.lsp.buf...
vim.api.nvim_del_keymap("x", "gra") -- vim.lsp.buf...
vim.api.nvim_del_keymap("n", "gri") -- vim.lsp.buf...
vim.api.nvim_del_keymap("n", "grn") -- vim.lsp.buf...
vim.api.nvim_del_keymap("n", "grr") -- vim.lsp.buf...
vim.api.nvim_del_keymap("n", "gO") -- vim.lsp.buf...

vim.api.nvim_del_keymap("n", "]d") -- vim.lsp.buf...
vim.api.nvim_del_keymap("n", "]D") -- vim.lsp.buf...

vim.api.nvim_del_keymap("n", "gx") -- open filepath under cursor
vim.api.nvim_del_keymap("x", "gx") -- open filepath under cursor

vim.keymap.set("n", "<CR>", function()
  if vim.bo.buftype == "quickfix" then
    -- Execute the default Enter behavior in quickfix list
    return vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<CR>", true, false, true), "n", false)
  end
  -- Get the current line number
  local line = vim.fn.line(".")
  -- Get the fold level of the current line
  local foldlevel = vim.fn.foldlevel(line)
  if foldlevel == 0 then
    vim.notify("No fold found", vim.log.levels.INFO)
  else
    vim.cmd("normal! za")
    vim.cmd("normal! zz") -- center the cursor line on screen
  end
end, { desc = "Toggle fold" })

map("n", "<leader>uI", function()
  vim.treesitter.inspect_tree()
  vim.api.nvim_input("I")
end, { desc = "Inspect Tree" })
