local M = {}
local api = vim.api
local opts = { noremap = true, silent = true }

local function toggle_tabs_spaces()
    local notify = require('notify')
    if vim.opt.expandtab:get() then
        vim.opt.expandtab = false
        vim.opt.smarttab = false
        vim.opt.softtabstop = 0 -- reset to default
        notify('Indent with Tabs.', vim.log.levels.INFO)
    else
        vim.opt.expandtab = true
        vim.opt.smarttab = true
        vim.opt.softtabstop = -1 -- fallback to shiftwidth
        notify('Indent with Spaces.', vim.log.levels.INFO)
    end
end

local function toggle_venn()
    local notify = require('notify')
    local venn_enabled = vim.inspect(vim.b.venn_enabled)
    if venn_enabled == 'nil' then
        vim.b.venn_enabled = true
        api.nvim_command('setlocal virtualedit=all')
        -- Draw lines with WASD keystroke
        api.nvim_buf_set_keymap(0, 'n', 'A', '<C-v>h:VBox<CR>', opts)
        api.nvim_buf_set_keymap(0, 'n', 'S', '<C-v>j:VBox<CR>', opts)
        api.nvim_buf_set_keymap(0, 'n', 'W', '<C-v>k:VBox<CR>', opts)
        api.nvim_buf_set_keymap(0, 'n', 'D', '<C-v>l:VBox<CR>', opts)
        -- Draw boxes by pressing 'f' with visual selection
        api.nvim_buf_set_keymap(0, 'x', 'f', ':VBox<CR>', opts)
        notify('Virtual box edit enabled.', vim.log.levels.INFO)
    else
        vim.b.venn_enabled = nil
        api.nvim_command('setlocal virtualedit=block')
        api.nvim_buf_del_keymap(0, 'x', 'f')
        api.nvim_buf_del_keymap(0, 'n', 'A')
        api.nvim_buf_del_keymap(0, 'n', 'S')
        api.nvim_buf_del_keymap(0, 'n', 'W')
        api.nvim_buf_del_keymap(0, 'n', 'D')
        notify('Virtual box edit disabled.', vim.log.levels.INFO)
    end
end

M.normal_map = {
    -- Close a window
    ['<C-q>'] = { '<C-w>q', 'Quit current window' },
    -- Close current buffer
    ['<A-q>'] = { '<cmd>bdelete<CR>', 'Close current buffer' },
    -- Copy the whole buffer
    ['<A-a>'] = { '<cmd>%y+<CR>', 'Copy whole buffer' },
    -- Remove trailing whitespace
    ['<A-w>'] = { '<cmd>%s/\\s\\+$//e<CR>', 'Remove trailing' },
    -- Resize buffer
    ['<A-j>'] = { '<cmd>resize -2<CR>', 'Resize vertical -2' },
    ['<A-k>'] = { '<cmd>resize +2<CR>', 'Resize vertical +2' },
    ['<A-h>'] = { '<cmd>vertical resize -2<CR>', 'Resize horizontal -2' },
    ['<A-l>'] = { '<cmd>vertical resize +2<CR>', 'Resize horizontal +2' },
    -- Switch between tabs and spaces
    ['<A-t>'] = { toggle_tabs_spaces, 'Switch indent style' },
    -- Naming common keys
    ['['] = {
        name = 'Block motions (previous)',
        g = 'Previous git hunk',
    },
    [']'] = {
        name = 'Block motions (next)',
        g = 'Next git hunk',
    },
    g = {
        name = 'Goto motions',
        o = { '<cmd>TodoTrouble<CR>', 'Todo list' },
    },
    z = { name = 'Misc utils' },
    -- Move between tabs
    ['<TAB>'] = { '<cmd>BufferLineCycleNext<CR>', 'Next buffer' },
    ['<S-TAB>'] = { '<cmd>BufferLineCyclePrev<CR>', 'Previous buffer' },
    -- ToggleTerm
    ['<C-\\>'] = { '<cmd>ToggleTerm<CR>', 'Toggle terminal' },
    ['<A-\\>'] = { '<cmd>ToggleTerm direction=float<CR>', 'Toggle float terminal' },
    -- hop.nvim
    S = { '<cmd>HopWord<CR>', 'Hop to word' },
    ['<C-s>'] = { '<cmd>HopLine<CR>', 'Hop to character' },
    ['<A-s>'] = { '<cmd>HopPattern<CR>', 'Hop to pattern' },
}

M.normal_with_leader_map = {
    d = {
        name = 'DAP',
        b = 'Toggle breakpoint',
        B = 'Set breakpoint with condition',
        c = 'Run to cursor',
        d = 'Disconnect adapter',
        e = 'Hover under cursor (UI)',
        f = 'Open float window (UI)',
        i = 'Step into function/method',
        l = 'List breakpoints',
        n = 'Resume the execution',
        o = 'Step out of function/method',
        r = 'Inspect in REPL',
        t = 'Toggle UI',
        v = 'Run again 1 step',
        z = 'Re-run the last adapter',
    },
    e = {
        name = 'Editor',
        a = { '<cmd>EasyAlign<CR>', 'Align elements' },
        g = { '<cmd>Neogen<CR>', 'Generate annotations' },
        h = { '<cmd>TSHighlightCapturesUnderCursor<CR>', 'Syntax under cursor' },
        s = { '<cmd>ISwapWith<CR>', 'Swap elements' },
        t = { '<cmd>TableModeToggle<CR>', 'Toggle table mode' },
        v = { toggle_venn, 'Toggle virtual box edit' },
        z = { '<cmd>ZenMode<CR>', 'Zen mode' },
        Z = { '<cmd>Twilight<CR>', 'Twilight mode' },
    },
    f = {
        name = 'Telescope',
        a = { '<cmd>Telescope autocommands<CR>', 'Autocommands' },
        b = { '<cmd>Telescope buffers<CR>', 'Buffers' },
        c = { '<cmd>Telescope commands<CR>', 'Commands' },
        d = { '<cmd>Telescope projects<CR>', 'Recent directories' },
        e = { '<cmd>Telescope file_browser<CR>', 'File browser' },
        f = { '<cmd>Telescope find_files<CR>', 'Find files' },
        g = { '<cmd>Telescope live_grep<CR>', 'Live grep' },
        h = { '<cmd>Telescope help_tags<CR>', 'Help tags' },
        i = { '<cmd>Telescope highlights<CR>', 'Highlight groups' },
        j = {
            name = 'Symbols',
            g = 'Gitmoji',
            h = { '<cmd>Telescope symbols<CR>', 'All symbols' },
            j = 'Emoji',
            k = 'Kaomoji',
            l = 'Julia',
            m = 'Math symbols',
            n = 'Nerd-fonts symbols',
            t = 'Latex symbols',
        },
        k = { '<cmd>Telescope keymaps<CR>', 'Normal keymaps' },
        l = { '<cmd>Telescope notify<CR>', 'Notification history' },
        m = { '<cmd>Telescope marks<CR>', 'Bookmarks' },
        n = { '<cmd>Telescope man_pages<CR>', 'Man pages' },
        o = { '<cmd>Telescope oldfiles<CR>', 'Recent files' },
        p = { '<cmd>Telescope project display_type=full<CR>', 'Projects' },
        r = { '<cmd>Telescope reloader<CR>', 'Reload lua modules' },
        s = { '<cmd>Telescope treesitter<CR>', 'Treesitter' },
        t = { '<cmd>TodoTelescope<CR>', 'Todo list' },
        u = { '<cmd>Telescope current_buffer_fuzzy_find<CR>', 'Search current buffer' },
        v = { '<cmd>Telescope vim_options<CR>', 'Vim options' },
        x = {
            name = 'Tasks',
            a = { '<cmd>Telescope yabs tasks<CR>', 'All tasks' },
            g = { '<cmd>Telescope yabs global_tasks<CR>', 'Global tasks' },
            l = { '<cmd>Telescope yabs current_language_tasks<CR>', 'Tasks for current filetype' },
            t = { '<cmd>YabsDefaultTask<CR>', 'Run default task' },
        },
        y = { '<cmd>Telescope filetypes<CR>', 'Filetypes' },
        z = { '<cmd>Telescope registers<CR>', 'Vim registers' },
    },
    g = {
        name = 'Git',
        a = { '<cmd>Telescope git_stash<CR>', 'Stash' },
        b = 'Blame current line',
        c = { '<cmd>Telescope git_bcommits<CR>', 'Buffer commits' },
        C = { '<cmd>Telescope git_commits<CR>', 'Commits' },
        d = 'Show diff against the index',
        D = 'Show diff against last commit',
        m = { '<cmd>Telescope git_branches<CR>', 'Branches' },
        n = { '<cmd>Neogit<CR>', 'Neogit' },
        o = { '<cmd>Octo actions<CR>', 'Octo' },
        p = 'Preview hunk',
        r = 'Reset hunk',
        R = 'Reset all hunks in buffer',
        s = 'Stage hunk',
        S = 'Stage buffer',
        t = { '<cmd>Telescope git_status<CR>', 'Status' },
        u = 'Undo hunk',
        U = 'Reset buffer index',
        y = 'Get remote url for cursorline',
        Y = 'Get remote url',
        z = 'Toggle showing deleted hunk',
    },
    n = { '<cmd>NvimTreeToggle<CR>', 'NvimTree' },
    p = {
        name = 'Find/Replace',
        o = 'Open spectre',
        p = 'Search in current file',
        w = 'Find/Replace cursorword',
    },
    r = {
        name = 'REST',
        c = { '<Plug>RestNvim', 'Run request under cursor' },
        p = { '<Plug>RestNvimPreview', 'Preview request cURL command' },
        l = { '<Plug>RestNvimLast', 'Re-run last request' },
    },
    s = {
        name = 'Session',
        s = 'Save session for current directory',
        d = 'Stop automatically saving',
        l = 'Load last session',
        r = 'Load session for current directory',
    },
    w = { '<cmd>WindowPick<CR>', 'Pick window' },
}

M.visual_with_leader_map = {
    e = {
        name = 'Editor',
        a = { '<cmd>EasyAlign<CR>', 'Range align' },
    },
    d = {
        name = 'DAP',
        e = 'Hover on range (UI)',
    },
    g = {
        name = 'Git',
        r = 'Reset hunk',
        s = 'Stage hunk',
        y = 'Get remote url for range',
    },
    p = 'Find/Replace',
}

M.local_map = {
    c = { '<cmd>ColorizerToggle<CR>', 'Colorizer' },
    d = {
        name = 'Delete',
        a = { '<cmd>BDelete all<CR>', 'All buffers' },
        d = { '<cmd>BDelete this<CR>', 'Current buffer' },
        h = { '<cmd>BDelete hidden<CR>', 'Hidden buffers' },
        n = { '<cmd>BDelete nameless<CR>', 'Buffers without name' },
        o = { '<cmd>BDelete other<CR>', 'Other buffers' },
    },
    j = { '<cmd>BufferLineCyclePrev<CR>', 'Previous buffer' },
    k = { '<cmd>BufferLineCycleNext<CR>', 'Next buffer' },
    n = { '<cmd>enew<CR>', 'New buffer' },
    q = { '<cmd>q<CR>', 'Quit current buffer' },
    u = { '<cmd>UndotreeToggle<CR>', 'Undotree' },
}

M.markdown_map = {
    p = { '<cmd>MarkdownPreviewToggle<CR>', 'Preview Markdown file' },
}

M.tex_map = {
    p = { '<cmd>TexlabForward<CR>', 'Preview Latex file' },
}

M.org_map = {
    ['<leader>o'] = {
        name = 'Org',
        a = 'Agenda',
        A = 'Toggle ARCHIVE',
        c = 'Capture',
        e = 'Export',
        i = {
            name = 'Insert',
            d = 'Insert/Update deadline date',
            h = 'Add headline',
            s = 'Insert/Update scheduled date',
            t = 'Add TODO heading and content',
            T = 'Add TODO heading',
            ['.'] = 'Insert/Update under cursor',
            ['!'] = 'Insert/Update inactive date under cursor',
        },
        J = 'Move subtree down',
        K = 'Move subtree up',
        o = 'Open at point',
        r = 'Refile',
        t = 'Set tags',
        x = {
            name = 'Clock',
            e = 'Set effort estimate property',
            i = 'Clock in headline',
            j = 'Jump to currently clocked',
            o = 'Clock out headline',
            q = 'Cancel active clock',
        },
        ['$'] = 'Archive current headline',
        [','] = 'Choose headline priority',
        ['*'] = 'Toggle headline',
        ["'"] = 'Open source block',
    },
    ['<leader><CR>'] = 'Org meta return',
    ['<C-a>'] = 'Org increase date',
    ['<C-x>'] = 'Org decrease date',
    ['<S-Up>'] = 'Increase date by 1',
    ['<S-Down>'] = 'Decrease date by 1',
    ['cid'] = 'Org change date',
    ['cir'] = 'Org decrease headline priority',
    ['ciR'] = 'Org increase headline priority',
    ['cit'] = 'Org TODO',
    ['ciT'] = 'Org TODO prev',
    ['<C-Space>'] = 'Org toggle checkbox',
    ['<TAB>'] = 'Org cycle folding',
    ['<S-TAB>'] = 'Org cycle global folding',
    ['<<'] = 'Org promote headline',
    ['>>'] = 'Org demote headline',
    ['<s'] = 'Org prmote subtree',
    ['>s'] = 'Org demote subtree',
    ['}'] = 'Org next visible heading',
    ['{'] = 'Org previous visible heading',
    [']]'] = 'Org forward heading',
    ['[['] = 'Org backward heading',
    ['g{'] = 'Org parent heading',
    ['?'] = 'Org help',
}

function M.undefined_map()
    -- Easier start and end of line
    api.nvim_set_keymap('n', 'H', '^', { silent = true })
    api.nvim_set_keymap('n', 'L', '$', { silent = true })
    api.nvim_set_keymap('x', 'H', '^', { silent = true })
    api.nvim_set_keymap('x', 'L', '$', { silent = true })
    api.nvim_set_keymap('o', 'H', '^', { silent = true })
    api.nvim_set_keymap('o', 'L', '$', { silent = true })
    -- Escape to normal mode in terminal buffer
    api.nvim_set_keymap('t', '<Esc>', '<C-\\><C-n>', opts)
    -- Continuous indent
    api.nvim_set_keymap('x', '<', '<gv', opts)
    api.nvim_set_keymap('x', '>', '>gv', opts)
    -- Also move up/down virtual lines (:set wrap)
    api.nvim_set_keymap('n', 'j', 'v:count == 0 ? "gj" : "j"', { noremap = true, expr = true, silent = true })
    api.nvim_set_keymap('n', 'k', 'v:count == 0 ? "gk" : "k"', { noremap = true, expr = true, silent = true })
    -- winshift.nvim
    api.nvim_set_keymap('n', '<C-w>m', '<cmd>WinShift<CR>', opts)
    api.nvim_set_keymap('n', '<C-A-h>', '<cmd>WinShift left<CR>', opts)
    api.nvim_set_keymap('n', '<C-A-j>', '<cmd>WinShift down<CR>', opts)
    api.nvim_set_keymap('n', '<C-A-k>', '<cmd>WinShift up<CR>', opts)
    api.nvim_set_keymap('n', '<C-A-l>', '<cmd>WinShift right<CR>', opts)
end

return M
