-- Rafi's Neovim keymaps
-- github.com/rafi/vim-config
-- ===

-- This file is automatically loaded by plex.config.init

local Util = require('plex.lib.utils')
local map = vim.keymap.set

local function augroup(name)
	return vim.api.nvim_create_augroup('plex_' .. name, {})
end

-- enable elite mode, for people that are more used to vim than non-vim
vim.g.plex_elite_mode = true

-- Elite-mode: ctrl + Arrow-keys resize window
if vim.g.plex_elite_mode then
	map('n', '<C-Up>', '<cmd>resize +1<cr>', { desc = 'Resize Window' })
	map('n', '<C-Down>', '<cmd>resize -1<cr>', { desc = 'Resize Window' })
	map('n', '<C-Left>', '<cmd>vertical resize +1<cr>', { desc = 'Resize Window' })
	map('n', '<C-Right>', '<cmd>vertical resize -1<cr>', { desc = 'Resize Window' })
end

-- Package-manager
map('n', '<leader>l', '<cmd>Lazy<cr>', { desc = 'Open Lazy UI' })

-- stylua: ignore start

-- Navigation
-- ===

-- Move faster between lines
-- See vim-smoothie
--map({ 'n', 'x' }, 'K', "<C-u>")
--map({ 'n', 'x' }, 'J', "<C-d>")

-- Easier line-wise movement
map({'n', 'v'}, 'H', '<HOME>')
map({'n', 'v'}, 'L', '<END>')

-- Toggle fold or select option from popup menu
---@return string
map('n', '<CR>', function()
	return vim.fn.pumvisible() == 1 and '<CR>' or 'za'
end, { expr = true, desc = 'Toggle Fold' })

-- Focus the current fold by closing all others
map('n', '<S-Return>', 'zMzv', { remap = true, desc = 'Focus Fold' })

-- Location/quickfix list movement
if not Util.has('mini.bracketed') and not Util.has('trouble.nvim') then
	map('n', ']q', '<cmd>cnext<CR>', { desc = 'Next Quickfix Item' })
	map('n', '[q', '<cmd>cprev<CR>', { desc = 'Previous Quickfix Item' })
end
map('n', '<A-k>', '<cmd>lnext<CR>', { desc = 'Next Loclist Item' })
map('n', '<A-j>', '<cmd>lprev<CR>', { desc = 'Previous Loclist Item' })

-- go to next diagnostics entry
map('n', '<leader>cj', function ()
	vim.diagnostic.goto_next()
end, { desc = 'go to next diagnostic'})
map('n', '<leader>ck', function ()
	vim.diagnostic.goto_prev()
end, { desc = 'go to last diagnostic'})

-- Whitespace jump (see plugin/whitespace.vim)
map('n', ']s', function()
	require('plex.lib.edit').whitespace_jump(1)
end, { desc = 'Next Whitespace' })
map('n', '[s', function()
	require('plex.lib.edit').whitespace_jump(-1)
end, { desc = 'Previous Whitespace' })

-- Navigation in command line
map('c', '<C-h>', '<Home>')
map('c', '<C-l>', '<End>')

-- Scroll step sideways
map('n', 'zl', 'z4l')
map('n', 'zh', 'z4h')

-- move to spelling mistake
map('n', 'zn', ']s')
map('n', 'zN', '[s')

-- move screen and line up
map('n', 'zk', '<C-y>k')
map('n', 'zj', '<C-e>j')

-- Clipboard
-- ===

-- Yank to system clipboard
map({'n', 'v', 'x'}, '<leader>y', '"+y"', { desc = "yank to system"})
map({'n', 'v', 'x'}, '<leader>yy', '"+yy"', { desc = "yank line to system"})
map({'n', 'v', 'x'}, '<leader>Y', '"+Y"', { desc = "yank to system until line end"})

-- Yank buffer's relative path to clipboard
map('n', '<Leader>yp', function()
	local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':~:.')
	vim.fn.setreg('+', path)
	vim.notify(path, vim.log.levels.INFO, { title = 'Yanked relative path' })
end, { silent = true, desc = 'Yank relative path' })

-- Yank absolute path
map('n', '<Leader>Yp', function()
	local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':p')
	vim.fn.setreg('+', path)
	vim.notify(path, vim.log.levels.INFO, { title = 'Yanked absolute path' })
end, { silent = true, desc = 'Yank absolute path' })

-- Paste in visual-mode without pushing to register
map('x', 'p', 'p:let @+=@0<CR>:let @"=@0<CR>', { silent = true, desc = 'Paste' })
map('x', 'P', 'P:let @+=@0<CR>:let @"=@0<CR>', { silent = true, desc = 'Paste In-place' })

-- Edit
-- ===

-- Macros
map('n', '<C-q>', 'q', { desc = 'Macro Prefix' })

-- Insert new lines above and below
map('n', 'oo', 'o<ESC>', { desc = 'Insert newline below' })
map('n', 'OO', 'O<ESC>', { desc = 'Insert newline below' })

-- keep regular behavior
map('n', 'O', 'O', { desc = 'Insert newline below' })
map('n', 'o', 'o', { desc = 'Insert newline below' })

-- Start new line from any cursor position in insert-mode
map('i', '<S-Return>', '<C-o>o', { desc = 'Start Newline' })

-- Split and join lines
map('n', '<leader>jj', ':join<CR>g$', { desc = 'Join lines', silent = true })
map('n', '<leader>jJ', 'k:join<CR>j', { desc = 'Join lines', silent = true })
map('n', '<leader>ss', 'i<CR><ESC>', { desc = 'Split lines', silent = true })
map('n', '<leader>sS', 'i<CR><ESC>kg$', { desc = 'Split lines', silent = true })

-- Re-select blocks after indenting in visual/select mode
map('x', '<', '<gv', { desc = 'Indent Right and Re-select' })
map('x', '>', '>gv|', { desc = 'Indent Left and Re-select' })

-- Arrows to move identation in normal mode
map('n', '<LEFT>', '<<', { desc = 'Indent Right and Re-select' })
map('n', '<RIGHT>', '>>', { desc = 'Indent Left and Re-select' })
map('v', '<RIGHT>', '>gv|', { desc = 'Indent Right and Re-select' })
map('v', '<LEFT>', '<gv', { desc = 'Indent Left and Re-select' })

-- Use tab for indenting in visual/select mode
map('x', '<Tab>', '>gv|', { desc = 'Indent Left' })
map('x', '<S-Tab>', '<gv', { desc = 'Indent Right' })

-- Drag current line/s vertically and auto-indent with Arrow up/down
map('n', '<UP>', '<cmd>move-2<CR>==', { desc = 'Move line up' })
map('n', '<DOWN>', '<cmd>move+<CR>==', { desc = 'Move line down' })
map('x', '<UP>', ":move'<-2<CR>gv=gv", { desc = 'Move selection up' })
map('x', '<DOWN>', ":move'>+<CR>gv=gv", { desc = 'Move selection down' })

-- Remove spaces at the end of lines
map('n', '<Leader>fw', '<cmd>lua MiniTrailspace.trim()<CR>', { desc = 'Erase Whitespace' })

-- Search & Replace
-- ===

-- Make marks with m[some key]
-- go to mark with #[some key]
map('n', '#', '\'')

-- Clear search with <Esc>
map('n', '<Esc>', '<cmd>noh<CR>', { desc = 'Clear Search Highlight' })

-- Clear search, diff update and redraw taken from runtime/lua/_editor.lua
map(
	'n',
	'<leader>ur',
	'<cmd>nohlsearch<bar>diffupdate<bar>normal! <C-L><CR>',
	{ desc = 'Redraw / clear hlsearch / diff update' }
)

-- Use backspace key for matching parens
map({ 'n', 'x' }, '<BS>', '%', { remap = true, desc = 'Jump to Paren' })

-- Select last paste
map('n', 'gpp', "'`['.strpart(getregtype(), 0, 1).'`]'", { expr = true, desc = 'Select Paste' })

-- Quick substitute within selected area
map('x', '<leader>Sub', ':s//gc<Left><Left><Left>', { desc = 'Substitute Within Selection' })

-- Command & History
-- ===

-- Start an external command with a single bang
map('n', '!', ':!', { desc = 'Execute Shell Command' })

-- Put vim command output into buffer
--map('n', 'g!', ":put=execute('')<Left><Left>", { desc = 'Paste Command' })

-- Allow misspellings
--vim.cmd.cnoreabbrev('qw', 'wq')
--vim.cmd.cnoreabbrev('Wq', 'wq')
--vim.cmd.cnoreabbrev('WQ', 'wq')
--vim.cmd.cnoreabbrev('Qa', 'qa')
--vim.cmd.cnoreabbrev('Bd', 'bd')
--vim.cmd.cnoreabbrev('bD', 'bd')

-- File operations
-- ===

-- Switch (window) to the directory of the current opened buffer
map('n', '<Leader>cd', function()
	local bufdir = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':p:h')
	if bufdir ~= nil and vim.loop.fs_stat(bufdir) then
		vim.cmd.tcd(bufdir)
		vim.notify(bufdir)
	end
end, { desc = 'Change Local Directory' })

-- Fast saving from all modes
--map('n', '<Leader>w', '<cmd>write<CR>', { desc = 'Save' })
--map({ 'n', 'i', 'v' }, '<C-s>', '<cmd>write<CR>', { desc = 'Save' })

-- Editor UI
-- ===

-- Toggle editor's visual effects
map('n', '<leader>uf', require('plex.plugins.lsp.format').toggle, { desc = 'Toggle format on Save' })
map('n', '<Leader>us', '<cmd>setlocal spell!<CR>', { desc = 'Toggle Spellcheck' })
map('n', '<Leader>ul', '<cmd>setlocal nonumber!<CR>', { desc = 'Toggle Line Numbers' })
map('n', '<Leader>ulr', '<cmd>setlocal nornu!<CR>', { desc = 'Toggle Relative Line Numbers' })
map('n', '<Leader>uo', '<cmd>setlocal nolist!<CR>', { desc = 'Toggle Whitespace Symbols' })

if vim.lsp.inlay_hint then
	map('n', '<leader>uh', function() vim.lsp.inlay_hint(0, nil) end, { desc = 'Toggle Inlay Hints' })
end

-- Smart wrap toggle (breakindent and colorcolumn toggle as-well)
map('n', '<Leader>uw', function()
	vim.opt_local.wrap = not vim.wo.wrap
	vim.opt_local.breakindent = not vim.wo.breakindent

	if vim.wo.colorcolumn == '' then
		vim.opt_local.colorcolumn = tostring(vim.bo.textwidth)
	else
		vim.opt_local.colorcolumn = ''
	end
end, { desc = 'Toggle Wrap' })

-- Tabs: Many ways to navigate them
map('n', '<A-j>', '<cmd>tabprevious<CR>', { desc = 'Previous Tab' })
map('n', '<A-k>', '<cmd>tabnext<CR>', { desc = 'Next Tab' })

-- New and Close
map('n', '<A-t>', '<cmd>tabnew<CR>', { desc = 'New Tab' })
map('n', '<A-c>', '<cmd>tabclose<CR>', { desc = 'Close Tab' })

-- Moving tabs
map('n', '<A-S-j>', '<cmd>-tabmove<CR>', { desc = 'Tab Move Backwards' })
map('n', '<A-S-k>', '<cmd>+tabmove<CR>', { desc = 'Tab Move Forwards' })

-- Show treesitter nodes under cursor
-- highlights under cursor
if vim.fn.has('nvim-0.9') == 1 then
	map('n', '<Leader>ui', vim.show_pos, { desc = 'Show Treesitter Node' })
end

-- Custom Tools
-- ===

-- Append mode-line to current buffer
map('n', '<Leader>ml', function()
	require('plex.lib.edit').append_modeline()
end, { desc = 'Append Modeline' })

-- Jump entire buffers throughout jumplist
--map('n', 'g<C-i>', function()
--	require('plex.lib.edit').jump_buffer(1)
--end, { desc = 'Jump to newer buffer' })
--map('n', 'g<C-o>', function()
--	require('plex.lib.edit').jump_buffer(-1)
--end, { desc = 'Jump to older buffer' })

-- Context aware menu. See lua/lib/contextmenu.lua
map('n', '<LocalLeader>c', function()
	require('plex.lib.contextmenu').show()
end, { desc = 'Content-aware menu' })

-- Lazygit
map('n', '<leader>tg', function() Util.float_term({ 'lazygit' }, { cwd = Util.get_root(), esc_esc = false }) end, { desc = 'Lazygit (root dir)' })

-- Floating terminal
map('t', '<Esc><Esc>', '<C-\\><C-n>', { desc = 'Enter Normal Mode' })
map('t', '<C-w>', '<C-\\><C-n>', { desc = 'Enter Normal Mode' })
map('n', '<F12>', function() Util.float_term(nil, { cwd = Util.get_root() }) end, { desc = 'Terminal (root dir)' })
map('t', '<F12>', function() Util.float_term(nil, { cwd = Util.get_root() }) end, { desc = 'Terminal (root dir)' })

if vim.fn.has('mac') then
	-- Open the macOS dictionary on current word
	map('n', '<Leader>?', '<cmd>silent !open dict://<cword><CR>', { desc = 'Dictionary' })

	-- Use Marked for real-time Markdown preview
	-- See: https://marked2app.com/
	if vim.fn.executable('/Applications/Marked 2.app') then
		vim.api.nvim_create_autocmd('FileType', {
			group = augroup('marked_preview'),
			pattern = 'markdown',
			callback = function()
				local cmd = "<cmd>silent !open -a Marked\\ 2.app '%:p'<CR>"
				map('n', '<Leader>P', cmd, { desc = 'Markdown Preview' })
			end,
		})
	end
end

-- Windows, buffers and tabs
-- ===

-- Ultimatus Quitos
-- Use Q to quit whatever
if vim.F.if_nil(vim.g.plex_window_q_mapping, true) then
	vim.api.nvim_create_autocmd({ 'BufWinEnter', 'VimEnter' }, {
		group = augroup('quit_mapping'),
		callback = function(event)
			if vim.bo.buftype == '' and vim.fn.maparg('Q', 'n') == '' then
				local args = { buffer = event.buf, desc = 'Quit' }
				map('n', 'Q', '<cmd>quit<CR>', args)
			end
		end,
	})
end

-- Toggle quickfix window
map('n', '<Leader>q', function()
	require('plex.lib.edit').toggle_list('quickfix')
end, { desc = 'Open Quickfix' })

-- Set locations with diagnostics and open the list.
map('n', '<Leader>a', function()
	if vim.bo.filetype ~= 'qf' then
		vim.diagnostic.setloclist({ open = false })
	end
	require('plex.lib.edit').toggle_list('loclist')
end, { desc = 'Open Location List' })

map('n', '<leader>%', '<cmd>split<CR>', { desc = 'Split window horizontally' })
map('n', '<leader>"', '<cmd>vsplit<CR>', { desc = 'Split window vertically' })

map('n', '<leader>wb', '<cmd>buffer#<CR>', { desc = 'Alternate buffer' })
map('n', '<leader>wc', '<cmd>close<CR>', { desc = 'Close window' })
map('n', '<leader>wd', '<cmd>bdelete<CR>', { desc = 'Buffer delete' })
map('n', '<leader>w%', '<cmd>split<CR>', { desc = 'Split window horizontally' })
map('n', '<leader>w"', '<cmd>vsplit<CR>', { desc = 'Split window vertically' })
map('n', '<leader>wt', '<cmd>tabnew<CR>', { desc = 'New tab' })
map('n', '<leader>wtp', '<cmd>tabprevious<CR>', { desc = 'Previous tab' })
map('n', '<leader>wtn', '<cmd>tabnext<CR>', { desc = 'Next tab' })
map('n', '<leader>wtc', '<cmd>tabclose<CR>', { desc = 'Close tab' })
map('n', '<leader>wo', '<cmd>only<CR>', { desc = 'Close other windows' })
map('n', '<leader>wq', '<cmd>quit<CR>', { desc = 'Quit' })
map('n', '<leader>wz', '<cmd>vertical resize | resize | normal! ze<CR>', { desc = 'Maximize' })
map('n', '<leader>wx', function()
	require('mini.bufremove').delete(0, false)
	vim.cmd.enew()
end, { desc = 'Delete buffer and open new' })

-- Background dark/light toggle
map('n', '<leader>uh', function()
	if vim.o.background == 'dark' then
		vim.o.background = 'light'
	else
		vim.o.background = 'dark'
	end
end, { desc = 'Toggle background dark/light' })
