---@class addonTableBaganator
local addonTable = select(2, ...)

local classicTabObjectCounter = 0

BaganatorItemViewCommonBackpackViewMixin = {}

local function PreallocateItemButtons(pool, buttonCount)
  assert(not InCombatLockdown())
  for i = 1, buttonCount do
    local button = pool:Acquire()
    addonTable.Skins.AddFrame("ItemButton", button)
  end
  pool:ReleaseAll()
end

function BaganatorItemViewCommonBackpackViewMixin:OnLoad()
  ButtonFrameTemplate_HidePortrait(self)
  ButtonFrameTemplate_HideButtonBar(self)
  self.Inset:Hide()
  self:RegisterForDrag("LeftButton")
  self:SetMovable(true)
  self:SetClampedToScreen(true)
  self:SetUserPlaced(false)

  self.liveItemButtonPool = addonTable.ItemViewCommon.GetLiveItemButtonPool(self)

  self.Anchor = addonTable.ItemViewCommon.GetAnchorSetter(self, addonTable.Config.Options.MAIN_VIEW_POSITION)

  -- DO NOT REMOVE
  -- Preallocating is necessary to avoid taint issues if a
  -- player logs in or first opens their bags when in combat
  -- 6 is bags + reagent bag (retail) or bags + keyring (wrath)
  PreallocateItemButtons(self.liveItemButtonPool, Syndicator.Constants.MaxBagSize * 6 + addonTable.Constants.ContainerTypes)

  addonTable.Utilities.AddBagSortManager(self) -- self.sortManager
  addonTable.Utilities.AddBagTransferManager(self) -- self.transferManager

  addonTable.Utilities.AddScrollBar(self)

  self.tabsPool = addonTable.ItemViewCommon.GetTabButtonPool(self)

  self.refreshState = {}
  for _, value in pairs(addonTable.Constants.RefreshReason) do
    self.refreshState[value] = true
  end

  addonTable.CallbackRegistry:RegisterCallback("BagCacheAfterNewItemsUpdate",  function(_, character, updatedBags)
    self:SetLiveCharacter(character)
    if next(updatedBags.bags) ~= nil then
      self.refreshState[addonTable.Constants.RefreshReason.ItemData] = true
    end
    if updatedBags.containerBags.bags then
      self.refreshState[addonTable.Constants.RefreshReason.ItemData] = true
      self.refreshState[addonTable.Constants.RefreshReason.Layout] = true
    end
    self:NotifyBagUpdate(updatedBags)
    if self:IsVisible() and next(self.refreshState) ~= nil then
      self:UpdateForCharacter(character, true)
    end
  end)

  addonTable.CallbackRegistry:RegisterCallback("RefreshStateChange",  function(_, refreshState)
    self.refreshState = Mixin(self.refreshState, refreshState)

    for _, layout in ipairs(self.Container.Layouts) do
      layout:UpdateRefreshState(refreshState)
    end

    if self:IsVisible() then
      self:UpdateForCharacter(self.lastCharacter, self.isLive)
    end
  end)

  addonTable.CallbackRegistry:RegisterCallback("SettingChanged",  function(_, settingName)
    if not self.lastCharacter then
      return
    end
    if settingName == addonTable.Config.Options.MAIN_VIEW_SHOW_BAG_SLOTS and self:IsVisible() then
      self.BagSlots:Update(self.lastCharacter, self.isLive)
      self:OnFinished(true)
    end
  end)

  addonTable.CallbackRegistry:RegisterCallback("SearchTextChanged",  function(_, text)
    self.searchToApply = true
    self:ApplySearch(text)
  end)

  addonTable.CallbackRegistry:RegisterCallback("CharacterSelect", function(_, character)
    if character ~= self.lastCharacter then
      self.refreshState[addonTable.Constants.RefreshReason.ItemData] = true
      self.refreshState[addonTable.Constants.RefreshReason.Layout] = true
      if self:IsVisible() then
        self:UpdateForCharacter(character, self.liveCharacter == character)
      else
        self.lastCharacter = character
      end
    end
  end)

  Syndicator.CallbackRegistry:RegisterCallback("GuildNameSet",  function(_, guild)
    if self.lastCharacter ~= nil and Syndicator.API.GetCharacter(self.lastCharacter) then
      self:UpdateGuildButton()
    end
  end)

  if addonTable.Constants.IsEra then
    local index = tIndexOf(self.TopButtons, self.ToggleGuildBankButton)
    table.remove(self.TopButtons, index)
    self.ToggleGuildBankButton:Hide()
  end

  for index = 2, #self.TopButtons do
    local button = self.TopButtons[index]
    button:SetPoint("TOPLEFT", self.TopButtons[index-1], "TOPRIGHT")
  end

  self.TopButtons[1]:ClearAllPoints()
  self.TopButtons[1]:SetPoint("TOPLEFT", self, "TOPLEFT", addonTable.Constants.ButtonFrameOffset + 2, -1)

  addonTable.Skins.AddFrame("ButtonFrame", self, {"backpack"})

  self:SetLiveCharacter(Syndicator.API.GetCurrentCharacter())
end

function BaganatorItemViewCommonBackpackViewMixin:OnShow()
  if addonTable.Config.Get(addonTable.Config.Options.AUTO_SORT_ON_OPEN) then
    C_Timer.After(0, function()
      self:CombineStacksAndSort()
    end)
  end
  PlaySound(SOUNDKIT.IG_BACKPACK_OPEN);
end

function BaganatorItemViewCommonBackpackViewMixin:OnHide()
  PlaySound(SOUNDKIT.IG_BACKPACK_CLOSE);
end

function BaganatorItemViewCommonBackpackViewMixin:SetLiveCharacter(character)
  self.liveCharacter = character
end

function BaganatorItemViewCommonBackpackViewMixin:OnDragStart()
  if not addonTable.Config.Get(addonTable.Config.Options.LOCK_FRAMES) then
    self:StartMoving()
    self:SetUserPlaced(false)
  end
end

function BaganatorItemViewCommonBackpackViewMixin:OnDragStop()
  self:StopMovingOrSizing()
  self:SetUserPlaced(false)
  local oldCorner = addonTable.Config.Get(addonTable.Config.Options.MAIN_VIEW_POSITION)[1]
  addonTable.Config.Set(addonTable.Config.Options.MAIN_VIEW_POSITION, {addonTable.Utilities.ConvertAnchorToCorner(oldCorner, self)})
  self:ClearAllPoints()
  self:SetPoint(unpack(addonTable.Config.Get(addonTable.Config.Options.MAIN_VIEW_POSITION)))
end

function BaganatorItemViewCommonBackpackViewMixin:ToggleBank()
  addonTable.CallbackRegistry:TriggerEvent("BankToggle")
  self:Raise()
end

function BaganatorItemViewCommonBackpackViewMixin:ToggleGuildBank()
  addonTable.CallbackRegistry:TriggerEvent("GuildToggle", Syndicator.API.GetCharacter(self.lastCharacter).details.guild)
end

function BaganatorItemViewCommonBackpackViewMixin:ToggleReagents()
  addonTable.Config.Set(addonTable.Config.Options.SHOW_REAGENTS, not addonTable.Config.Get(addonTable.Config.Options.SHOW_REAGENTS))
end

function BaganatorItemViewCommonBackpackViewMixin:ToggleBagSlots()
  addonTable.Config.Set(addonTable.Config.Options.MAIN_VIEW_SHOW_BAG_SLOTS, not addonTable.Config.Get(addonTable.Config.Options.MAIN_VIEW_SHOW_BAG_SLOTS))
end

function BaganatorItemViewCommonBackpackViewMixin:UpdateForCharacter(character, isLive)
  addonTable.ReportEntry()

  self.searchToApply = self.searchToApply or self.refreshState[addonTable.Constants.RefreshReason.Searches] or self.refreshState[addonTable.Constants.RefreshReason.ItemData] or self.refreshState[addonTable.Constants.RefreshReason.ItemWidgets]

  local start = debugprofilestop()

  local characterData = Syndicator.API.GetCharacter(character)

  if not characterData then
    self:SetTitle("")
    return
  else
    self:SetTitle(addonTable.Locales.XS_BAGS:format(characterData.details.character))
  end

  local oldLast = self.lastCharacter
  self.lastCharacter = character
  if oldLast ~= character then
    self.refreshState[addonTable.Constants.RefreshReason.ItemData] = true
    self.refreshState[addonTable.Constants.RefreshReason.Layout] = true
    self.refreshState[addonTable.Constants.RefreshReason.Character] = true
    addonTable.CallbackRegistry:TriggerEvent("CharacterSelect", character)
  end

  if isLive ~= self.isLive then
    self.refreshState[addonTable.Constants.RefreshReason.ItemData] = true
  end

  self.isLive = isLive

  addonTable.Utilities.AddGeneralDropSlot(self, function()
    return Syndicator.API.GetCharacter(Syndicator.API.GetCurrentCharacter()).bags
  end, Syndicator.Constants.AllBagIndexes)

  if self.refreshState[addonTable.Constants.RefreshReason.ItemData] then
    self.BagSlots:Update(self.lastCharacter, self.isLive)
  end
  local containerInfo = characterData.containerInfo
  self.ToggleBagSlotsButton:SetShown(self.isLive or (containerInfo and containerInfo.bags))

  self.SortButton:SetShown(addonTable.Utilities.ShouldShowSortButton() and isLive)
  self:UpdateTransferButton()

  local sideSpacing, topSpacing = addonTable.Utilities.GetSpacing()

  self.BagSlots:SetPoint("BOTTOMLEFT", self, "TOPLEFT", addonTable.Constants.ButtonFrameOffset, 0)

  if self.refreshState[addonTable.Constants.RefreshReason.Layout] then
    self.SearchWidget:SetSpacing(sideSpacing)
  end

  if self.CurrencyWidget.lastCharacter ~= self.lastCharacter then
    self.CurrencyWidget:UpdateCurrencies(character)
  end
end

function BaganatorItemViewCommonBackpackViewMixin:OnFinished(forceResize)
  if self.refreshState[addonTable.Constants.RefreshReason.Layout] or forceResize then
    local sideSpacing, topSpacing, searchSpacing = addonTable.Utilities.GetSpacing()

    local externalVerticalSpacing = (self.BagSlots:GetHeight() > 0 and (self.BagSlots:GetTop() - self:GetTop()) or 0)

    local additionalPadding = 0
    if addonTable.Config.Get(addonTable.Config.Options.REDUCE_SPACING) then
      additionalPadding = 1
    end

    self:SetSize(
      self.Container:GetWidth() + sideSpacing * 2 + addonTable.Constants.ButtonFrameOffset - 2,
      math.min(self.Container:GetHeight() + 49 + searchSpacing + additionalPadding + topSpacing / 2 + self.CurrencyWidget:GetExtraHeight(), UIParent:GetHeight() / self:GetScale() - externalVerticalSpacing)
    )

    self:UpdateScroll(74 + additionalPadding + topSpacing / 2 + externalVerticalSpacing, self:GetScale())
  end

  self:UpdateAllButtons()
end

function BaganatorItemViewCommonBackpackViewMixin:CombineStacks(callback)
  addonTable.Sorting.CombineStacks(Syndicator.API.GetCharacter(self.liveCharacter).bags, Syndicator.Constants.AllBagIndexes, function(status)
    self.sortManager:Apply(status, function()
      self:CombineStacks(callback)
    end, function()
      callback()
    end)
  end)
end

function BaganatorItemViewCommonBackpackViewMixin:UpdateTransferButton()
  if not self:IsVisible() then
    return
  end

  if not self.isLive then
    self.TransferButton:Hide()
    return
  end

  self.TransferButton:ClearAllPoints()
  if self.SortButton:IsShown() then
    self.TransferButton:SetPoint("RIGHT", self.SortButton, "LEFT")
  else
    self.TransferButton:SetPoint("RIGHT", self.CustomiseButton, "LEFT")
  end

  for _, info in ipairs(addonTable.BagTransfers) do
    if info.condition() then
      self.TransferButton:Show()
      self.TransferButton.tooltipText = info.tooltipText
      return
    end
  end
  self.TransferButton:Hide()
end

function BaganatorItemViewCommonBackpackViewMixin:IsTransferActive()
  return self.TransferButton:IsShown()
end

function BaganatorItemViewCommonBackpackViewMixin:UpdateAllButtons()
  self.ButtonVisibility:Update()
  self:UpdateGuildButton()
end

function BaganatorItemViewCommonBackpackViewMixin:UpdateGuildButton()
  local guildName = Syndicator.API.GetCharacter(self.lastCharacter).details.guild
  self.ToggleGuildBankButton:SetEnabled(guildName ~= nil and Syndicator.API.GetGuild(guildName))
  self.ToggleGuildBankButton.Icon:SetDesaturated(not self.ToggleGuildBankButton:IsEnabled())
end

function BaganatorItemViewCommonBackpackViewMixin:RunAction(action, getItems)
  action((getItems and getItems()) or self:GetSearchMatches(), self.liveCharacter, function(status, modes)
    self.transferManager:Apply(status, modes or {"BagCacheUpdate"}, function()
      self:RunAction(action, getItems)
    end, function() end)
  end)
end

function BaganatorItemViewCommonBackpackViewMixin:Transfer(force, getItems)
  for _, transferDetails in ipairs(addonTable.BagTransfers) do
    if transferDetails.condition() then
      if not force and transferDetails.confirmOnAll and self.SearchWidget.SearchBox:GetText() == "" then
        addonTable.Dialogs.ShowConfirm(addonTable.Locales.CONFIRM_TRANSFER_ALL_ITEMS_FROM_BAG, YES, NO, function()
          self:Transfer(true, self.data)
        end)
        break
      else
        self:RunAction(transferDetails.action, getItems)
        break
      end
    end
  end
end

function BaganatorItemViewCommonBackpackViewMixin:DoSort(isReverse)
  local bagsToSort = {}
  for index, bagID in ipairs(Syndicator.Constants.AllBagIndexes) do
    bagsToSort[index] = true
  end
  local bagChecks = addonTable.Sorting.GetBagUsageChecks(Syndicator.Constants.AllBagIndexes)
  local function DoSortInternal()
    local status = addonTable.Sorting.ApplyBagOrdering(
      Syndicator.API.GetCharacter(self.liveCharacter).bags,
      Syndicator.Constants.AllBagIndexes,
      bagsToSort,
      bagChecks,
      isReverse,
      addonTable.Config.Get(addonTable.Config.Options.SORT_IGNORE_SLOTS_AT_END),
      addonTable.Config.Get(addonTable.Config.Options.SORT_IGNORE_BAG_SLOTS_COUNT)
    )
    self.sortManager:Apply(status, DoSortInternal, function() end)
  end
  DoSortInternal()
end

function BaganatorItemViewCommonBackpackViewMixin:CombineStacksAndSort(isReverse)
  local sortMethod = addonTable.Config.Get(addonTable.Config.Options.SORT_METHOD)

  if not addonTable.Sorting.IsModeAvailable(sortMethod) then
    addonTable.Config.ResetOne(addonTable.Config.Options.SORT_METHOD)
    sortMethod = addonTable.Config.Get(addonTable.Config.Options.SORT_METHOD)
  end

  if addonTable.API.ExternalContainerSorts[sortMethod] then
    if addonTable.Config.Get(addonTable.Config.Options.SORT_START_AT_BOTTOM) then
      isReverse = not isReverse
    end
    addonTable.API.ExternalContainerSorts[sortMethod].callback(isReverse, Baganator.API.Constants.ContainerType.Backpack)
  elseif sortMethod == "combine_stacks_only" then
    self:CombineStacks(function() end)
  else
    self:CombineStacks(function()
      self:DoSort(isReverse)
    end)
  end
end

function BaganatorItemViewCommonBackpackViewMixin:GetExternalSortMethodName()
  return addonTable.Utilities.GetExternalSortMethodName()
end
