local M = {}
local wk = require('which-key')

local function setup_mappings(bufnr)
    local function list_workspace_folders()
        local folders = vim.inspect(vim.lsp.buf.list_workspace_folders())
        vim.notify(folders, vim.log.levels.INFO)
    end

    local lsp_mappings = {
        g = {
            d = { '<cmd>lua vim.lsp.buf.definition()<CR>', 'Goto definition' },
            D = { '<cmd>lua vim.lsp.buf.declaration()<CR>', 'Goto declaration' },
            i = { '<cmd>lua vim.lsp.buf.implementation()<CR>', 'Implementations' },
            I = { '<cmd>Telescope lsp_implementations<CR>', 'Implementations (Telescope)' },
            r = { '<cmd>TroubleToggle lsp_references<CR>', 'References' },
            R = { '<cmd>Telescope lsp_references<CR>', 'References (Telescope)' },
        },
        K = { '<cmd>lua vim.lsp.buf.hover()<CR>', 'Hover doc' },
        ['[d'] = { '<cmd>lua vim.diagnostic.goto_prev()<CR>', 'Previous diagnostics' },
        [']d'] = { '<cmd>lua vim.diagnostic.goto_next()<CR>', 'Next diagnostics' },
    }
    local lsp_leader_mappings = {
        l = {
            name = 'LSP',
            a = { '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', 'Add workspace folder' },
            b = { '<cmd>TroubleToggle document_diagnostics<CR>', 'Buffer diagnostics' },
            B = { '<cmd>Telescope diagnostics bufnr=0<CR>', 'Buffer diagnostics (Telescope)' },
            c = { '<cmd>lua vim.lsp.buf.code_action()<CR>', 'Code action' },
            C = { '<cmd>Telescope lsp_code_actions<CR>', 'Code action (Telescope)' },
            d = { '<cmd>TroubleToggle lsp_definitions<CR>', 'Definitions' },
            D = { '<cmd>Telescope lsp_definitions<CR>', 'Definitions (Telescope)' },
            e = { '<cmd>lua vim.diagnostic.open_float()<CR>', 'Line diagnostics' },
            g = { '<cmd>SymbolsOutline<CR>', 'Symbol outline' },
            i = { '<cmd>LspInfo<CR>', 'Lsp info' },
            l = { '<cmd>TroubleToggle loclist<CR>', 'Diagnostics loclist' },
            n = { '<cmd>lua vim.lsp.buf.rename()<CR>', 'Rename' },
            r = { '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', 'Remove workspace folder' },
            s = { '<cmd>Telescope lsp_document_symbols<CR>', 'Buffer symbols' },
            S = { '<cmd>Telescope lsp_workspace_symbols<CR>', 'Workspace symbols' },
            t = { '<cmd>TroubleToggle lsp_type_definitions<CR>', 'Type definitions' },
            T = { '<cmd>Telescope lsp_type_definitions<CR>', 'Type definitions (Telescope)' },
            w = { '<cmd>TroubleToggle workspace_diagnostics<CR>', 'Workspace diagnostics' },
            W = { '<cmd>Telescope diagnostics<CR>', 'Workspace diagnostics (Telescope)' },
            x = { '<cmd>lua vim.lsp.buf.signature_help()<CR>', 'Signature help' },
            y = { list_workspace_folders, 'List workspace folders' },
        },
    }
    local lsp_visual_mappings = {
        l = {
            name = 'LSP',
            c = { ':<C-U>lua vim.lsp.buf.range_code_action()<CR>', 'Range code action' },
            C = { ':Telescope lsp_range_code_actions<CR>', 'Range code action (Telescope)' },
        },
    }

    wk.register(lsp_mappings, { buffer = bufnr })
    wk.register(lsp_leader_mappings, { buffer = bufnr, prefix = '<leader>' })
    wk.register(lsp_visual_mappings, { buffer = bufnr, prefix = '<leader>', mode = 'x' })

    local ft = vim.api.nvim_buf_get_option(bufnr, 'filetype')
    -- clangd SwitchSourceHeader
    if ft == 'c' or ft == 'cpp' then
        wk.register({
            l = {
                name = 'LSP',
                h = { '<cmd>ClangdSwitchSourceHeader<CR>', 'Switch source header (clangd)' },
            },
        }, { buffer = bufnr, prefix = '<localleader>' })
    end
end

local function setup_formatting_mapping(client, bufnr)
    if client.resolved_capabilities.document_formatting then
        wk.register({
            ['lo'] = { '<cmd>lua vim.lsp.buf.formatting()<CR>', 'Format buffer' },
        }, { buffer = bufnr, prefix = '<leader>' })
        -- vim.api.nvim_exec(
        --     [[
        -- augroup lsp_autoformat
        -- autocmd! * <buffer>
        -- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()
        -- augroup END
        -- ]],
        --     false
        -- )
    elseif client.resolved_capabilities.document_range_formatting then
        wk.register({
            ['lo'] = { '<cmd>lua vim.lsp.buf.range_formatting({},{0,0},{vim.fn.line("$"),0})<CR>', 'Format buffer' },
        }, { buffer = bufnr, prefix = '<leader>' })
        -- vim.api.nvim_exec(
        --     [[
        -- augroup lsp_autoformat
        -- autocmd! * <buffer>
        -- autocmd BufWritePre <buffer> lua vim.lsp.buf.range_formatting({},{0,0},{vim.fn.line("$"),0})
        -- augroup END
        -- ]],
        --     false
        -- )
    end

    if client.resolved_capabilities.document_range_formatting then
        wk.register({
            ['lo'] = { '<cmd>lua vim.lsp.buf.range_formatting()<CR>', 'Format range' },
        }, { buffer = bufnr, prefix = '<leader>', mode = 'x' })
    end
end

-- local function setup_document_highlight(client)
--     if client.resolved_capabilities.document_highlight then
--         vim.api.nvim_exec(
--             [[
--         augroup lsp_document_highlight
--         autocmd! * <buffer>
--         autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
--         autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
--         augroup END
--         ]],
--             false
--         )
--     end
-- end

local function setup_codelens(client)
    if client.resolved_capabilities.code_lens then
        vim.api.nvim_exec(
            [[
        augroup lsp_code_lens_refresh
        autocmd! * <buffer>
        autocmd InsertLeave <buffer> lua vim.lsp.codelens.refresh()
        autocmd InsertLeave <buffer> lua vim.lsp.codelens.display()
        augroup END
        ]],
            false
        )
    end
end

local function setup_codeaction_prompt(client)
    if client.resolved_capabilities.code_action then
        vim.api.nvim_exec(
            [[
        augroup lsp_code_action_prompt
        autocmd! * <buffer>
        autocmd CursorHold <buffer> lua require('user.modules.lsp.util').update_codeaction_prompt()
        augroup END
        ]],
            false
        )
    end
end

function M.update_codeaction_prompt()
    local lightbulb = require('nvim-lightbulb')
    lightbulb.update_lightbulb {
        sign = { enabled = false },
        status_text = {
            enabled = true,
            text = ' Code action',
            text_unavailable = '',
        },
    }
end

function M.common_on_attach(client, bufnr)
    -- vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
    setup_mappings(bufnr)
    setup_codelens(client)
    setup_codeaction_prompt(client)
    -- setup_document_highlight(client)
    setup_formatting_mapping(client, bufnr)
end

function M.common_capabilities()
    local capabilities = vim.lsp.protocol.make_client_capabilities()
    -- Add additional capabilities supported by nvim-cmp
    local completionItem = capabilities.textDocument.completion.completionItem
    completionItem.snippetSupport = true
    completionItem.preselectSupport = true
    completionItem.insertReplaceSupport = true
    completionItem.labelDetailsSupport = true
    completionItem.deprecatedSupport = true
    completionItem.commitCharactersSupport = true
    completionItem.tagSupport = { valueSet = { 1 } }
    completionItem.resolveSupport = {
        properties = {
            'documentation',
            'detail',
            'additionalTextEdits',
        },
    }

    return capabilities
end

function M.lua_lib_path()
    local lib_path = {}

    -- lib_path[vim.fn.expand('~/.luarocks/share/lua/5.4')] = true
    -- lib_path[vim.fn.expand('/usr/share/lua/5.4')] = true
    lib_path[os.getenv('VIMRUNTIME') .. '/lua'] = true
    -- local function add(lib)
    --     for _, p in pairs(vim.fn.expand(lib .. '/lua', false, true)) do
    --         p = vim.loop.fs_realpath(p)
    --         if p then lib_path[p] = true end
    --     end
    -- end

    -- for _, site in pairs(vim.split(vim.opt.packpath:get(), ',')) do
    --     add(site .. '/pack/*/opt/*')
    --     add(site .. '/pack/*/start/*')
    -- end

    return lib_path
end

function M.lua_runtime_path()
    local lib_path = M.lua_lib_path()
    local runtime_path = {}

    table.insert(runtime_path, 'lua/?.lua')
    table.insert(runtime_path, 'lua/?/init.lua')
    -- table.insert(runtime_path, '?.lua')
    -- table.insert(runtime_path, '?/?.lua')
    -- table.insert(runtime_path, '?/init.lua')

    for lib, _ in pairs(lib_path) do
        table.insert(runtime_path, lib .. '/?.lua')
        table.insert(runtime_path, lib .. '/?/init.lua')
    end

    return runtime_path
end

-- https://github.com/neovim/nvim-lspconfig/wiki/User-contributed-tips#switchsourceheader
function M.switch_source_header_splitcmd(bufnr, splitcmd)
    local notify = require('notify')
    bufnr = require('lspconfig').util.validate_bufnr(bufnr)
    local clangd_client = require('lspconfig').util.get_active_client_by_name(bufnr, 'clangd')
    local params = { uri = vim.uri_from_bufnr(bufnr) }
    if clangd_client then
        clangd_client.request('textDocument/switchSourceHeader', params, function(err, result)
            if err then
                error(tostring(err))
            end
            if not result then
                notify('Corresponding file can’t be determined', vim.log.levels.ERROR)
                return
            end
            vim.api.nvim_command(splitcmd .. ' ' .. vim.uri_to_fname(result))
        end, bufnr)
    else
        notify(
            'textDocument/switchSourceHeader is not supported by the clangd server active on the current buffer',
            vim.log.levels.ERROR
        )
    end
end

return M
