local api = vim.api
local lsp = vim.lsp

local M = {}

--- @param msg string
local function echo_err(msg)
  api.nvim_echo({ { msg } }, true, { err = true })
end

--- @return string[]
local function get_client_names()
  return vim
    .iter(lsp.get_clients())
    :map(function(client)
      return client.name
    end)
    :unique()
    :totable()
end

--- @param filter vim.lsp.get_configs.Filter
--- @return fun():string[]
local function filtered_config_names(filter)
  return function()
    return vim
      .iter(lsp.get_configs(filter))
      :map(function(config)
        return config.name
      end)
      :totable()
  end
end

local complete_args = {
  enable = filtered_config_names { enabled = false },
  disable = filtered_config_names { enabled = true },
  restart = get_client_names,
  stop = get_client_names,
}

--- @param names string[]
--- @param enable? boolean
local function checked_enable(names, enable)
  for _, name in ipairs(names) do
    if name:find('*') == nil and lsp.config[name] ~= nil then
      lsp.enable(name, enable)
    else
      echo_err(("No client config named '%s'"):format(name))
    end
  end
end

--- @param config_names string[]
local function ex_lsp_enable(config_names)
  -- Default to enabling all clients matching the filetype of the current buffer.
  if #config_names == 0 then
    local filetype = vim.bo.filetype
    for _, config in ipairs(lsp.get_configs()) do
      local filetypes = config.filetypes
      if filetypes == nil or vim.list_contains(filetypes, filetype) then
        table.insert(config_names, config.name)
      end
    end
    if #config_names == 0 then
      if filetype == '' then
        echo_err('Current buffer has no filetype')
      else
        echo_err(("No configs for filetype '%s'"):format(filetype))
      end
      return
    end
  end

  checked_enable(config_names)
end

--- @param config_names string[]
local function ex_lsp_disable(config_names)
  -- Default to disabling all clients attached to the current buffer.
  if #config_names == 0 then
    config_names = vim
      .iter(lsp.get_clients { bufnr = api.nvim_get_current_buf() })
      :map(function(client)
        return client.name
      end)
      :filter(function(name)
        return lsp.config[name] ~= nil
      end)
      :totable()
    if #config_names == 0 then
      echo_err('No configs with clients attached to current buffer')
      return
    end
  end

  checked_enable(config_names, false)
end

--- @param client_names string[]
--- @return vim.lsp.Client[]
local function get_clients_from_names(client_names)
  -- Default to all active clients attached to the current buffer.
  if #client_names == 0 then
    local clients = lsp.get_clients { bufnr = api.nvim_get_current_buf() }
    if #clients == 0 then
      echo_err('No clients attached to current buffer')
    end
    return clients
  else
    return vim
      .iter(client_names)
      :map(function(name)
        local clients = lsp.get_clients { name = name }
        if #clients == 0 then
          echo_err(("No active clients named '%s'"):format(name))
        end
        return clients
      end)
      :flatten()
      :totable()
  end
end

--- @param client_names string[]
local function ex_lsp_restart(client_names)
  local clients = get_clients_from_names(client_names)

  for _, client in ipairs(clients) do
    client:_restart(client.exit_timeout)
  end
end

--- @param client_names string[]
local function ex_lsp_stop(client_names)
  local clients = get_clients_from_names(client_names)

  for _, client in ipairs(clients) do
    client:stop(client.exit_timeout)
  end
end

local actions = {
  enable = ex_lsp_enable,
  disable = ex_lsp_disable,
  restart = ex_lsp_restart,
  stop = ex_lsp_stop,
}

local available_subcmds = vim.tbl_keys(actions)

--- Implements command: `:lsp {subcmd} {name}?`.
--- @param args string
M.ex_lsp = function(args)
  local fargs = api.nvim_parse_cmd('lsp ' .. args, {}).args
  if not fargs then
    return
  end
  local subcmd = fargs[1]
  if not vim.list_contains(available_subcmds, subcmd) then
    echo_err(("Invalid subcommand '%s'"):format(subcmd))
    return
  end

  local clients = { unpack(fargs, 2) }

  actions[subcmd](clients)
end

--- Completion logic for `:lsp` command
--- @param line string content of the current command line
--- @return string[] list of completions
function M.lsp_complete(line)
  local split = vim.split(line, '%s+')
  if #split == 2 then
    return available_subcmds
  else
    local subcmd = split[2]
    return vim
      .iter(complete_args[subcmd]())
      --- @param n string
      :map(function(n)
        return vim.fn.escape(n, ' \t')
      end)
      :totable()
  end
end

return M
