local Buffer = require('lualine.utils.class'):extend()

local modules = require('lualine_require').lazy_require {
  highlight = 'lualine.highlight',
  utils = 'lualine.utils.utils',
}

---initialize a new buffer from opts
---@param opts table
function Buffer:init(opts)
  assert(opts.bufnr, 'Cannot create Buffer without bufnr')
  self.bufnr = opts.bufnr
  self.buf_index = opts.buf_index
  self.options = opts.options
  self.highlights = opts.highlights
  self:get_props()
end

function Buffer:is_current()
  return vim.api.nvim_get_current_buf() == self.bufnr
end

function Buffer:is_alternate()
  return vim.fn.bufnr('#') == self.bufnr and not self:is_current()
end

---setup icons, modified status for buffer
function Buffer:get_props()
  self.file = modules.utils.stl_escape(vim.api.nvim_buf_get_name(self.bufnr))
  self.buftype = vim.api.nvim_buf_get_option(self.bufnr, 'buftype')
  self.filetype = vim.api.nvim_buf_get_option(self.bufnr, 'filetype')
  local modified = self.options.show_modified_status and vim.api.nvim_buf_get_option(self.bufnr, 'modified')
  self.modified_icon = modified and self.options.symbols.modified or ''
  self.alternate_file_icon = self:is_alternate() and self.options.symbols.alternate_file or ''
  self.icon = ''
  if self.options.icons_enabled then
    local dev
    local status, _ = pcall(require, 'nvim-web-devicons')
    if not status then
      dev, _ = '', ''
    elseif self.filetype == 'TelescopePrompt' then
      dev, _ = require('nvim-web-devicons').get_icon('telescope')
    elseif self.filetype == 'fugitive' then
      dev, _ = require('nvim-web-devicons').get_icon('git')
    elseif self.filetype == 'vimwiki' then
      dev, _ = require('nvim-web-devicons').get_icon('markdown')
    elseif self.buftype == 'terminal' then
      dev, _ = require('nvim-web-devicons').get_icon('zsh')
    elseif vim.fn.isdirectory(self.file) == 1 then
      dev, _ = self.options.symbols.directory, nil
    else
      dev, _ = require('nvim-web-devicons').get_icon(self.file, vim.fn.expand('#' .. self.bufnr .. ':e'))
    end
    if dev then
      self.icon = dev .. ' '
    end
  end
end

---returns line configured for handling mouse click
---@param name string
---@return string
function Buffer:configure_mouse_click(name)
  return string.format('%%%s@LualineSwitchBuffer@%s%%T', self.bufnr, name)
end

---returns rendered buffer
---@return string
function Buffer:render()
  local name = self:name()
  if self.options.fmt then
    name = self.options.fmt(name or '', self)
  end

  if self.ellipse then -- show ellipsis
    name = '...'
  else
    name = self:apply_mode(name)
  end
  name = Buffer.apply_padding(name, self.options.padding)
  self.len = vim.fn.strchars(name)

  -- setup for mouse clicks
  local line = self:configure_mouse_click(name)
  -- apply highlight
  line = modules.highlight.component_format_highlight(self.highlights[(self.current and 'active' or 'inactive')])
    .. line

  -- apply separators
  if self.options.self.section < 'x' and not self.first then
    local sep_before = self:separator_before()
    line = sep_before .. line
    self.len = self.len + vim.fn.strchars(sep_before)
  elseif self.options.self.section >= 'x' and not self.last then
    local sep_after = self:separator_after()
    line = line .. sep_after
    self.len = self.len + vim.fn.strchars(sep_after)
  end
  return line
end

---apply separator before current buffer
---@return string
function Buffer:separator_before()
  if self.current or self.aftercurrent then
    return '%Z{' .. self.options.section_separators.left .. '}'
  else
    return self.options.component_separators.left
  end
end

---apply separator after current buffer
---@return string
function Buffer:separator_after()
  if self.current or self.beforecurrent then
    return '%z{' .. self.options.section_separators.right .. '}'
  else
    return self.options.component_separators.right
  end
end

---returns name of current buffer after filtering special buffers
---@return string
function Buffer:name()
  if self.options.filetype_names[self.filetype] then
    return self.options.filetype_names[self.filetype]
  elseif self.buftype == 'help' then
    return 'help:' .. vim.fn.fnamemodify(self.file, ':t:r')
  elseif self.buftype == 'terminal' then
    local match = string.match(vim.split(self.file, ' ')[1], 'term:.*:(%a+)')
    return match ~= nil and match or vim.fn.fnamemodify(vim.env.SHELL, ':t')
  elseif self.buftype == 'quickfix' then
    local is_loclist = 0 ~= vim.fn.getloclist(0, { filewinid = 1 }).filewinid
    return is_loclist and 'Location list' or 'Quickfix List'
  elseif vim.fn.isdirectory(self.file) == 1 then
    return vim.fn.fnamemodify(self.file, ':p:.')
  elseif self.file == '' then
    return '[No Name]'
  end

  local name
  if self.options.show_filename_only then
    name = vim.fn.fnamemodify(self.file, ':t')
  else
    name = vim.fn.pathshorten(vim.fn.fnamemodify(self.file, ':p:.'))
  end
  if self.options.hide_filename_extension then
    name = vim.fn.fnamemodify(name, ':r')
  end
  return name
end

---adds spaces to left and right
function Buffer.apply_padding(str, padding)
  local l_padding, r_padding = 1, 1
  if type(padding) == 'number' then
    l_padding, r_padding = padding, padding
  elseif type(padding) == 'table' then
    l_padding, r_padding = padding.left or 0, padding.right or 0
  end
  return string.rep(' ', l_padding) .. str .. string.rep(' ', r_padding)
end

function Buffer:apply_mode(name)
  if self.options.mode == 0 then
    return string.format('%s%s%s%s', self.alternate_file_icon, self.icon, name, self.modified_icon)
  end

  if self.options.mode == 1 then
    return string.format('%s%s %s%s', self.alternate_file_icon, self.buf_index or '', self.icon, self.modified_icon)
  end

  if self.options.mode == 2 then
    return string.format(
      '%s%s %s%s%s',
      self.alternate_file_icon,
      self.buf_index or '',
      self.icon,
      name,
      self.modified_icon
    )
  end

  if self.options.mode == 3 then
    return string.format('%s%s %s%s', self.alternate_file_icon, self.bufnr or '', self.icon, self.modified_icon)
  end

  -- if self.options.mode == 4 then
  return string.format('%s%s %s%s%s', self.alternate_file_icon, self.bufnr or '', self.icon, name, self.modified_icon)
end

return Buffer
