local Config = require("neoconf.config")
local Settings = require("neoconf.settings")
local Util = require("neoconf.util")

---@type SettingsPlugin
local M = {}

function M.on_schema(schema)
  schema:set("lspconfig", {
    description = "lsp server settings for lspconfig",
  })

  for name, s in pairs(require("neoconf.build.schemas").get_schemas()) do
    if Config.options.plugins.jsonls.configured_servers_only == false or Util.has_lspconfig(name) then
      schema:set("lspconfig." .. name, {
        anyOf = {
          { const = "false", description = "Set to false to disable this lsp server" },
          {
            ["$ref"] = vim.uri_from_fname(s.settings_file),
          },
        },
      })
    end
  end
end

function M.setup()
  local settings_root = Util.root_pattern(unpack(Util.file_patterns({ global = false })))

  Util.on_config({
    name = "settings/plugins/lspconfig",
    on_config = M.on_new_config,
    root_dir = settings_root,
  })
end

function M.on_new_config(config, root_dir, original_config)
  local options = Config.get({ file = root_dir })
  if not options.plugins.lspconfig.enabled then
    return
  end

  -- backup original lsp config
  config.original_settings = vim.deepcopy(original_config.settings or {})

  root_dir = require("neoconf.workspace").find_root({ file = root_dir })
  local enabled = Settings.get_local(root_dir):get("lspconfig." .. config.name, { expand = true })
  if enabled == false then
    config.enabled = false
    return
  end

  config.settings = Util.merge(
    config.settings,
    -- merge in global lsp settings
    Settings.get_global():get("lspconfig." .. config.name, { expand = true }) or {},
    options.import.coc and Settings.get_global():get("coc") or {},
    options.import.nlsp and Settings.get_global():get("nlsp." .. config.name) or {},
    -- merge in local lsp settings
    options.import.vscode and Settings.get_local(root_dir):get("vscode") or {},
    options.import.coc and Settings.get_local(root_dir):get("coc") or {},
    options.import.nlsp and Settings.get_local(root_dir):get("nlsp." .. config.name) or {},
    Settings.get_local(root_dir):get("lspconfig." .. config.name, { expand = true }) or {}
  )
end

function M.on_update(fname)
  local is_global = Util.is_global(fname)

  local clients = vim.lsp.get_clients()

  for _, client in ipairs(clients) do
    local settings_root = require("neoconf.workspace").find_root({ file = client.config.root_dir })

    -- reload this client if the global file changed, or its root dir equals the local one
    if is_global or Util.has_file(settings_root, client.config.root_dir) then
      local old_settings = vim.deepcopy(client.config.settings)

      -- retrieve new settings only
      client.config.settings = vim.deepcopy(client.config.original_settings or {})

      local document_config = Util.has_lspconfig(client.name) and require("lspconfig")[client.name].document_config

      -- re-apply config from any other plugins that were overriding on_new_config
      if document_config and document_config.on_new_config then
        pcall(document_config.on_new_config, client.config, client.config.root_dir)
      end
      if client.config.on_new_config then
        pcall(client.config.on_new_config, client.config, client.config.root_dir)
      end

      -- only send update when confiuration actually changed
      if not vim.deep_equal(old_settings, client.config.settings) then
        -- notify the lsp server of thr new config
        local ok = pcall(client.notify, "workspace/didChangeConfiguration", {
          settings = client.config.settings,
        })

        if ok then
          Util.info("Reloaded settings for " .. client.name)
        else
          Util.error("Reloading settings failed for " .. client.name)
        end
      end
    end
  end
end

return M
