--=============================================================================
-- scrollbar.lua --- scrollbar for SpaceVim
-- Copyright (c) 2016-2022 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================

local M = {}

local win = require('spacevim.api.vim.window')
local buffer = require('spacevim.api.vim.buffer')

local default_conf = {
  max_size = 10,
  min_size = 5,
  width = 1,
  right_offset = 1,
  excluded_filetypes = {
    'startify',
    'git-commit',
    'leaderf',
    'NvimTree',
    'tagbar',
    'defx',
    'neo-tree',
    'qf',
  },
  shape = {
    head = '▲',
    body = '█',
    tail = '▼',
  },
  highlight = {
    head = 'Normal',
    body = 'Normal',
    tail = 'Normal',
  },
}
local function get(opt)
  return default_conf[opt]
end

local scrollbar_bufnr = -1
local scrollbar_winid = -1
local scrollbar_size = -1
local ns_id = -1

local function add_highlight(winid, size)
  if vim.api.nvim_win_is_valid(winid) then
    local highlight = get('highlight')
    vim.fn.clearmatches(winid)
    vim.fn.matchaddpos(highlight.head, { 1 }, 10, ns_id, { window = winid })
    for i = 1, size - 2 do
      vim.fn.matchaddpos(highlight.body, { i + 1 }, 10, ns_id, { window = winid })
    end
    vim.fn.matchaddpos(highlight.tail, { size }, 10, ns_id, { window = winid })
  end
end

local function fix_size(size)
  return math.max(get('min_size'), math.min(get('max_size'), math.floor(size + 0.5)))
end

local function gen_bar_lines(size)
  local shape = get('shape')
  local lines = { shape.head }
  for _ = 2, size - 1 do
    table.insert(lines, shape.body)
  end
  table.insert(lines, shape.tail)
  return lines
end

local function create_scrollbar_buffer(size, lines)
  if not vim.api.nvim_buf_is_valid(scrollbar_bufnr) then
    scrollbar_bufnr = vim.api.nvim_create_buf(false, true)
  end
  buffer.set_option(scrollbar_bufnr, 'buftype', 'nofile')
  vim.api.nvim_buf_set_lines(scrollbar_bufnr, 0, -1, false, lines)
  return scrollbar_bufnr
end

function M.show()
  for _, ft in ipairs(get('excluded_filetypes')) do
    if ft == vim.o.filetype then
      M.clear()
      return
    end
  end
  local saved_ei = vim.o.eventignore
  vim.o.eventignore = 'all'
  local winnr = vim.fn.winnr()
  local bufnr = vim.fn.bufnr()
  local winid = vim.fn.win_getid()
  if win.is_float(winid) then
    M.clear()
    vim.o.eventignore = saved_ei
    return
  end

  local total = vim.api.nvim_buf_line_count(bufnr)
  local height = vim.fn.winheight(winnr)

  if total <= height then
    M.clear()
    vim.o.eventignore = saved_ei
    return
  end

  local curr_line = vim.fn.line('w0')
  local bar_size = fix_size(height * height / total)
  local width = vim.fn.winwidth(winnr)
  local col = width - get('width') - get('right_offset')
  local precision = height - bar_size
  local each_line = (total - height) * 1.0 / precision
  local visble_line = vim.fn.min({ curr_line, total - height + 1 })
  local row
  if each_line >= 1 then
    row = vim.fn.float2nr(visble_line / each_line)
  else
    row = vim.fn.float2nr(visble_line / each_line - 1 / each_line)
  end

  local opts = {
    style = 'minimal',
    relative = 'win',
    win = winid,
    width = get('width'),
    height = bar_size,
    row = row,
    col = vim.fn.float2nr(col),
    focusable = false,
    zindex = 10,
  }

  if win.is_float(scrollbar_winid) then
    if bar_size ~= scrollbar_size then
      scrollbar_size = bar_size
      local bar_lines = gen_bar_lines(bar_size)
      vim.api.nvim_buf_set_lines(scrollbar_bufnr, 0, -1, false, bar_lines)
    end
    vim.api.nvim_win_set_config(scrollbar_winid, opts)
  else
    scrollbar_size = bar_size
    local bar_lines = gen_bar_lines(bar_size)
    scrollbar_bufnr = create_scrollbar_buffer(bar_size, bar_lines)
    scrollbar_winid = vim.api.nvim_open_win(scrollbar_bufnr, false, opts)
    -- vim.fn.setwinvar(
    -- vim.fn.win_id2win(scrollbar_winid),
    -- '&winhighlight',
    -- 'Normal:ScrollbarWinHighlight'
    -- )
  end
  add_highlight(scrollbar_winid, scrollbar_size)
  vim.o.eventignore = saved_ei
end

function M.clear()
  if vim.api.nvim_win_is_valid(scrollbar_winid) then
    vim.api.nvim_win_close(scrollbar_winid, true)
  end
end

function M.usable()
  return true
end

return M
