
--- @class LSPServerOptions : LSPFeatures
--- @field server_capabilities? lsp.ServerCapabilities
--- @field on_save function | nil
--- @field mason? boolean

--- @class LSPFeatures
--- @field document_highlight? { enabled: boolean }
--- @field codelens? { enabled: boolean }
--- @field inlay_hints? { enabled: boolean, exclude?: table }

--- @type LSPServerOptions
local defaultServerOpts = {
    server_capabilities = nil,
    on_save = nil,
    mason = true,
}

--- @class LSPOptions
local options = {
    --- @type LSPFeatures
    features = {
        document_highlight = {
            enabled = true,
        },
        codelens = {
            enabled = false,
        },
        inlay_hints = {
            enabled = true,
            exclude = {},
        },
    },
    --- @type { string: LSPServerOptions }
    servers = {},
}

--- @param opts LSPOptions
local config = function(_, opts)
    local utils = require("user.utils.lsp")
    local augroup = vim.api.nvim_create_augroup("Lsp", {})
    local capabilities = vim.lsp.protocol.make_client_capabilities()

    --- @param server string
    local function setup(server)

        local server_opts = opts.servers[server] or {}

        if type(server_opts) == "function" then
            server_opts = server_opts()
        end

        -- Set missing features to default ones in server options.
        server_opts = vim.tbl_deep_extend("keep", server_opts, opts.features)

        ---@param client vim.lsp.Client
        ---@param bufnr number
        local on_attach = function(client, bufnr)
            vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })

            -- Override any server capabilities.
            client.server_capabilities = vim.tbl_extend("force",
                client.server_capabilities,
                server_opts.server_capabilities or {})

            if server_opts.inlay_hints.enabled and client:supports_method("textDocument/inlayHint") then
                vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
            end

            if server_opts.codelens.enabled and client:supports_method("textDocument/codeLens") then
                vim.lsp.codelens.enable(true, { bufnr = bufnr })
            end

            if server_opts.document_highlight.enabled and client:supports_method("textDocument/documentHighlight") then
                utils.document_highlight(bufnr)
            end

            -- Setup on save handler
            if server_opts.on_save then
                vim.api.nvim_create_autocmd("BufWritePre", {
                    group = augroup,
                    buffer = bufnr,
                    callback = server_opts.on_save,
                })
            end
        end

        server_opts = vim.tbl_deep_extend("force", {
            on_attach = on_attach,
            capabilities = capabilities
        }, server_opts or {})

        vim.lsp.config(server, server_opts)
        vim.lsp.enable(server)
    end

    local have_mason, mlsp = pcall(require, "mason-lspconfig")

    local all_mslp_servers = {}
    if have_mason then
        -- Need to refresh the registry, otherwise mlsp does not find any servers.
        require("mason-registry").refresh()
        all_mslp_servers = vim.tbl_keys(require("mason-lspconfig").get_mappings().lspconfig_to_package)
    end

    local ensure_installed = {} ---@type string[]
    for name, server_opts in pairs(opts.servers) do
        setup(name)

        if type(server_opts) == "function" then
            server_opts = server_opts()
        end

        server_opts = vim.tbl_deep_extend("keep", server_opts, defaultServerOpts)
        if server_opts.mason == true and vim.tbl_contains(all_mslp_servers, name) then
            ensure_installed[#ensure_installed + 1] = name
        end
    end

    if have_mason then
        mlsp.setup({
            automatic_enable = false,
            ensure_installed = ensure_installed,
        })
    end
end

return {
    "neovim/nvim-lspconfig",
    event = { "BufReadPre", "BufNewFile" },
    dependencies = {
        {
            "mason-org/mason.nvim",
            version = "^2.0.1",
            optional = true,
            dependencies = {
                { "mason-org/mason-lspconfig.nvim", version = "^2.1.0", config = function () end }
            }
        }
    },
    opts = options,
    config = config
}
