local misc = require('core.misc')
local map, auto, augroup = misc.map, misc.auto, misc.augroup

return { 'williamboman/mason-lspconfig.nvim',
  requires = {
    'williamboman/mason.nvim',
    'mfussenegger/nvim-jdtls',
    'neovim/nvim-lspconfig'
  },
  function()
    local util = require('lspconfig.util')

    -- configure lsp when attached
    local function lsp_attach(client, bufnr)
      -- helper function(s)
      local function set_lsp_sign(name, text)
        vim.fn.sign_define(name, { text = text, texthl = name })
      end

      set_lsp_sign("DiagnosticSignError", "x")
      set_lsp_sign("DiagnosticSignWarn" , "!")
      set_lsp_sign("DiagnosticSignInfo" , "i")
      set_lsp_sign("DiagnosticSignHint" , "h")

      local opts = { buffer = bufnr }
      -- LSP actions
      map('n', 'K', vim.lsp.buf.hover, opts)
      map('n', 'gD', vim.lsp.buf.definition, opts)
      -- map('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<cr>')
      map('n', 'gI', vim.lsp.buf.implementation, opts)
      map('n', 'gY', vim.lsp.buf.type_definition, opts)
      map('n', 'gR', vim.lsp.buf.references, opts)
      map('n', '<S-Tab>', vim.lsp.buf.signature_help, opts)
      map('n', '<leader>lr', vim.lsp.buf.rename, opts)
      map('n', '<F2>', vim.lsp.buf.rename, opts)
      map('n', 'gA', vim.lsp.buf.code_action, {
        buffer = bufnr,
        desc = 'check code actions',
      })
      map('n', '<F4>', vim.lsp.buf.code_action, {
        buffer = bufnr,
        desc = 'check code actions'
      })

      -- Diagnostics
      map('n', '[d', vim.diagnostic.goto_prev)
      map('n', ']d', vim.diagnostic.goto_next)
    end

    -- setup lsp capabilities
    local capabilities = vim.lsp.protocol.make_client_capabilities()
    capabilities.textDocument.completion.completionItem = {
      documentationFormat = { "markdown", "plaintext" },
      snippetSupport = true,
      preselectSupport = true,
      insertReplaceSupport = true,
      labelDetailsSupport = true,
      deprecatedSupport = true,
      commitCharactersSupport = true,
      tagSupport = {
        valueSet = { 1 }
      },
      resolveSupport = {
        properties = {
          "documentation",
          "detail",
          "additionalTextEdits"
        }
      }
    }

    -- setup language servers
    require('mason-lspconfig').setup {
      ensure_installed = {
        "lua_ls",
        "clangd",
        "jdtls",
        "phpactor",
        "html",
        "cssls",
        "bashls",
        "zls"
        -- "asm-lsp", -- seems to be broken
      }
    }
    require('mason-lspconfig').setup_handlers {
      function(server_name)
        require('lspconfig')[server_name].setup {
          on_attach = lsp_attach,
          capabilities = capabilities
        }
      end,

      -- setup luals
      ["lua_ls"] = function(server_name)
        local root_files = { '.luarc.json', '.luarc.jsonc', '.luacheckrc',
          '.stylua.toml', 'stylua.toml', 'selene.toml', 'selene.yml',
          'README.md'
        }

        -- FIXME: luals seems to start up twice and sends back twice the
        -- completions (one configured with the below settings and one without)
        require('lspconfig')[server_name].setup {
          on_attach = lsp_attach,
          settings = {
            Lua = {
              diagnostics = {
                globals = { "vim", 'mp' }
              },
              runtime = {
                version = 'LuaJIT'
              },
              format = {
                enable = false
              },
              workspace = {
                checkThirdParty = false,
                library = {
                  vim.env.VIMRUNTIME
                }
              }
            }
          },

          root_dir = function(fname)
            local root = util.root_pattern(unpack(root_files))(fname)
            if root and root ~= vim.env.HOME then
              return root
            end

            root = util.root_pattern('lua/')(fname)
            if root then
              return root
            end
            return util.find_git_ancestor(fname)
          end
        }
      end,

      -- setup clangd
      ["clangd"] = function(server_name)
        require('lspconfig')[server_name].setup {
          on_attach = function(client, bufnr)
            lsp_attach(client, bufnr)

            -- add some clangd specific mappings
            local opts = { buffer = bufnr }
            map("n", "<leader>o", "<cmd>ClangdSwitchSourceHeader<CR>", opts)
          end,
          capabilities = capabilities,

          cmd = {
            "clangd",
            "--background-index",
            "--clang-tidy",
            "--header-insertion=iwyu",
            "--completion-style=detailed",
            "--function-arg-placeholders",
            "--fallback-style=llvm"
          },
          init_options = {
            usePlaceholders = true,
            clangdFileStatus = true,
            fallback_flags = {
              "-xc" -- makes clangd think we're using c instead of c++
            }
          }
        }
      end,

      -- setup jdtls
      ["jdtls"] = function(server_name)
        auto("Filetype", {
          pattern = "java",
          callback = function()
            -- must be a java interpreter of version 17 or greater
            local java = "java"

            local buffer = {}
            ---@type function
            local startlsp

            -- check if version of java in use is high enough
            vim.fn.jobstart({ java, vim.fn.stdpath('config').."/extras/JavaVersion.java" }, {
              stdin = nil,
              on_stdout = function(_, data, _)
                table.insert(buffer, table.concat(data))
              end,
              on_exit = function(_, exit_code, _)
                local v = vim.version.parse(table.concat(buffer))

                -- if there's an error, no version info, or the java version is
                -- less than 17 stop the lsp from starting
                if exit_code ~= 0 then
                  vim.notify(string.format(
                    "java version check failed: exit code %s", exit_code),
                    vim.log.levels.ERROR, { title = misc.appid })
                  return
                elseif not v then
                  vim.notify("no java version info found", vim.log.levels.ERROR,
                    { title = misc.appid })
                  return
                elseif v.major < 17 then
                  vim.notify(string.format(
                    "java version %s < 17.0.0 Cannot run jdtls, bailing out",
                    v[1].."."..v[2].."."..v[3]),
                    vim.log.levels.ERROR, { title = misc.appid })
                  return
                end

                startlsp()
              end
            })

            function startlsp()
              local ok, jdtls = pcall(require, "jdtls")
              if not ok then
                vim.notify("jdtls not found, can't start java lsp",
                  vim.log.levels.ERROR, {})
                return
              end

              local config = {}

              config.on_attach = function(client, bufnr)
                lsp_attach(client, bufnr)

                -- add some jdtls specific mappings
                local opts = { buffer = bufnr }
                map('n', 'cri', jdtls.organize_imports, opts)
                map('n', 'crv', jdtls.extract_variable, opts)
                map('n', 'crc', jdtls.extract_constant, opts)
                map('x', 'crv', "<esc><cmd>lua require('jdtls').extract_variable(true)<cr>", opts)
                map('x', 'crc', "<esc><cmd>lua require('jdtls').extract_constant(true)<cr>", opts)
                map('x', 'crm', "<esc><Cmd>lua require('jdtls').extract_method(true)<cr>", opts)

                -- refresh the codelens every time after writing the file
                local jdtls_cmds = augroup("jdtls_cmds")

                pcall(vim.lsp.codelens.refresh)
                auto('BufWritePost', {
                  buffer = bufnr,
                  group = jdtls_cmds,
                  desc = 'refresh codelens',
                  callback = function()
                    pcall(vim.lsp.codelens.refresh)
                  end
                })
              end

              -- setup path stuff
              local path = {}
              local jdtls_install = require('mason-registry').get_package('jdtls'):get_install_path()
              path.data_dir = vim.fn.stdpath('cache')..'/nvim-jdtls'
              path.java_agent = jdtls_install..'/lombok.jar'
              path.launcher_jar = vim.fn.glob(jdtls_install..'/plugins/org.eclipse.equinox.launcher_*.jar')
              path.platform_config = jdtls_install..'/config_linux'
              path.bundles = {}

              -- data dir
              local data_dir = path.data_dir..'/'..vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t')

              -- enable basic capabilities
              config.capabilities = capabilities

              -- enable some extended client capabilities
              local extendedClientCapabilities = jdtls.extendedClientCapabilities
              extendedClientCapabilities.resolveAdditionalTextEditsSupport = true

              -- command to start the lsp server
              config.cmd = {
                java, -- this has to be java17 or newer
                '-Declipse.application=org.eclipse.jdt.ls.core.id1',
                '-Dosgi.bundles.defaultStartLevel=4',
                '-Declipse.product=org.eclipse.jdt.ls.core.product',
                '-Dlog.protocol=true',
                '-Dlog.level=ALL',
                '-Xmx1G',
                '--add-modules=ALL-SYSTEM',
                '--add-opens', 'java.base/java.util=ALL-UNNAMED',
                '--add-opens', 'java.base/java.lang=ALL-UNNAMED',
                '-jar', path.launcher_jar,
                '-configuration', path.platform_config,
                '-data', data_dir
              }

              -- settings
              config.settings = {
                java = {
                  eclipse = { downloadSources = true },
                  gradle = { enabled = true },
                  maven = { downloadSources = true },
                  implementationsCodeLens = { enabled = true },
                  referencesCodeLens = { enabled = true },
                  references = { includeDecompiledSources = true },
                  symbols = { includeSourceMethodDeclarations = true },
                  inlayHints = {
                    parameterNames = {
                      enabled = "all"
                    }
                  },
                  completion = {
                    favoriteStaticMembers = {
                      "org.hamcrest.MatcherAssert.assertThat",
                      "org.hamcrest.Matchers.*",
                      "org.hamcrest.CoreMatchers.*",
                      "org.junit.jupiter.api.Assertions.*",
                      "java.util.Objects.requireNonNull",
                      "java.util.Objects.requireNonNullElse",
                      "org.mockito.Mockito.*"
                    },
                    filteredTypes = {
                      "com.sun.*",
                      "io.micrometer.shaded.*",
                      "java.awt.*",
                      "jdk.*",
                      "sun.*"
                    },
                    importOrder = {
                      "java",
                      "javax",
                      "com",
                      "org"
                    }
                  },
                  sources = {
                    organizeImports = {
                      starThreshold = 9999,
                      staticStarThreshold = 9999
                    }
                  },
                  codeGeneration = {
                    toString = {
                      template = '${object.className}{${member.name()}=${member.value}, ${otherMembers}}'
                    },
                    hashCodeEquals = { useJava7Objects = true },
                    useBlocks = true,
                  }
                }
              }
              config.signatureHelp = { enabled = true }
              config.flags = { allow_incremental_sync = true }

              -- disable all messages from printing
              config.handlers = {
                ['language/status'] = function() end
              }

              config.init_options = {
                extendedClientCapabilities = extendedClientCapabilities,
              }

              config.root_dir = vim.fs.root(0, { ".git", "mvnw", ".gradle",
                "gradlew", "build.xml" })

              -- start it up
              jdtls.start_or_attach(config)
            end
          end
        })
      end
    }
  end
}