local M = {}
local cfg = {}
local buf, win, prev_win, autocmd
local get_clients

M.update = function()
  if not win or not vim.api.nvim_win_is_valid(win) then
    M.toggle()
  end

  local clients = get_clients()
  local gotHover = false
  for i = 1, #clients do
    if clients[i].supports_method("textDocument/hover") then
      gotHover = true
      break
    end
  end
  if not gotHover then
    return
  end

  local l, c = unpack(vim.api.nvim_win_get_cursor(0))
  vim.lsp.buf_request(0, "textDocument/hover", {
    textDocument = { uri = "file://" .. vim.api.nvim_buf_get_name(0) },
    position = { line = l - 1, character = c },
  }, function(err, result, ctx)
    if win and vim.api.nvim_win_is_valid(win) and result and result.contents then
      local md_lines = vim.lsp.util.convert_input_to_markdown_lines(result.contents)
      --md_lines = vim.lsp.util.trim_empty_lines(md_lines)
      if vim.tbl_isempty(md_lines) then
        return
      end

      vim.api.nvim_buf_set_option(buf, "modifiable", true)
      vim.api.nvim_buf_set_lines(buf, 0, -1, true, {})
      vim.lsp.util.stylize_markdown(buf, md_lines)
      vim.api.nvim_buf_set_option(buf, "modifiable", false)
    end
  end)
end

M.toggle = function()
  if win and vim.api.nvim_win_is_valid(win) then
    vim.api.nvim_win_close(win, false)
    if autocmd then
      vim.api.nvim_del_autocmd(autocmd)
    end
    buf, win, prev_win, autocmd = nil, nil, nil, nil
  else
    local height = cfg["height"]
    local width = cfg["width"]
    local update_mode = cfg["update_mode"]
    if update_mode ~= "manual" then
      update_mode = "auto"
    end

    prev_win = vim.api.nvim_get_current_win()

    if cfg.position == "bottom" then
      vim.api.nvim_command("bel new")
      width = vim.api.nvim_win_get_width(prev_win)
    elseif cfg.position == "top" then
      vim.api.nvim_command("top new")
      width = vim.api.nvim_win_get_height(prev_win)
    elseif cfg.position == "left" then
      vim.api.nvim_command("topleft vnew")
    else
      vim.api.nvim_command("botright vnew")
    end

    win = vim.api.nvim_get_current_win()
    buf = vim.api.nvim_get_current_buf()

    if cfg.position == "bottom" or cfg.position == "top" then
      vim.api.nvim_win_set_height(win, math.ceil(height))
    end
    vim.api.nvim_win_set_width(win, math.ceil(width))

    vim.api.nvim_buf_set_name(buf, "Docs View")
    vim.api.nvim_buf_set_option(buf, "buftype", "nofile")
    vim.api.nvim_buf_set_option(buf, "swapfile", false)
    vim.api.nvim_buf_set_option(buf, "bufhidden", "wipe")
    vim.api.nvim_buf_set_option(buf, "filetype", "nvim-docs-view")
    vim.api.nvim_buf_set_option(buf, "buflisted", false)

    vim.api.nvim_set_current_win(prev_win)

    if update_mode == "auto" then
      autocmd = vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
        pattern = "*",
        callback = function()
          if win and vim.api.nvim_win_is_valid(win) then
            M.update()
          else
            vim.api.nvim_del_autocmd(autocmd)
            buf, win, prev_win, autocmd = nil, nil, nil, nil
          end
        end,
      })
    end
  end
end

M.setup = function(user_cfg)
  local default_cfg = {
    position = "right",
    height = 10,
    width = 60,
    update_mode = "auto",
  }

  cfg = vim.tbl_extend("force", default_cfg, user_cfg)

  if vim.fn.has("nvim-0.11.0") then
    get_clients = function()
      return vim.lsp.get_clients()
    end
  elseif vim.fn.has("nvim-0.8.0") then
    get_clients = function()
      return vim.lsp.get_active_clients()
    end
  else
    get_clients = function()
      return vim.lsp.buf_get_clients(0)
    end
  end

  vim.api.nvim_create_user_command("DocsViewToggle", M.toggle, { nargs = 0 })
  vim.api.nvim_create_user_command("DocsViewUpdate", M.update, { nargs = 0 })
end

return M
