local _, private = ...

-- Lua Globals --
-- luacheck: globals tinsert ipairs next type

-- Libs --
local Aurora = _G.Aurora
local Base = Aurora.Base
local Skin = Aurora.Skin

-- RealUI --
local Inventory = private.Inventory

-- Hidden frame used to reparent suppressed overlay textures so they never render.
private.overlayDump = private.overlayDump or _G.CreateFrame("Frame")
private.overlayDump:Hide()
private.noop = private.noop or function() end

local function SlotFactory(pool)
    local numActive = pool:GetNumActive()
    --print("CreateSlot", numActive, pool.parent, pool.parent:GetDebugName(), pool.parent:GetName())
    local slot = _G.CreateFrame("ItemButton", "$parent_Slot"..numActive, _G[pool.parent], pool.frameTemplate)
    _G.Mixin(slot, pool.mixin)

    if _G.InCombatLockdown() then
        slot.isTainted = true
    end

    slot:OnLoad()
    return slot
end
local function SlotReset(pool, slot)
    slot:ClearAllPoints()
    slot:Hide()

    if slot.cancel then
        slot.cancel()
    end
    slot.item = nil
    if not slot.location then
        slot.location = _G.ItemLocation:CreateEmpty()
    end

    local bagID, slotIndex = slot:GetBagAndSlot()
    if Inventory.main.new[bagID] then
        Inventory.main.new[bagID][slotIndex] = nil
    end

    if Inventory.db.char.junk[bagID] then
        Inventory.db.char.junk[bagID][slotIndex] = nil
    end
end

--[[ Item Slots ]]--
local ItemSlotMixin = {}
function ItemSlotMixin:OnLoad()
    if self.isTainted then
        self:SetScript("OnClick", nil)
    else
        self:HookScript("OnClick", self.OnClickHook)
    end
end
function ItemSlotMixin:Update()
    local bagID, slotIndex = self:GetBagAndSlot()
    local item = self.item

    local itemInfo = _G.C_Container.GetContainerItemInfo(bagID, slotIndex)
    local itemQuestInfo = _G.C_Container.GetContainerItemQuestInfo(bagID, slotIndex)

    local icon = item:GetItemIcon()
    local quality = item:GetItemQuality()
    _G.SetItemButtonTexture(self, icon)
    _G.SetItemButtonQuality(self, quality, item:GetItemLink())
    _G.SetItemButtonCount(self, itemInfo.stackCount)
    _G.SetItemButtonDesaturated(self, item:IsItemLocked())

    if self:GetItemType() == "equipment" then
        self.Count:SetText(self.item:GetCurrentItemLevel())
        if quality and quality > _G.Enum.ItemQuality.Poor then
            self.Count:SetTextColor(_G.BAG_ITEM_QUALITY_COLORS[quality]:GetRGB())
        end
        self.Count:Show()
    end

    local questTexture = self.IconQuestTexture
    if itemQuestInfo.questID then
        if self._auroraIconBorder then
            self._auroraIconBorder:SetBackdropBorderColor(1, 1, 0)
        end
        if not itemQuestInfo.isActive then
            questTexture:SetTexture(_G.TEXTURE_ITEM_QUEST_BANG)
            questTexture:Show()
        elseif itemQuestInfo.isQuestItem then
            questTexture:SetTexture(_G.TEXTURE_ITEM_QUEST_BORDER)
            questTexture:Show()
        end
    else
        questTexture:Hide()
    end

    self:UpdateCooldown()
    self.readable = itemInfo.isReadable

    if self == _G.GameTooltip:GetOwner() then
        local right = self.GetRight and self:GetRight()
        if right then
            self:UpdateTooltip()
        end
    end

    self:UpdateItemContext()
end
function ItemSlotMixin:UpdateCooldown()
    local cooldown = self.Cooldown
    self.Cooldown:Hide()

    local start, duration, enable = _G.C_Container.GetContainerItemCooldown(self:GetBagAndSlot())
    _G.CooldownFrame_Set(cooldown, start, duration, enable)

    if duration > 0 and enable == 0 then
        _G.SetItemButtonTextureVertexColor(self, 0.4, 0.4, 0.4)
    else
        _G.SetItemButtonTextureVertexColor(self, 1, 1, 1)
    end
end
function ItemSlotMixin:UpdateItemContext()
    local isFiltered = _G.C_Container.GetContainerItemInfo(self:GetBagAndSlot()).isFiltered
    self:UpdateItemContextMatching()
    self:SetMatchesSearch(not isFiltered)
end
function ItemSlotMixin:GetItemType()
    if not self.item then return end

    local invType = self.item:GetInventoryType()
    if invType then
        if invType == _G.Enum.InventoryType.IndexBagType or invType == _G.Enum.InventoryType.IndexQuiverType then
            return "bag"
        elseif invType == _G.Enum.InventoryType.IndexNonEquipType then
            return "other"
        else
            return "equipment"
        end
    end
end
function ItemSlotMixin:GetBagType()
    return private.GetBagTypeForBagID(self:GetBagAndSlot())
end
function ItemSlotMixin:GetBagAndSlot()
	return self.location:GetBagAndSlot()
end
function ItemSlotMixin:SplitStack(split)
    local bagID, slotIndex = self:GetBagAndSlot()
    _G.C_Container.SplitContainerItem(bagID, slotIndex, split)
end
function ItemSlotMixin:OnClickHook(button)
    if button == "RightButton" and _G.IsAltKeyDown() and _G.IsControlKeyDown() then
        private.menu:Open(self)
    end
end


local InventorySlotMixin = _G.CreateFromMixins(ItemSlotMixin)
function InventorySlotMixin:OnLoad()
    ItemSlotMixin.OnLoad(self)

    -- Regular bag slots were never Aurora-skinned — only BankSlotMixin and the
    -- Bag Slots panel (BagSlotMixin:Init) were. That left them with Blizzard's
    -- rounded WhiteIconFrame quality border, and without _auroraIconBorder it
    -- also made the quest-item border colouring in ItemSlotMixin:Update a
    -- silent no-op. Same call BankSlotMixin makes; the pool creates these from
    -- ContainerFrameItemButtonTemplate.
    Skin.ContainerFrameItemButtonTemplate(self)

    self.IconQuestTexture = _G[self:GetName().."IconQuestTexture"]
end
function InventorySlotMixin:Update()
    ItemSlotMixin.Update(self)

    if self.assignedTag ~= "junk" then
        self.JunkIcon:Hide()
    end

    self.BattlepayItemTexture:SetShown(_G.C_Container.IsBattlePayItem(self:GetBagAndSlot()))
end
local inventorySlots = _G.CreateUnsecuredObjectPool(SlotFactory, SlotReset)
inventorySlots.frameTemplate = "ContainerFrameItemButtonTemplate"
inventorySlots.parent = "RealUIInventory"
inventorySlots.mixin = InventorySlotMixin


local BankSlotMixin = _G.CreateFromMixins(ItemSlotMixin)
function BankSlotMixin:OnLoad()
    ItemSlotMixin.OnLoad(self)

    Skin.ContainerFrameItemButtonTemplate(self)
    if self.IconBorder then
        self.IconBorder:SetAlpha(0)
    end
    if self.IconOverlay then
        self.IconOverlay:SetAlpha(0)
    end
    -- Suppress NormalTexture quality glow
    _G.hooksecurefunc(self, "SetNormalTexture", function(btn)
        local tex = btn:GetNormalTexture()
        if tex then
            tex:SetAlpha(0)
        end
    end)
    -- Suppress the emptyBackgroundAtlas slot texture that ItemButton uses
    if self.emptyBackgroundAtlas then
        self.emptyBackgroundAtlas = nil
    end

    -- Completely disable the Blizzard item-context dark overlay for bank slots.
    -- The overlay is shown by UpdateItemContextOverlay (called from PostOnShow,
    -- SetMatchesSearch, and OnItemContextChanged). Overriding the method to a
    -- no-op prevents all code paths from ever showing it. We also reparent the
    -- overlay texture to a hidden frame so even C-side SetShown calls are moot.
    self.UpdateItemContextOverlay = private.noop
    self.itemContextMatchResult = _G.ItemButtonUtil.ItemContextMatchResult.DoesNotApply
    if self.ItemContextOverlay then
        self.ItemContextOverlay:Hide()
        self.ItemContextOverlay:ClearAllPoints()
        self.ItemContextOverlay:SetParent(private.overlayDump)
    end

    -- BattlepayItemTexture (store-item-highlight blue glow) is visible by
    -- default in ContainerFrameItemButtonTemplate. Hide it immediately.
    if self.BattlepayItemTexture then
        self.BattlepayItemTexture:Hide()
    end
end
function BankSlotMixin:Update()
    ItemSlotMixin.Update(self)
    -- After every update, force-hide all known overlay textures that
    -- Blizzard's C code or template scripts may have re-shown.
    if self.IconBorder then self.IconBorder:SetAlpha(0) end
    if self.IconOverlay then self.IconOverlay:Hide() end
    if self.IconOverlay2 then self.IconOverlay2:Hide() end
    local normalTex = self:GetNormalTexture()
    if normalTex then normalTex:SetAlpha(0) end

    -- BattlepayItemTexture (store-item-highlight blue glow) is visible by
    -- default in ContainerFrameItemButtonTemplate. Bank items are never
    -- store-purchased, so always hide it.
    self.BattlepayItemTexture:Hide()
end
function BankSlotMixin:GetItemContextMatchResult()
    -- Always return DoesNotApply so the Blizzard item-context system
    -- (vendor/trade highlights) never darkens bank slot icons.
    -- PostOnShow → UpdateItemContextMatching reads this and skips the overlay.
    return _G.ItemButtonUtil.ItemContextMatchResult.DoesNotApply
end
function BankSlotMixin:UpdateItemContext()
    -- Skip UpdateItemContextMatching() for bank slots — the Blizzard item
    -- context system (vendor/trade highlights) applies a dark overlay to
    -- non-matching items which is unwanted in the bank frame.
    local info = _G.C_Container.GetContainerItemInfo(self:GetBagAndSlot())
    local isFiltered = info and info.isFiltered
    self:SetMatchesSearch(not isFiltered)
end
local bankSlots = _G.CreateUnsecuredObjectPool(SlotFactory, SlotReset)
bankSlots.frameTemplate = "ContainerFrameItemButtonTemplate"
bankSlots.parent = "RealUIBank"
bankSlots.mixin = BankSlotMixin

private.ReleaseAllBankSlots = function()
    bankSlots:ReleaseAll()
end


function private.UpdateSlots(bagID)
    Inventory:debug("private.UpdateSlots", bagID)
    for slotIndex = 1, _G.C_Container.GetContainerNumSlots(bagID) do
        local slot = private.GetSlot(bagID, slotIndex)
        if slot then
            slot.cancel = slot.item:ContinueWithCancelOnItemLoad(function()
                private.AddSlotToBag(slot, bagID)
                slot:Update()
            end)
            slot:Show()
        end
    end
end

function private.GetSlot(bagID, slotIndex)
    --Inventory:debug("private.GetSlot", bagID, slotIndex)
    local slots = private.GetSlotTypeForBag(bagID)

    for slot in slots:EnumerateActive() do
        if slot.location:IsEqualToBagAndSlot(bagID, slotIndex) then
            if slot.location:IsValid() then
                if slot.isTainted and not _G.InCombatLockdown() then
                    -- We're out of combat, excise tainted slot and create a new one
                    slots.activeObjectCount = slots.activeObjectCount - 1
                    slots.activeObjects[slot] = nil
                    slots.resetFunc(slots, slot)
                    break
                end
                return slot
            else
                slots:Release(slot)
                return
            end
        end
    end

    local slot = slots:Acquire()
    if slot then
        slot.location:SetBagAndSlot(bagID, slotIndex)
        if slot.location:IsValid() then
            slot:SetID(slotIndex)
            if slot.SetBagID then
                slot:SetBagID(bagID)
            end
            slot.item = _G.Item:CreateFromItemLocation(slot.location)
            return slot
        else
            slots:Release(slot)
        end
    end
end

function private.GetFirstFreeSlot(bagID)
    if _G.C_Container.GetContainerNumFreeSlots(bagID) > 0 then
        local numSlots = _G.C_Container.GetContainerNumSlots(bagID)
        for slotIndex = 1, numSlots do
            if not _G.C_Container.GetContainerItemLink(bagID, slotIndex) then
                return slotIndex
            end
        end
    end
end

function private.GetSlotTypeForBag(bagID)
    if bagID >= _G.Enum.BagIndex.Backpack and bagID <= _G.NUM_TOTAL_EQUIPPED_BAG_SLOTS then
        return inventorySlots
    else
        return bankSlots
    end
end

--[[ Bag Slots ]]--
local searchBags = {}
local function SearchItemsForBag(bagID)
    local slots = private.GetSlotTypeForBag(bagID)

    for slot in slots:EnumerateActive() do
        slot:SetMatchesSearch(searchBags[(slot:GetBagAndSlot())])
    end
end
private.SearchItemsForBag = SearchItemsForBag

local mainBags = {
    [_G.Enum.BagIndex.Backpack] = _G.BACKPACK_TOOLTIP,
    [_G.Enum.BagIndex.Characterbanktab] = _G.BANK,
    [_G.Enum.BagIndex.Accountbanktab] = _G.ACCOUNT_BANK_PANEL_TITLE,
}
local BagSlotMixin = {}
function BagSlotMixin:Init(bagID)
    self:SetID(bagID)
    self:SetSize(20, 20)
    self:SetFrameLevel(5)

    local highlight = self:CreateTexture(nil, "OVERLAY")
    highlight:SetAllPoints()
    highlight:SetBlendMode("ADD")
    highlight:SetTexture([[Interface\Buttons\CheckButtonHilight]])
    Base.CropIcon(highlight)
    self.highlight = highlight

    self:RegisterForDrag("LeftButton")
    self:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    self:RegisterEvent("PLAYER_ENTERING_WORLD");
    self:RegisterEvent("BAG_UPDATE_DELAYED")
    self:RegisterEvent("INVENTORY_SEARCH_UPDATE")
    self:SetScript("OnEvent", self.OnEvent)
    self:SetScript("OnEnter", self.OnEnter)
    self:SetScript("OnLeave", self.OnLeave)
    self:SetScript("OnClick", self.OnClick)
    self:SetScript("OnDragStart", self.OnDragStart)
    self:SetScript("OnReceiveDrag", self.OnReceiveDrag)

    self.bagType = private.GetBagTypeForBagID(bagID)
    self.isBag = not mainBags[bagID]

    Skin.FrameTypeItemButton(self)
    if self.isBag then
        if bagID >= _G.Enum.BagIndex.Backpack and bagID <= _G.NUM_BAG_SLOTS then
            self.inventorySlot = "Bag"..(bagID - 1).."Slot"

            self.inventoryID, self.fallbackTexture = _G.GetInventorySlotInfo(self.inventorySlot)
        elseif bagID == _G.Enum.BagIndex.ReagentBag then
            local slotID = bagID - _G.NUM_TOTAL_EQUIPPED_BAG_SLOTS
            self.inventorySlot = "ReagentBag"..slotID.."Slot"
            self.inventoryID, self.fallbackTexture = _G.GetInventorySlotInfo(self.inventorySlot)
        else
            -- Bank tab bag slots (Character/Account) — WoW 12+ uses C_Bank APIs for tab icons,
            -- no traditional inventory slot IDs. Use tab icon from C_Bank.FetchPurchasedBankTabData() instead.
            self.fallbackTexture = [[Interface\PaperDoll\UI-Backpack-EmptySlot]]
        end
    else
        self.fallbackTexture = [[Interface\Buttons\Button-Backpack-Up]]
    end

    _G.SetItemButtonTexture(self, self.fallbackTexture)
end
function BagSlotMixin:GetItemContextMatchResult()
    return _G.ItemButtonUtil.GetItemContextMatchResultForContainer(self:GetID())
end
function BagSlotMixin:GetBankTabInfo(bagID)
    for bankType, config in next, private.bankTypeConfig do
        for _, tabBagID in ipairs(config.tabRange) do
            if tabBagID == bagID then
                local purchasedTabs = _G.C_Bank.FetchPurchasedBankTabData(bankType)
                if purchasedTabs then
                    for _, tabData in ipairs(purchasedTabs) do
                        if tabData.ID == bagID then
                            return bankType, tabData.icon
                        end
                    end
                end
                return bankType, nil  -- Tab exists in range but not purchased
            end
        end
    end
    return nil, nil
end
function BagSlotMixin:Update()
    if self.isBag then
        if self.inventoryID then
            local textureName = _G.GetInventoryItemTexture("player", self.inventoryID)
            _G.SetItemButtonTexture(self, textureName or self.fallbackTexture)

            local quality = _G.GetInventoryItemQuality("player", self.inventoryID)
            _G.SetItemButtonQuality(self, quality, _G.GetInventoryItemID("player", self.inventoryID), true)
        end

        if self.bagType == "bank" then
            -- WoW 12+: Bank tabs use C_Bank APIs for icons and purchase state
            local bagID = self:GetID()
            local _, tabIcon = self:GetBankTabInfo(bagID)
            if tabIcon then
                _G.SetItemButtonTexture(self, tabIcon)
                _G.SetItemButtonTextureVertexColor(self, 1.0, 1.0, 1.0)
                self.tooltipText = _G.BANK_BAG
            else
                _G.SetItemButtonTexture(self, self.fallbackTexture)
                _G.SetItemButtonTextureVertexColor(self, 1.0, 0.1, 0.1)
                self.tooltipText = _G.BANK_BAG_PURCHASE
            end
        else
            self.tooltipText = (self:GetID() == 5) and _G.EQUIP_CONTAINER_REAGENT or _G.EQUIP_CONTAINER
        end
    else
        self.tooltipText = mainBags[self:GetID()]
    end

    searchBags[self:GetID()] = true
    self.highlight:Show()
    self:UpdateItemContextMatching()
end

function BagSlotMixin:OnEvent(event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        self:UpdateBagMatchesSearch()
	elseif event == "INVENTORY_SEARCH_UPDATE" then
		self:UpdateBagMatchesSearch();
    else
        self:Update()
    end
end

function BagSlotMixin:UpdateBagMatchesSearch()
	self:SetMatchesSearch(not _G.C_Container.IsContainerFiltered(self:GetID()));
end

function BagSlotMixin:OnEnter()
    _G.GameTooltip:SetOwner(self, "ANCHOR_LEFT")
    local hasItem = self.inventoryID and _G.GameTooltip:SetInventoryItem("player", self.inventoryID)
    if not hasItem then
        _G.GameTooltip:SetText(self.tooltipText, 1.0, 1.0, 1.0)
    end

    local freeSlots = _G.C_Container.GetContainerNumFreeSlots(self:GetID())
    local text = _G.NUM_FREE_SLOTS:format(freeSlots)
    if freeSlots == 0 then
        _G.GameTooltip_AddErrorLine(_G.GameTooltip, text)
    else
        _G.GameTooltip_AddNormalLine(_G.GameTooltip, text)
    end

    _G.GameTooltip:Show()
end
function BagSlotMixin:OnLeave()
    _G.GameTooltip:Hide()
    _G.ResetCursor()
end
function BagSlotMixin:OnClick(button)
    local hadItem = self.isBag and self.inventoryID and _G.PutItemInBag(self.inventoryID)
    local needsPurchase = false  -- WoW 12+: bank tab purchase handled via TabSidebar purchase button
    if not hadItem and not needsPurchase then
        if self.highlight:IsShown() then
            searchBags[self:GetID()] = false
            self.highlight:Hide()
        else
            searchBags[self:GetID()] = true
            self.highlight:Show()
        end

        SearchItemsForBag(self:GetID())
    end
end
function BagSlotMixin:OnDragStart()
    if self.inventoryID then
        _G.PickupBagFromSlot(self.inventoryID)
    end
end
function BagSlotMixin:OnReceiveDrag()
    self:OnClick()
end

private.bagSlots = {}
function private.CreateBagSlots(main)
    Inventory:debug("private.CreateBagSlots", main.bagType)
    local bagSlots, bagType = private.bagSlots, main.bagType
    bagSlots[bagType] = {}

    local bagSlot, previousButton
    for k, bagID in main:IterateBagIDs() do
        bagSlot = _G.CreateFrame("ItemButton", "$parent_Bag"..bagID, main)
        _G.Mixin(bagSlot, BagSlotMixin)
        bagSlot:Init(bagID)

        if previousButton then
            bagSlot:SetPoint("TOPLEFT", previousButton, "TOPRIGHT", 5, 0)
        end

        previousButton = bagSlot
        bagSlots[bagType][bagID] = bagSlot
    end
end
