if vim.g.did_load_keymaps_plugin then
  return
end
vim.g.did_load_keymaps_plugin = true

local api = vim.api
local fn = vim.fn
local keymap = vim.keymap
local diagnostic = vim.diagnostic

-- Yank from current position till end of current line
keymap.set('n', 'Y', 'y$', { silent = true, desc = '[Y]ank to end of line' })

require('which-key').add({
  -- Buffer list navigation, ordering, ...
  { "<C-p>",      "<Cmd>BufferLineTogglePin<Cr>",                               desc = '[p]in buffer' },
  { "gB",         "<Cmd>BufferLinePick<Cr>",                                    desc = '[g]o to [b]uffer' },
  { '>b',         "<Cmd>BufferLineMoveNext<Cr>",                                desc = 'move [b]uffer right' },
  { '<b',         "<Cmd>BufferLineMovePrev<Cr>",                                desc = 'move [b]uffer left' },
  { '[b',         "<Cmd>BufferLineCyclePrev<Cr>",                               desc = 'previous [b]uffer' },
  { ']b',         "<Cmd>BufferLineCycleNext<Cr>",                               desc = 'next [b]uffer' },
  { '[B',         vim.cmd.bfirst,                                               desc = 'first [B]uffer' },
  { ']B',         vim.cmd.blast,                                                desc = 'last [B]uffer' },
  { '<leader>tn', vim.cmd.tabnew,                                               desc = '[t]ab: [n]ew' },
  { '<leader>tq', "<Cmd>bd<Cr>",                                                desc = '[t]ab: [q]uit/close' },
  { '<leader>c',  "<Cmd>bd<Cr>",                                                desc = '[c]lose tab' },

  -- Window resizing
  { "<C-Up>",     "<Cmd>resize -2<CR>",                                         desc = "resize split up" },
  { "<C-Down>",   "<Cmd>resize +2<CR>",                                         desc = "resize split down" },
  { "<C-Left>",   "<Cmd>vertical resize +2<CR>",                                desc = "resize split left" },
  { "<C-Right>",  "<Cmd>vertical resize -2<CR>",                                desc = "resize split right" },

  -- ToggleTerm
  { "<leader>Tf", "<Cmd>ToggleTerm direction=float<CR>",                        desc = "[T]oggleterm [f]loat" },
  { "<leader>Th", "<Cmd>ToggleTerm size=10 direction=horizontal<CR>",           desc = "[T]oggleterm [h]orizontal split" },
  { "<leader>Tv", "<Cmd>ToggleTerm size=80 direction=vertical<CR>",             desc = "[T]oggleterm [v]ertical split" },
  { "<F7>",       '<Cmd>execute v:count . "ToggleTerm"<CR>',                    desc = "Toggle terminal" },
  { mode = "t",   "<F7>",                                                       "<Cmd>ToggleTerm<CR>",                                  desc = "Toggle terminal" },
  { mode = "i",   "<F7>",                                                       "<Esc><Cmd>ToggleTerm<CR>",                             desc = "Toggle terminal" },
  { "<C-'>",      '<Cmd>execute v:count . "ToggleTerm"<CR>',                    desc = "Toggle terminal" }, -- requires terminal that supports binding <C-'>
  { "<C-'>",      "<Cmd>ToggleTerm<CR>",                                        desc = "Toggle terminal" }, -- requires terminal that supports binding <C-'>
  { "<C-'>",      "<Esc><Cmd>ToggleTerm<CR>",                                   desc = "Toggle terminal" }, -- requires terminal that supports binding <C-'>`

  -- Trouble
  { "<leader>x",  group = "trouble" },
  { "<leader>xt", "<cmd>Trouble diagnostics toggle<cr>",                        desc = "trouble: [t]oggle", },
  { "<leader>xX", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>",           desc = "trouble: [b]uffer diagnostics", },
  { "<leader>xs", "<cmd>Trouble symbols toggle focus=false<cr>",                desc = "trouble: [s]ymbols (Trouble)", },
  { "<leader>xl", "<cmd>Trouble lsp toggle focus=false win.position=right<cr>", desc = "trouble: [l]sp definitions / references / ...", },
  { "<leader>xL", "<cmd>Trouble loclist toggle<cr>",                            desc = "trouble: [L]ocation list", },
  { "<leader>xQ", "<cmd>Trouble qflist toggle<cr>",                             desc = "trouble: [q]uickfix list", },
  {
    "<leader>xw",
    noremap = true,
    callback = function()
      for _, client in ipairs(vim.lsp.buf_get_clients()) do
        require("workspace-diagnostics").populate_workspace_diagnostics(client, 0)
      end
    end,
    desc = "trouble: load [w]orkspace"
  },

  { "<leader>lP", "<cmd>MarkdownPreviewToggle<cr>",                             desc = "Markdown [l] [P]review"},
})

-- Toggle the quickfix list (only opens if it is populated)
local function toggle_qf_list()
  local qf_exists = false
  for _, win in pairs(fn.getwininfo() or {}) do
    if win['quickfix'] == 1 then
      qf_exists = true
    end
  end
  if qf_exists == true then
    vim.cmd.cclose()
    return
  end
  if not vim.tbl_isempty(vim.fn.getqflist()) then
    vim.cmd.copen()
  end
end

keymap.set('n', '<C-c>', toggle_qf_list, { desc = 'toggle quickfix list' })
keymap.set({ "v", "n" }, "gf", require("actions-preview").code_actions, { desc = '[l]sp [f]ix' })
keymap.set({ "v", "n" }, "<leader>lf", require("actions-preview").code_actions, { desc = '[l]sp [f]ix' })

local function try_fallback_notify(opts)
  local success, _ = pcall(opts.try)
  if success then
    return
  end
  success, _ = pcall(opts.fallback)
  if success then
    return
  end
  vim.notify(opts.notify, vim.log.levels.INFO)
end

-- Cycle the quickfix and location lists
local function cleft()
  try_fallback_notify {
    try = vim.cmd.cprev,
    fallback = vim.cmd.clast,
    notify = 'Quickfix list is empty!',
  }
end

local function cright()
  try_fallback_notify {
    try = vim.cmd.cnext,
    fallback = vim.cmd.cfirst,
    notify = 'Quickfix list is empty!',
  }
end

keymap.set('n', '[c', cleft, { silent = true, desc = '[c]ycle quickfix left' })
keymap.set('n', ']c', cright, { silent = true, desc = '[c]ycle quickfix right' })
keymap.set('n', '[C', vim.cmd.cfirst, { silent = true, desc = 'first quickfix entry' })
keymap.set('n', ']C', vim.cmd.clast, { silent = true, desc = 'last quickfix entry' })

local function lleft()
  try_fallback_notify {
    try = vim.cmd.lprev,
    fallback = vim.cmd.llast,
    notify = 'Location list is empty!',
  }
end

local function lright()
  try_fallback_notify {
    try = vim.cmd.lnext,
    fallback = vim.cmd.lfirst,
    notify = 'Location list is empty!',
  }
end

keymap.set('n', '[l', lleft, { silent = true, desc = 'cycle [l]oclist left' })
keymap.set('n', ']l', lright, { silent = true, desc = 'cycle [l]oclist right' })
keymap.set('n', '[L', vim.cmd.lfirst, { silent = true, desc = 'first [L]oclist entry' })
keymap.set('n', ']L', vim.cmd.llast, { silent = true, desc = 'last [L]oclist entry' })

-- Resize vertical splits
local toIntegral = math.ceil
keymap.set('n', '<leader>w+', function()
  local curWinWidth = api.nvim_win_get_width(0)
  api.nvim_win_set_width(0, toIntegral(curWinWidth * 3 / 2))
end, { silent = true, desc = 'inc window [w]idth' })
keymap.set('n', '<leader>w-', function()
  local curWinWidth = api.nvim_win_get_width(0)
  api.nvim_win_set_width(0, toIntegral(curWinWidth * 2 / 3))
end, { silent = true, desc = 'dec window [w]idth' })
keymap.set('n', '<leader>h+', function()
  local curWinHeight = api.nvim_win_get_height(0)
  api.nvim_win_set_height(0, toIntegral(curWinHeight * 3 / 2))
end, { silent = true, desc = 'inc window [h]eight' })
keymap.set('n', '<leader>h-', function()
  local curWinHeight = api.nvim_win_get_height(0)
  api.nvim_win_set_height(0, toIntegral(curWinHeight * 2 / 3))
end, { silent = true, desc = 'dec window [h]eight' })

-- Close floating windows [Neovim 0.10 and above]
keymap.set('n', '<leader>fq', function()
  vim.cmd('fclose!')
end, { silent = true, desc = '[f]loating windows: [q]uit/close all' })

-- Remap Esc to switch to normal mode and Ctrl-Esc to pass Esc to terminal
keymap.set('t', '<Esc>', '<C-\\><C-n>', { desc = 'switch to normal mode' })
keymap.set('t', '<C-Esc>', '<Esc>', { desc = 'send Esc to terminal' })

-- Shortcut for expanding to current buffer's directory in command mode
keymap.set('c', '%%', function()
  if fn.getcmdtype() == ':' then
    return fn.expand('%:h') .. '/'
  else
    return '%%'
  end
end, { expr = true, desc = "expand to current buffer's directory" })


local severity = diagnostic.severity

keymap.set('n', '<leader>le', function()
  local _, winid = diagnostic.open_float(nil, { scope = 'line' })
  if not winid then
    vim.notify('no diagnostics found', vim.log.levels.INFO)
    return
  end
  vim.api.nvim_win_set_config(winid or 0, { focusable = true })
end, { noremap = true, silent = true, desc = 'diagnostics floating window' })
keymap.set('n', '[d', diagnostic.goto_prev, { noremap = true, silent = true, desc = 'previous [d]iagnostic' })
keymap.set('n', ']d', diagnostic.goto_next, { noremap = true, silent = true, desc = 'next [d]iagnostic' })
keymap.set('n', '[e', function()
  diagnostic.goto_prev {
    severity = severity.ERROR,
  }
end, { noremap = true, silent = true, desc = 'previous [e]rror diagnostic' })
keymap.set('n', ']e', function()
  diagnostic.goto_next {
    severity = severity.ERROR,
  }
end, { noremap = true, silent = true, desc = 'next [e]rror diagnostic' })
keymap.set('n', '[w', function()
  diagnostic.goto_prev {
    severity = severity.WARN,
  }
end, { noremap = true, silent = true, desc = 'previous [w]arning diagnostic' })
keymap.set('n', ']w', function()
  diagnostic.goto_next {
    severity = severity.WARN,
  }
end, { noremap = true, silent = true, desc = 'next [w]arning diagnostic' })
keymap.set('n', '[h', function()
  diagnostic.goto_prev {
    severity = severity.HINT,
  }
end, { noremap = true, silent = true, desc = 'previous [h]int diagnostic' })
keymap.set('n', ']h', function()
  diagnostic.goto_next {
    severity = severity.HINT,
  }
end, { noremap = true, silent = true, desc = 'next [h]int diagnostic' })

local function toggle_spell_check()
  ---@diagnostic disable-next-line: param-type-mismatch
  vim.opt.spell = not (vim.opt.spell:get())
end

keymap.set('n', '<leader>S', toggle_spell_check, { noremap = true, silent = true, desc = 'toggle [S]pell' })

keymap.set('n', '<C-d>', '<C-d>zz', { desc = 'move [d]own half-page and center' })
keymap.set('n', '<C-u>', '<C-u>zz', { desc = 'move [u]p half-page and center' })
keymap.set('n', '<C-f>', '<C-f>zz', { desc = 'move DOWN [f]ull-page and center' })
keymap.set('n', '<C-b>', '<C-b>zz', { desc = 'move UP full-page and center' })

keymap.set('n', '<C-h>', '<Cmd>wincmd h<CR>', { desc = 'focus left window' })
keymap.set('n', '<C-j>', '<Cmd>wincmd j<CR>', { desc = 'focus bottom window' })
keymap.set('n', '<C-k>', '<Cmd>wincmd k<CR>', { desc = 'focus top window' })
keymap.set('n', '<C-l>', '<Cmd>wincmd l<CR>', { desc = 'focus right window' })

keymap.set('n', '<leader>tt', '<Cmd>TodoTelescope<Cr>', { desc = '[t]elescope [t]odo comments' })

--- Disabled keymaps [enable at your own risk]

-- Automatic management of search highlight
-- XXX: This is not so nice if you use j/k for navigation
-- (you should be using <C-d>/<C-u> and relative line numbers instead ;)
--
-- local auto_hlsearch_namespace = vim.api.nvim_create_namespace('auto_hlsearch')
-- vim.on_key(function(char)
--   if vim.fn.mode() == 'n' then
--     vim.opt.hlsearch = vim.tbl_contains({ '<CR>', 'n', 'N', '*', '#', '?', '/' }, vim.fn.keytrans(char))
--   end
-- end, auto_hlsearch_namespace)