--========================================================--
--             Scorpio Secure Bag Slot Handler            --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2021/03/29                              --
--========================================================--

--========================================================--
Scorpio        "Scorpio.Secure.BagSlotHandler"       "1.0.0"
--========================================================--

_Enabled                        = false

------------------------------------------------------
-- Action Handler
------------------------------------------------------
handler                         = ActionTypeHandler {
    Name                        = "bagslot",
    Target                      = "bag",
    Detail                      = "slot",

    DragStyle                   = "Keep",
    ReceiveStyle                = "Keep",

    UpdateSnippet               = [[
        self:SetAttribute("*type1", "click")
        self:SetAttribute("*type2", "click")
        Manager:CallMethod("BindProxyClick", self:GetName(), "Scorpio_BagSlot_FakeItemButton")
        Manager:CallMethod("RegisterBagSlot", self:GetName())
    ]],
    ClearSnippet                = [[
        self:SetAttribute("*type1", nil)
        self:SetAttribute("*type2", nil)
        Manager:CallMethod("ClearProxyClick", self:GetName())
        Manager:CallMethod("UnregisterBagSlot", self:GetName())
    ]],
    ReceiveSnippet              = "Custom",

    PreClickSnippet             = [[
        local bag               = self:GetAttribute("bag")
        local slot              = self:GetAttribute("slot")

        _BagSlot_FakeContainer:SetID(bag)
        _BagSlot_FakeItemButton:SetID(slot)

        _BagSlot_FakeItemButton:ClearAllPoints()
        _BagSlot_FakeItemButton:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
        _BagSlot_FakeItemButton:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
        _BagSlot_FakeItemButton:Show()
    ]],

    OnEnableChanged             = function(self, value) _Enabled = value end,
}


------------------------------------------------------
-- Module Event Handler
------------------------------------------------------
_BagCache                       = {}

LE_ITEM_QUALITY_POOR            = _G.LE_ITEM_QUALITY_POOR or _G.Enum.ItemQuality.Poor
REPAIR_COST                     = _G.REPAIR_COST

BACKPACK_CONTAINER              = _G.BACKPACK_CONTAINER or _G.Enum and _G.Enum.BagIndex and _G.Enum.BagIndex.Backpack
BANK_CONTAINER                  = _G.BANK_CONTAINER or _G.Enum and _G.Enum.BagIndex and _G.Enum.BagIndex.Bank
REAGENTBANK_CONTAINER           = _G.REAGENTBANK_CONTAINER or _G.Enum and _G.Enum.BagIndex and _G.Enum.BagIndex.Reagentbank
NUM_BAG_SLOTS                   = _G.NUM_BAG_SLOTS or _G.Constants.InventoryConstants.NumBagSlots
NUM_BANKBAGSLOTS                = _G.NUM_BANKBAGSLOTS or _G.Constants.InventoryConstants.NumBankBagSlots
NUM_BANKGENERIC_SLOTS           = _G.NUM_BANKGENERIC_SLOTS or _G.Constants.InventoryConstants.NumGenericBankSlots

_ContainerBag                   = { BACKPACK_CONTAINER, 1, 2, 3, 4 }
_BankBag                        = { BANK_CONTAINER, 5, 6, 7, 8, 9, 10, 11 }

-- Event handler
function OnEnable()
    OnEnable                    = nil
    return handler:RefreshActionButtons()
end

-- Fix the GetContainerItemInfo
do
    local original              = GetContainerItemInfo
    local infoCache             = {}

    function GetContainerItemInfo(bag, slot)
        local time              = GetTime()
        local info              = infoCache[bag * 100 + slot]
        if not info or info.time < time - 0.1 then
            info                = original(bag, slot)
            if info then info.time = time end
        end

        if info then
            return info.iconFileID, info.stackCount, info.isLocked, info.quality, info.isReadable, info.hasLoot, info.hyperlink, info.isFiltered, info.hasNoValue, info.itemID, info.isBound
        end
    end
end

-- Fix the GetContainerItemQuestInfo
do
    local original              = GetContainerItemQuestInfo
    local infoCache             = {}
    if original then
        function GetContainerItemQuestInfo(bag, slot)
            local time          = GetTime()
            local info          = infoCache[bag * 100 + slot]
            if not info or info.time < time - 0.1 then
                info                = original(bag, slot)
                if info then info.time = time end
            end

            if info then
                return info.isQuestItem, info.questID, info.isActive
            end
        end
    else
        GetContainerItemQuestInfo = Toolset.fakefunc
    end
end

------------------------------------------------------
-- System Event Handler
------------------------------------------------------
__SystemEvent__()
function QUEST_ACCEPTED()
    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn in pairs(_BagCache[bag]) do
                handler:RefreshActionButtons(btn)
            end
        end
    end
end

__SystemEvent__()
function UNIT_QUEST_LOG_CHANGED(unit)
    if unit ~= "player" then return end

    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn in pairs(_BagCache[bag]) do
                handler:RefreshActionButtons(btn)
            end
        end
    end
end

local bagUpdateCache            = {}

__SystemEvent__()
function BAG_UPDATE(bag)
    if _BagCache[bag] then
        bagUpdateCache[bag]     = true
    end
end

__SystemEvent__()
function BAG_UPDATE_DELAYED()
    for bag in pairs(bagUpdateCache) do
        for btn in pairs(_BagCache[bag]) do
            handler:RefreshActionButtons(btn)
        end
    end
    wipe(bagUpdateCache)
end

__SystemEvent__()
function ITEM_LOCK_CHANGED(bag, slot)
    if _BagCache[bag] and slot then
        local _, _, locked      = GetContainerItemInfo(bag, slot)

        for btn, bslot in pairs(_BagCache[bag]) do
            if bslot == slot then
                -- Do it directly
                btn.IconLocked  = locked
            end
        end
    end
end

__SystemEvent__()
function BAG_UPDATE_COOLDOWN()
    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn in pairs(_BagCache[bag]) do
                handler:RefreshCooldown(btn)
            end
        end
    end
end

__SystemEvent__()
function INVENTORY_SEARCH_UPDATE()
    for bag, cache in pairs(_BagCache) do
        for btn, slot in pairs(cache) do
            handler:RefreshShowSearchOverlay(btn)
        end
    end
end

__SystemEvent__()
function BAG_NEW_ITEMS_UPDATED()
    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn, slot in pairs(_BagCache[bag]) do
                if C_NewItems.IsNewItem(bag, slot) then
                    handler:RefreshActionButtons(btn)
                end
            end
        end
    end
end

__SystemEvent__()
function MERCHANT_SHOW()
    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn, slot in pairs(_BagCache[bag]) do
                local texture, itemCount, locked, quality, readable, _, _, isFiltered, noValue, itemID = GetContainerItemInfo(bag, slot)
                if itemID then
                    btn.IsJunk  = (quality == LE_ITEM_QUALITY_POOR and not noValue)
                else
                    btn.IsJunk  = false
                end
            end
        end
    end
end

__SystemEvent__()
function MERCHANT_CLOSED()
    for _, bag in ipairs(_ContainerBag) do
        if _BagCache[bag] then
            for btn, slot in pairs(_BagCache[bag]) do
                btn.IsJunk      = false
            end
        end
    end
end

__SystemEvent__()
function PLAYERBANKSLOTS_CHANGED(slot)
    if slot <= NUM_BANKGENERIC_SLOTS then
        if _BagCache[BANK_CONTAINER] then
            for btn, bslot in pairs(_BagCache[BANK_CONTAINER]) do
                if bslot == slot then
                    handler:RefreshActionButtons(btn)
                end
            end
        end
    end
end

__SystemEvent__()
function PLAYERREAGENTBANKSLOTS_CHANGED(slot)
    if _BagCache[REAGENTBANK_CONTAINER] then
        for btn, bslot in pairs(_BagCache[REAGENTBANK_CONTAINER]) do
            if bslot == slot then
                handler:RefreshActionButtons(btn)
            end
        end
    end
end

if _G.IsContainerItemAnUpgrade then
    function RefreshUpgradeItem(self, bag, slot)
        local itemIsUpgrade         = IsContainerItemAnUpgrade(bag, slot)

        if itemIsUpgrade == nil then
            -- nil means not all the data was available to determine if this is an upgrade.
            self.IsUpgradeItem      = false
            Delay(0.5, RefreshUpgradeItem, self, bag, slot)
        else
            self.IsUpgradeItem      = itemIsUpgrade
        end
    end

    __SystemEvent__()
    function UNIT_INVENTORY_CHANGED(unit)
        if not unit or unit == "player" then
            for _, bag in ipairs(_ContainerBag) do
                if _BagCache[bag] then
                    for btn, slot in pairs(_BagCache[bag]) do
                        RefreshUpgradeItem(btn, bag, slot)
                    end
                end
            end
        end
    end

    __SystemEvent__()
    function PLAYER_SPECIALIZATION_CHANGED()
        for _, bag in ipairs(_ContainerBag) do
            if _BagCache[bag] then
                for btn, slot in pairs(_BagCache[bag]) do
                    RefreshUpgradeItem(btn, bag, slot)
                end
            end
        end
    end
else
    RefreshUpgradeItem          = Toolset.fakefunc
end

----------------------------------------
-- BagSlot Handler
----------------------------------------
-- Use fake container and item button to handle the click of item buttons
local fakeContainerFrame        = CreateFrame("Frame", "Scorpio_BagSlot_FakeContainer", _G.UIParent, "SecureFrameTemplate")
fakeContainerFrame:Hide()
local fakeItemButton            = CreateFrame("Button", "Scorpio_BagSlot_FakeItemButton", fakeContainerFrame, "ContainerFrameItemButtonTemplate, SecureFrameTemplate")
fakeItemButton:Hide()
fakeItemButton:SetToplevel(true)
fakeItemButton:SetFrameStrata("DIALOG")

handler.Manager:SetFrameRef("BagSlot_FakeContainer", fakeContainerFrame)
handler.Manager:SetFrameRef("BagSlot_FakeItemButton", fakeItemButton)
handler.Manager:Execute[[
    _BagSlot_FakeContainer      = Manager:GetFrameRef("BagSlot_FakeContainer")
    _BagSlot_FakeItemButton     = Manager:GetFrameRef("BagSlot_FakeItemButton")
]]

local function OnEnter(self)
    if self.IsNewItem then
        local bag, slot         = self.ActionTarget, self.ActionDetail
        if bag and slot then
            C_NewItems.RemoveNewItem(bag, slot)

            for btn, bslot in pairs(_BagCache[bag]) do
                if bslot == slot then
                    btn.IsNewItem = false
                end
            end
        end
    end

    if _G.ArtifactFrame and self._BagSlot_ItemID then
        _G.ArtifactFrame:OnInventoryItemMouseEnter(self.ActionTarget, self.ActionDetail)
    end
end

local function OnLeave(self)
    ResetCursor()

    if _G.ArtifactFrame then
        _G.ArtifactFrame:OnInventoryItemMouseLeave(self:GetParent():GetID(), self:GetID())
    end
end

local function OnShow(self)
    local bag                   = self.ActionTarget
    if bag then
        _BagCache[bag]          = _BagCache[bag] or {}
        _BagCache[bag][self]    = self.ActionDetail
    end
    return handler:RefreshActionButtons(self)
end

local function OnHide(self)
    for k, v in pairs(_BagCache) do
        v[self]                 = nil
    end
end

__SecureMethod__()
function handler.Manager:RegisterBagSlot(btnName)
    self                        = UI.GetProxyUI(_G[btnName])

    if self:IsVisible() then
        local bag               = self:GetAttribute("bag")
        _BagCache[bag]          = _BagCache[bag] or {}
        _BagCache[bag][self]    = self:GetAttribute("slot")
    end

    self.OnEnter                = self.OnEnter + OnEnter
    self.OnLeave                = self.OnLeave + OnLeave
    self.OnShow                 = self.OnShow + OnShow
    self.OnHide                 = self.OnHide + OnHide
end

__SecureMethod__()
function handler.Manager:UnregisterBagSlot(btnName)
    self                        = UI.GetProxyUI(_G[btnName])

    for k, v in pairs(_BagCache) do
        v[self]                 = nil
    end

    self.ItemQuality            = nil
    self.ItemQuestStatus        = nil
    self.IsJunk                 = false
    self.IsBattlePayItem        = false
    self.IsNewItem              = false
    self.IsUpgradeItem          = false
    self.IsArtifactRelicItem    = false

    self.OnEnter                = self.OnEnter - OnEnter
    self.OnLeave                = self.OnLeave - OnLeave
    self.OnShow                 = self.OnShow - OnShow
    self.OnHide                 = self.OnHide - OnHide
end

-- Overwrite methods
function handler:Refresh()
    local bag, slot             = self.ActionTarget, self.ActionDetail
    if not bag or not slot then return end

    local texture, itemCount, locked, quality, readable, _, _, isFiltered, noValue, itemID = GetContainerItemInfo(bag, slot)
    local isQuestItem, questId, isActive = GetContainerItemQuestInfo(bag, slot)

    if itemID then
        self._BagSlot_ItemID    = itemID
        self._BagSlot_Readable  = readable

        self.ItemQuality        = quality
        self.IsArtifactRelicItem= IsArtifactRelicItem(itemID)

        if questId and not isActive then
            self.ItemQuestStatus= false
        elseif questId or isQuestItem then
            self.ItemQuestStatus= true
        else
            self.ItemQuestStatus= nil
        end

        if MerchantFrame:IsShown() then
            self.IsJunk         = (quality == LE_ITEM_QUALITY_POOR and not noValue)
        else
            self.IsJunk         = false
        end

        self.IsBattlePayItem    = IsBattlePayItem(bag, slot)
        self.IsNewItem          = C_NewItems.IsNewItem(bag, slot)

        RefreshUpgradeItem(self, bag, slot)
    else
        self._BagSlot_ItemID    = nil
        self._BagSlot_Readable  = nil
        self.ItemQuality        = nil
        self.IconLocked         = false
        self.IsSearching        = false
        self.ItemQuestStatus    = nil
        self.IsJunk             = false
        self.IsBattlePayItem    = false
        self.IsNewItem          = false
        self.IsUpgradeItem      = false
        self.IsArtifactRelicItem= false
    end
end

function handler:ReceiveAction(target, detail)
    return PickupContainerItem(target, detail)
end

function handler:HasAction()
    return GetContainerItemID(self.ActionTarget, self.ActionDetail) and true or false
end

function handler:GetActionTexture()
    return (GetContainerItemInfo(self.ActionTarget, self.ActionDetail))
end

function handler:GetActionCount()
    return (select(2, GetContainerItemInfo(self.ActionTarget, self.ActionDetail)))
end

function handler:GetActionCooldown()
    return GetContainerItemCooldown(self.ActionTarget, self.ActionDetail)
end

function handler:IsEquippedItem()
    return false
end

function handler:IsActivedAction()
    return false
end

function handler:IsUsableAction()
    local bag                   = self.ActionTarget

    if bag >= 0 and bag <= 4 then
        local item              = GetContainerItemID(bag, self.ActionDetail)
        return item and IsUsableItem(item)
    else
        return true
    end
end

function handler:IsConsumableAction()
    local item                  = GetContainerItemID(self.ActionTarget, self.ActionDetail)
    if not item then return false end

    local maxStack              = select(8, GetItemInfo(item)) or 0
    return maxStack > 1
end

function handler:IsInRange()
    local bag                   = self.ActionTarget
    if bag >= 0 and bag <= 4 then
        return IsItemInRange(GetContainerItemID(self.ActionTarget, self.ActionDetail), self:GetAttribute("unit") or "target")
    end
end

function handler:IsSearchOverlayShow()
    return (select(8, GetContainerItemInfo(self.ActionTarget, self.ActionDetail)))
end

function handler:IsIconLocked()
    local _, _, locked          = GetContainerItemInfo(self.ActionTarget, self.ActionDetail)
    return locked
end

function handler:SetTooltip(GameTooltip)
    local bag                   = self.ActionTarget
    local slot                  = self.ActionDetail

    if ( (IsModifiedClick("COMPAREITEMS") or GetCVarBool("alwaysCompareItems")) ) then
        GameTooltip_ShowCompareItem(GameTooltip)
    end

    if bag == BANK_CONTAINER or bag == REAGENTBANK_CONTAINER then
        local invId

        if bag == BANK_CONTAINER then
            invId               = BankButtonIDToInvSlotID(slot)
        else
            invId               = ReagentBankButtonIDToInvSlotID(slot)
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")

        local hasItem, hasCooldown, repairCost, speciesID, level, breedQuality, maxHealth, power, speed, name = GameTooltip:SetInventoryItem("player", invId)
        if(speciesID and speciesID > 0) then
            BattlePetToolTip_Show(speciesID, level, breedQuality, maxHealth, power, speed, name)
            CursorUpdate(self)
            return
        end

        if IsModifiedClick("DRESSUP") and self._BagSlot_ItemID then
            ShowInspectCursor()
        elseif self._BagSlot_Readable then
            ShowInspectCursor()
        else
            ResetCursor()
        end
    else
        --GameTooltip:SetOwner(self, "ANCHOR_NONE")

        local showSell          = nil
        local hasCooldown, repairCost, speciesID, level, breedQuality, maxHealth, power, speed, name = GameTooltip:SetBagItem(bag, slot)

        GameTooltip:ClearAllPoints()
        if self:GetRight() < GetScreenWidth() / 2 then
            GameTooltip:SetPoint("BOTTOMLEFT", self, "TOPRIGHT")
        else
            GameTooltip:SetPoint("BOTTOMRIGHT", self, "TOPLEFT")
        end

        if speciesID and speciesID > 0 then
            BattlePetToolTip_Show(speciesID, level, breedQuality, maxHealth, power, speed, name)
            return
        else
            if _G.BattlePetTooltip then
                _G.BattlePetTooltip:Hide()
            end
        end

        if InRepairMode() and (repairCost and repairCost > 0) then
            GameTooltip:AddLine(REPAIR_COST, nil, nil, nil, true)
            SetTooltipMoney(IGAS:GetUI(GameTooltip), repairCost)
        elseif _G.MerchantFrame:IsShown() and _G.MerchantFrame.selectedTab == 1 then
            showSell            = 1
        end

        if IsModifiedClick("DRESSUP") and self._BagSlot_ItemID then
            ShowInspectCursor()
        elseif showSell then
            ShowContainerSellCursor(self.ActionTarget, self.ActionDetail)
        elseif self._BagSlot_Readable then
            ShowInspectCursor()
        else
            ResetCursor()
        end
    end
end

__Sealed__()
class "ContainerFrameItemButton" (function(_ENV)
    inherit "SecureActionButton"

    import "System.Reactive"

    ------------------------------------------------------
    -- Property
    ------------------------------------------------------
    --- The item's quality
    __Observable__()
    property "ItemQuality"      { type = Number }

    --- The item's quest status, true if actived quest, false if not actived quest, nil if not a quest item.
    __Observable__()
    property "ItemQuestStatus"  { type = Boolean }

    --- Whether the item is a new item
    __Observable__()
    property "IsNewItem"        { type = Boolean }

    --- Whether the item is a battle pay item
    __Observable__()
    property "IsBattlePayItem"  { type = Boolean }

    --- Whether show the item as junk
    __Observable__()
    property "IsJunk"           { type = Boolean }

    --- Whether the item is an upgrade item
    __Observable__()
    property "IsUpgradeItem"    { type = Boolean }

    --- Whether the item is an artifact relic item
    __Observable__()
    property "IsArtifactRelicItem" { type = Boolean }
end)

----------------------------------------
-- Client Patch
----------------------------------------
if Scorpio.IsRetail then return end

C_NewItems                      = _G.C_NewItems or {
    IsNewItem                   = Toolset.fakefunc,
    RemoveNewItem               = Toolset.fakefunc,
}

IsBattlePayItem                 = _G.IsBattlePayItem or Toolset.fakefunc
IsArtifactRelicItem             = _G.IsArtifactRelicItem or Toolset.fakefunc