-- Class icon auras (Midnight).

-- Mirrors Blizzard's arena DebuffFrame CC icon + cooldown swipe onto our class portrait.



local AC = _G.ArenaCore

if not AC then return end



if not (AC.Midnight and AC.Midnight.isMidnight) then return end



-- Master debug flag for aura debugging - set to false to completely disable all debug output
local AURA_DEBUG_ENABLED = false



local ClassIconAuras = {}

AC.ClassIconAuras = ClassIconAuras



local MAX_ARENA_ENEMIES = 3



ClassIconAuras.initialized = false

ClassIconAuras.hookedFrames = {}

ClassIconAuras.ccActive = {}

ClassIconAuras.importantActive = {}

ClassIconAuras.importantAuraData = {}



-- Maps arbitrary unit tokens (nameplate1, party1, target, etc.) to classIcon frames
ClassIconAuras.unitToClassIcon = {}



-- ════════════════════════════════════════════════════════════════
-- HELPER FUNCTIONS
-- ════════════════════════════════════════════════════════════════

local function IsPortraitGlowLineEnabled()

    local db = AC.DB and AC.DB.profile

    if not (db and db.bigDebuffs) then return true end

    return db.bigDebuffs.disablePortraitGlowLine ~= true

end



-- ════════════════════════════════════════════════════════════════
-- FRAME LOOKUP: Find a classIcon for any unit token
-- ════════════════════════════════════════════════════════════════

local function GetPlayerClassIconWrapper()
    if ClassIconAuras._playerWrapper then
        return ClassIconAuras._playerWrapper, PlayerFrame
    end

    if not _G.PlayerFrame or not _G.PlayerFrame.portrait then return nil end

    local wrapper = {}
    wrapper.icon = _G.PlayerFrame.portrait
    wrapper.overlay = nil
    wrapper.unit = "player"
    wrapper._parentFrame = _G.PlayerFrame

    -- Spell overlay: the "top sheet" that sits on top of the portrait.
    -- We paint spell icons here instead of overwriting the portrait texture.
    -- When hidden, the untouched portrait underneath shows through naturally.
    local spellOverlayFrame = CreateFrame("Frame", nil, _G.PlayerFrame)
    spellOverlayFrame:SetAllPoints(_G.PlayerFrame.portrait)
    spellOverlayFrame:SetFrameStrata("HIGH")
    spellOverlayFrame:SetFrameLevel((_G.PlayerFrame:GetFrameLevel() or 1) + 6)
    local spellOverlay = spellOverlayFrame:CreateTexture(nil, "OVERLAY")
    spellOverlay:SetAllPoints(spellOverlayFrame)
    spellOverlay:SetTexture(nil)
    spellOverlay:Hide()
    
    -- Circular mask for the spell overlay so it fits inside the round portrait
    local spellMask = spellOverlayFrame:CreateMaskTexture()
    spellMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    spellMask:SetAllPoints(spellOverlayFrame)
    spellOverlay:AddMaskTexture(spellMask)
    wrapper.spellMask = spellMask
    
    wrapper.spellOverlay = spellOverlay
    wrapper.spellOverlayFrame = spellOverlayFrame

    -- Build a cooldown overlay that sits on top of everything
    -- Parent it to spellOverlayFrame so it shares the same masked coordinate space
    local cd = CreateFrame("Cooldown", "ArenaCorePlayerPortraitCooldown", spellOverlayFrame, "CooldownFrameTemplate")
    cd:SetAllPoints(spellOverlayFrame)
    cd:SetFrameStrata("HIGH")
    cd:SetFrameLevel(spellOverlayFrame:GetFrameLevel() + 1)
    cd:SetHideCountdownNumbers(false)
    
    -- Use circular swipe texture to eliminate sharp darkened corners
    if cd.SetSwipeTexture then
        cd:SetSwipeTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
    end
    
    -- Circular mask for the cooldown swipe - apply to ALL textures to clip the square spiral
    local cdMask = cd:CreateMaskTexture()
    cdMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    cdMask:SetAllPoints(cd)
    
    local function ApplyCooldownMask(self)
        local regions = { self:GetRegions() }
        for _, region in ipairs(regions) do
            if region and region.GetObjectType and region:GetObjectType() == "Texture" then
                pcall(function() region:AddMaskTexture(cdMask) end)
            end
        end
    end
    -- Apply mask immediately to catch existing textures
    ApplyCooldownMask(cd)
    -- Hook SetCooldown to catch textures created when the spiral starts
    hooksecurefunc(cd, "SetCooldown", function(self) ApplyCooldownMask(self) end)
    
    cd:SetDrawEdge(IsPortraitGlowLineEnabled())
    cd:SetDrawBling(false)

    wrapper.importantAuraCooldown = cd

    ClassIconAuras._playerWrapper = wrapper
    return wrapper, _G.PlayerFrame
end



local function GetTargetClassIconWrapper()
    if ClassIconAuras._targetWrapper then
        return ClassIconAuras._targetWrapper, TargetFrame
    end

    if not _G.TargetFrame or not _G.TargetFrame.portrait then return nil end

    local wrapper = {}
    wrapper.icon = _G.TargetFrame.portrait
    wrapper.overlay = nil
    wrapper.unit = "target"
    wrapper._parentFrame = _G.TargetFrame

    local spellOverlayFrame = CreateFrame("Frame", nil, _G.TargetFrame)
    spellOverlayFrame:SetAllPoints(_G.TargetFrame.portrait)
    spellOverlayFrame:SetFrameStrata("HIGH")
    spellOverlayFrame:SetFrameLevel((_G.TargetFrame:GetFrameLevel() or 1) + 6)
    local spellOverlay = spellOverlayFrame:CreateTexture(nil, "OVERLAY")
    spellOverlay:SetAllPoints(spellOverlayFrame)
    spellOverlay:SetTexture(nil)
    spellOverlay:Hide()
    
    -- Circular mask for the spell overlay so it fits inside the round portrait
    local spellMask = spellOverlayFrame:CreateMaskTexture()
    spellMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    spellMask:SetAllPoints(spellOverlayFrame)
    spellOverlay:AddMaskTexture(spellMask)
    wrapper.spellMask = spellMask
    
    wrapper.spellOverlay = spellOverlay
    wrapper.spellOverlayFrame = spellOverlayFrame

    local cd = CreateFrame("Cooldown", "ArenaCoreTargetPortraitCooldown", spellOverlayFrame, "CooldownFrameTemplate")
    cd:SetAllPoints(spellOverlayFrame)
    cd:SetFrameStrata("HIGH")
    cd:SetFrameLevel(spellOverlayFrame:GetFrameLevel() + 1)
    cd:SetHideCountdownNumbers(false)
    
    -- Use circular swipe texture to eliminate sharp darkened corners
    if cd.SetSwipeTexture then
        cd:SetSwipeTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
    end
    
    -- Circular mask for the cooldown swipe - apply to ALL textures to clip the square spiral
    local cdMask = cd:CreateMaskTexture()
    cdMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    cdMask:SetAllPoints(cd)
    
    local function ApplyCooldownMask(self)
        local regions = { self:GetRegions() }
        for _, region in ipairs(regions) do
            if region and region.GetObjectType and region:GetObjectType() == "Texture" then
                pcall(function() region:AddMaskTexture(cdMask) end)
            end
        end
    end
    ApplyCooldownMask(cd)
    hooksecurefunc(cd, "SetCooldown", function(self) ApplyCooldownMask(self) end)
    
    wrapper.importantAuraCooldown = cd

    ClassIconAuras._targetWrapper = wrapper
    return wrapper, _G.TargetFrame
end



local function GetFocusClassIconWrapper()
    if ClassIconAuras._focusWrapper then
        return ClassIconAuras._focusWrapper, FocusFrame
    end

    if not _G.FocusFrame or not _G.FocusFrame.portrait then return nil end

    local wrapper = {}
    wrapper.icon = _G.FocusFrame.portrait
    wrapper.overlay = nil
    wrapper.unit = "focus"
    wrapper._parentFrame = _G.FocusFrame

    local spellOverlayFrame = CreateFrame("Frame", nil, _G.FocusFrame)
    spellOverlayFrame:SetAllPoints(_G.FocusFrame.portrait)
    spellOverlayFrame:SetFrameStrata("HIGH")
    spellOverlayFrame:SetFrameLevel((_G.FocusFrame:GetFrameLevel() or 1) + 6)
    local spellOverlay = spellOverlayFrame:CreateTexture(nil, "OVERLAY")
    spellOverlay:SetAllPoints(spellOverlayFrame)
    spellOverlay:SetTexture(nil)
    spellOverlay:Hide()
    
    -- Circular mask for the spell overlay so it fits inside the round portrait
    local spellMask = spellOverlayFrame:CreateMaskTexture()
    spellMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    spellMask:SetAllPoints(spellOverlayFrame)
    spellOverlay:AddMaskTexture(spellMask)
    wrapper.spellMask = spellMask
    
    wrapper.spellOverlay = spellOverlay
    wrapper.spellOverlayFrame = spellOverlayFrame

    local cd = CreateFrame("Cooldown", "ArenaCoreFocusPortraitCooldown", spellOverlayFrame, "CooldownFrameTemplate")
    cd:SetAllPoints(spellOverlayFrame)
    cd:SetFrameStrata("HIGH")
    cd:SetFrameLevel(spellOverlayFrame:GetFrameLevel() + 1)
    cd:SetHideCountdownNumbers(false)
    
    -- Use circular swipe texture to eliminate sharp darkened corners
    if cd.SetSwipeTexture then
        cd:SetSwipeTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask")
    end
    
    -- Circular mask for the cooldown swipe - apply to ALL textures to clip the square spiral
    local cdMask = cd:CreateMaskTexture()
    cdMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    cdMask:SetAllPoints(cd)
    
    local function ApplyCooldownMask(self)
        local regions = { self:GetRegions() }
        for _, region in ipairs(regions) do
            if region and region.GetObjectType and region:GetObjectType() == "Texture" then
                pcall(function() region:AddMaskTexture(cdMask) end)
            end
        end
    end
    ApplyCooldownMask(cd)
    hooksecurefunc(cd, "SetCooldown", function(self) ApplyCooldownMask(self) end)
    
    cd:SetDrawEdge(IsPortraitGlowLineEnabled())
    cd:SetDrawBling(false)

    wrapper.importantAuraCooldown = cd

    ClassIconAuras._focusWrapper = wrapper
    return wrapper, _G.FocusFrame
end



local function FindClassIconForUnit(unitToken)
    if not unitToken then return nil end

    -- 1) Player frame
    if unitToken == "player" then
        return GetPlayerClassIconWrapper()
    end

    -- 2) Target frame
    if unitToken == "target" then
        return GetTargetClassIconWrapper()
    end

    -- 3) Focus frame
    if unitToken == "focus" then
        return GetFocusClassIconWrapper()
    end

    -- NOTE: Arena frames are EXCLUDED from portrait spells system
    -- Arena frames use their own separate CC/important aura system via HookDebuffFrame
    -- Portrait spells should ONLY work on player/target/focus/nameplates

    -- 4) Nameplates with ArenaCore PartyClassIndicators
    for _, nameplate in ipairs(C_NamePlate.GetNamePlates()) do
        local u = nameplate.unit or nameplate.namePlateUnitToken
        if u == unitToken then
            if nameplate.classIcon then
                return nameplate.classIcon, nameplate
            elseif nameplate.ArenaCore and nameplate.ArenaCore.classFrame then
                return nameplate.ArenaCore.classFrame, nameplate
            end
        end
    end

    -- 4) Manual override table (for future integrations)
    local manual = ClassIconAuras.unitToClassIcon[unitToken]
    if manual then
        return manual, manual._parentFrame
    end

    return nil
end



-- Check if class icon auras are disabled.

local function IsDisabled()

    local db = AC.DB and AC.DB.profile

    return db and db.classIcons and db.classIcons.showAuras == false

end



local function IsSpiralGlowLineEnabled()

    local db = AC.DB and AC.DB.profile

    if not (db and db.classIcons) then return true end

    return db.classIcons.spiralGlowLine ~= false

end



local function IsPortraitSpellsEnabled()

    local db = AC.DB and AC.DB.profile

    return db and db.bigDebuffs and db.bigDebuffs.showSpellsOnPortraits == true

end



local function IsImportantAurasEnabled()
    -- Disabled due to recent Blizzard API changes (HELPFUL|IMPORTANT filter removed/changed)
    -- May come back in the future if the API is restored
    return false
end



local function ShouldScanNonArenaPortraits()
    -- Important auras disabled due to API changes; only portrait spells can still drive scanning
    return IsPortraitSpellsEnabled()
end



-- In Midnight arena, classification APIs return secret booleans. The aura filter
-- already narrowed the list to the right category, so treat secret as valid.
local function IsTruthyOrSecret(value)
    if issecretvalue(value) then return true end
    local success, result = pcall(function() return value == true end)
    return success and result
end

-- GetUnitAuras exposes the icon for arena units when GetAuraDataByAuraInstanceID
-- returns secret fields. Find the matching aura by instanceID and return its icon.
local function GetUnitAurasIconByInstanceID(unitToken, auraInstanceID)
    if not (unitToken and auraInstanceID and C_UnitAuras and C_UnitAuras.GetUnitAuras) then return nil end
    local sortRule = Enum.UnitAuraSortRule and Enum.UnitAuraSortRule.Unsorted or nil
    local sortDir = Enum.UnitAuraSortDirection and Enum.UnitAuraSortDirection.Normal or nil
    local auras = C_UnitAuras.GetUnitAuras(unitToken, "HARMFUL|CROWD_CONTROL", nil, sortRule, sortDir)
    if not auras then return nil end
    for _, aura in ipairs(auras) do
        if aura.auraInstanceID == auraInstanceID then
            -- icon may be secret in arena but SetTexture handles it fine
            if aura.icon then
                return aura.icon
            end
            if aura.spellId then
                local ok, tex = pcall(C_Spell.GetSpellTexture, aura.spellId)
                if ok and tex then
                    return tex
                end
            end
            return nil
        end
    end
    return nil
end



local function IsActiveLossOfControlAura(unit, auraInstanceID)

    if not (unit and auraInstanceID and C_LossOfControl and C_LossOfControl.GetActiveLossOfControlDataCountByUnit and C_LossOfControl.GetActiveLossOfControlDataByUnit) then
        return false
    end

    local count = C_LossOfControl.GetActiveLossOfControlDataCountByUnit(unit) or 0
    for i = 1, count do
        local data = C_LossOfControl.GetActiveLossOfControlDataByUnit(unit, i)
        if data and data.auraInstanceID == auraInstanceID then
            return true
        end
    end

    return false

end



local function IsImportantSpiralGlowLineEnabled()
    -- Disabled alongside important auras due to API changes
    return false
end



local function GetFontSize()

    local db = AC.DB and AC.DB.profile

    if db and db.classIcons and db.classIcons.fontSize then

        return db.classIcons.fontSize

    end

    return 14  -- Default font size

end



-- Midnight fires UNIT_AURA constantly, so we bail early when the payload
-- doesn't actually carry any changes worth reacting to.
local function AuraPayloadHasChanges(updateInfo)
    -- No updateInfo means a full refresh is needed
    if not updateInfo then return true end

    -- Full update flag from Blizzard means everything changed
    if updateInfo.isFullUpdate then return true end

    -- Check each delta bucket Blizzard provides on UNIT_AURA
    local buckets = {
        updateInfo.addedAuras,
        updateInfo.updatedAuraInstanceIDs,
        updateInfo.removedAuraInstanceIDs,
    }
    for i = 1, #buckets do
        if buckets[i] and buckets[i][1] then
            return true
        end
    end
    return false
end



-- Restore class icon after CC ends.

local function RestoreClassIcon(arenaIndex, classIcon, acFrame)

    classIcon.ccActive = false

    ClassIconAuras.ccActive[arenaIndex] = false

    if classIcon.auraCooldown then

        classIcon.auraCooldown:Clear()

    end

    if classIcon.importantAuraCooldown then
        classIcon.importantAuraCooldown:Clear()
        classIcon.importantAuraCooldown.durationObj = nil
    end



    local db = AC.DB and AC.DB.profile

    local showSpecInstead = db and db.classIcons and db.classIcons.showSpecInstead == true

    if showSpecInstead and acFrame and acFrame.specID and GetSpecializationInfoByID then

        local _, _, _, specIcon = GetSpecializationInfoByID(acFrame.specID)

        if specIcon and classIcon.icon and classIcon.icon.SetTexture then

            classIcon.icon:SetTexture(specIcon)

            if classIcon.icon.SetTexCoord then

                -- Restore normal texture coordinates (full icon, not cropped)
                classIcon.icon:SetTexCoord(0, 1, 0, 1)

            end

        end

        if classIcon.overlay and classIcon.overlay.SetTexture then

            classIcon.overlay:SetTexture("Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Overlays\\orangeoverlay.tga")

        end

        return

    end



    local storedClass = acFrame.class

    if storedClass and classIcon.UpdateClassIcon then

        classIcon.UpdateClassIcon(storedClass)

        -- Make sure border matches the user's setting after restoring the icon
        if classIcon.ApplyBorderStyle then
            classIcon.ApplyBorderStyle()
        end

    end

end



-- Get a safe icon from aura data. In Midnight arena, GetAuraDataByAuraInstanceID
-- returns secret fields, but GetUnitAuras exposes the icon for the same aura.
local function GetSafeAuraIcon(unitToken, auraInstanceID, auraData)
    -- icon from GetUnitAuras may be secret in arena but SetTexture handles it fine
    if auraData and auraData.icon then
        return auraData.icon
    end
    if auraData and auraData.spellId then
        local ok, tex = pcall(C_Spell.GetSpellTexture, auraData.spellId)
        if ok and tex then
            return tex
        end
    end
    return GetUnitAurasIconByInstanceID(unitToken, auraInstanceID)
end

-- Resolve CC icon from C_LossOfControl when Blizzard's texture is secret in arena.
-- In Midnight, GetAuraDataByAuraInstanceID returns secret fields, so we use the
-- GetUnitAuras path instead to fetch the non-secret icon.
local function ResolveCCIconFromLossOfControl(unitToken)
    if not (C_LossOfControl and C_LossOfControl.GetActiveLossOfControlDataCountByUnit) then return nil end
    local count = C_LossOfControl.GetActiveLossOfControlDataCountByUnit(unitToken)
    if not count or count == 0 then return nil end
    local bestPriority, bestIndex = -1, nil
    for i = 1, count do
        local data = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, i)
        if data and data.priority > bestPriority then
            bestPriority = data.priority
            bestIndex = i
        end
    end
    if bestIndex then
        local bestData = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, bestIndex)
        if bestData and bestData.auraInstanceID then
            return GetSafeAuraIcon(unitToken, bestData.auraInstanceID, nil)
        end
    end
    return nil
end

function ClassIconAuras:HookDebuffFrame(arenaIndex)

    local _, instanceType = IsInInstance()

    if instanceType ~= "arena" then return end

    if IsActiveBattlefieldArena and not IsActiveBattlefieldArena() then return end

    if self.hookedFrames[arenaIndex] then return end



    local blizzArenaFrame = _G["CompactArenaFrameMember" .. arenaIndex]

    if not blizzArenaFrame then return end



    local debuffFrame = blizzArenaFrame.DebuffFrame

    if not debuffFrame then return end



    local MFM = AC.MasterFrameManager

    local acFrame = MFM and MFM.frames and MFM.frames[arenaIndex]

    if not acFrame then return end



    local classIcon = acFrame.classIcon

    if not classIcon then return end

    -- Robust icon discovery: Blizzard frame structures vary by build/UI settings.
    -- We check multiple common paths so the hook lands on the actual Texture object.
    local debuffIcon = debuffFrame.Icon
    if not (debuffIcon and debuffIcon.SetTexture) then
        debuffIcon = debuffFrame.icon
    end
    if not (debuffIcon and debuffIcon.SetTexture) then
        debuffIcon = debuffFrame.Texture
    end
    if not (debuffIcon and debuffIcon.SetTexture) then
        debuffIcon = debuffFrame.texture
    end
    -- Some templates nest the texture inside an Icon frame widget
    if not (debuffIcon and debuffIcon.SetTexture) and debuffFrame.Icon then
        local child = debuffFrame.Icon.texture or debuffFrame.Icon.Texture
        if child and child.SetTexture then
            debuffIcon = child
        end
    end

    -- Cooldown overlay: swipe + numbers.

    local okCooldown, cooldownOrErr = pcall(function()

        local cd = CreateFrame("Cooldown", nil, classIcon, "CooldownFrameTemplate")

        if cd.SetHideCountdownNumbers then

            cd:SetHideCountdownNumbers(false)

        end

        local r = { cd:GetRegions() }

        for i = 1, #r do

            local region = r[i]

            if region and region.GetObjectType and region:GetObjectType() == "FontString" and region.SetFont then

                region:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", GetFontSize(), "OUTLINE")

            end

        end

        local anchor = classIcon.icon or classIcon

        cd:ClearAllPoints()

        cd:SetAllPoints(anchor)

        cd:SetDrawEdge(IsSpiralGlowLineEnabled())

        cd:SetDrawBling(false)

        cd:SetReverse(false)

        if cd.SetUseCircularEdge then

            cd:SetUseCircularEdge(true)

        end

        if cd.SetSwipeColor then

            cd:SetSwipeColor(0, 0, 0, 0.75)

        end

        return cd

    end)

    classIcon.auraCooldown = okCooldown and cooldownOrErr or nil



    -- Important Auras cooldown overlay: separate layer for priority

    local okImportantCooldown, importantCooldownOrErr = pcall(function()

        local cd = CreateFrame("Cooldown", nil, classIcon, "CooldownFrameTemplate")

        if cd.SetHideCountdownNumbers then

            cd:SetHideCountdownNumbers(false)

        end

        local r = { cd:GetRegions() }

        for i = 1, #r do

            local region = r[i]

            if region and region.GetObjectType and region:GetObjectType() == "FontString" and region.SetFont then

                -- Use ArenaCore's font for consistency and better visibility
                region:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", GetFontSize(), "OUTLINE")

            end

        end

        local anchor = classIcon.icon or classIcon

        cd:ClearAllPoints()

        cd:SetAllPoints(anchor)

        cd:SetDrawEdge(IsImportantSpiralGlowLineEnabled())

        cd:SetDrawBling(false)

        cd:SetReverse(false)

        if cd.SetUseCircularEdge then

            cd:SetUseCircularEdge(true)

        end

        if cd.SetSwipeColor then

            cd:SetSwipeColor(0, 0.5, 0, 0.75) -- Green tint for important auras

        end

        -- MEDIUM strata keeps the timer text from getting buried under other elements
        cd:SetFrameStrata("MEDIUM")
        
        -- White text keeps the countdown readable against any icon
        if cd.SetCooldownTextColor then
            cd:SetCooldownTextColor(1, 1, 1) -- White text for maximum visibility
        end

        return cd

    end)

    classIcon.importantAuraCooldown = okImportantCooldown and importantCooldownOrErr or nil



    -- Hook 1: mirror CC texture.

    -- Blizzard swaps the icon back to the question mark when CC ends.

    if debuffIcon then

        hooksecurefunc(debuffIcon, "SetTexture", function(_, tex)

            if IsDisabled() then

                if classIcon.ccActive then

                    RestoreClassIcon(arenaIndex, classIcon, acFrame)

                end

                return

            end

            -- Blizzard uses QUESTION_MARK_ICON = 134400 (numeric), not the string path.
            local isQuestionMark = not issecretvalue(tex) and
                (tex == 134400 or tex == "INTERFACE\\ICONS\\INV_MISC_QUESTIONMARK.BLP")

            if isQuestionMark then

                -- CC ended - clear the timer and check for important auras.
                classIcon.ccActive = false

                ClassIconAuras.ccActive[arenaIndex] = false

                if classIcon.auraCooldown then
                    classIcon.auraCooldown:Clear()
                end

                -- Restore the class icon to its base texture NOW, before scanning
                -- for important auras. If we skip this, ShowImportantAura grabs
                -- whatever is currently on the icon (the CC texture) as originalTexture,
                -- then restores it when the important aura ends - causing a CC flash.
                RestoreClassIcon(arenaIndex, classIcon, acFrame)

                ClassIconAuras:UpdateImportantAura(arenaIndex)

            else

                -- CC active (tex is the spell icon, may be secret in Midnight).
                classIcon.ccActive = true

                ClassIconAuras.ccActive[arenaIndex] = true

                ClassIconAuras:ClearImportantAura(arenaIndex)

                local iconTexture = classIcon.icon or classIcon
                if iconTexture and iconTexture.SetTexture then

                    local safeTex = tex
                    if issecretvalue(tex) then
                        -- Try resolving via C_LossOfControl first
                        safeTex = ResolveCCIconFromLossOfControl("arena" .. arenaIndex)
                        -- If resolution failed, try the secret value directly
                        -- SetTexture can handle secret texture IDs fine
                        if not safeTex then
                            safeTex = tex
                        end
                    end
                    if safeTex then
                        pcall(function()
                            iconTexture:SetTexture(safeTex)
                            iconTexture:SetTexCoord(0.1, 0.9, 0.1, 0.9)
                        end)
                    end

                end

            end

        end)

        -- Blizzard sometimes uses SetAtlas instead of SetTexture (Midnight/Retail).
        if debuffIcon.SetAtlas then
            hooksecurefunc(debuffIcon, "SetAtlas", function(_, atlas)
                if IsDisabled() then
                    if classIcon.ccActive then
                        RestoreClassIcon(arenaIndex, classIcon, acFrame)
                    end
                    return
                end
                if atlas and atlas ~= "" then
                    classIcon.ccActive = true
                    ClassIconAuras.ccActive[arenaIndex] = true
                    ClassIconAuras:ClearImportantAura(arenaIndex)
                    local iconTexture = classIcon.icon or classIcon
                    if iconTexture and iconTexture.SetAtlas then
                        pcall(function()
                            iconTexture:SetAtlas(atlas)
                        end)
                    end
                end
            end)
        end

    end



    -- Hook 2: mirror cooldown swipe + a safety net when it finishes.

    if debuffFrame.Cooldown then

        hooksecurefunc(debuffFrame.Cooldown, "SetCooldown", function()

            if IsDisabled() or not classIcon.auraCooldown then return end

            -- Midnight 12.0+: SetCooldown args are secret, so we query C_LossOfControl

            -- directly. priority is NeverSecret so comparisons are safe. The duration

            -- object API is the approved path that bypasses secret number restrictions.

            local unitToken = "arena" .. arenaIndex

            local count = C_LossOfControl.GetActiveLossOfControlDataCountByUnit(unitToken)

            local bestPriority, bestIndex = -1, nil

            for i = 1, count do

                local data = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, i)

                if data and data.priority > bestPriority then

                    bestPriority = data.priority

                    bestIndex = i

                end

            end

            if bestIndex then

                local durationObj = C_LossOfControl.GetActiveLossOfControlDuration(unitToken, bestIndex)

                if durationObj then

                    -- Avoid IsZero() here: it can produce a secret boolean in Midnight.

                    -- SetCooldownFromDurationObject(clearIfZero=true) handles clearing.

                    pcall(classIcon.auraCooldown.SetCooldownFromDurationObject, classIcon.auraCooldown, durationObj, true)

                    if classIcon.auraCooldown.SetHideCountdownNumbers then

                        classIcon.auraCooldown:SetHideCountdownNumbers(false)

                    end

                end

                -- Fallback: resolve the icon directly from C_LossOfControl aura data.
                -- Some users have debuffFrame.Icon structured differently (Frame vs Texture,
                -- or SetAtlas instead of SetTexture) so the SetTexture hook above misses it.
                -- We query the same API the cooldown uses and apply the icon ourselves.
                local bestData = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, bestIndex)
                if bestData and bestData.auraInstanceID then
                    local safeIcon = GetSafeAuraIcon(unitToken, bestData.auraInstanceID, nil)
                    if safeIcon then
                        local iconTexture = classIcon.icon or classIcon
                        if iconTexture and iconTexture.SetTexture then
                            pcall(function()
                                iconTexture:SetTexture(safeIcon)
                                iconTexture:SetTexCoord(0.1, 0.9, 0.1, 0.9)
                            end)
                            classIcon.ccActive = true
                            ClassIconAuras.ccActive[arenaIndex] = true
                            ClassIconAuras:ClearImportantAura(arenaIndex)
                        end
                    end
                end

            end

        end)

        debuffFrame.Cooldown:HookScript("OnCooldownDone", function()

            if classIcon.ccActive then

                RestoreClassIcon(arenaIndex, classIcon, acFrame)

            end

        end)

    end



    -- Safety net: on Retail, Blizzard doesn't always swap the icon back to question mark.

    -- Sometimes it just hides the DebuffFrame / Icon when CC ends.

    if debuffFrame.HookScript then

        debuffFrame:HookScript("OnHide", function()

            if classIcon.ccActive then

                RestoreClassIcon(arenaIndex, classIcon, acFrame)

            end

        end)

        -- Safety net: if Blizzard re-shows the DebuffFrame while CC is still active,
        -- re-apply the spell icon so it doesn't get stuck on the class icon.
        debuffFrame:HookScript("OnShow", function()
            if IsDisabled() then return end
            local unitToken = "arena" .. arenaIndex
            local count = C_LossOfControl.GetActiveLossOfControlDataCountByUnit(unitToken)
            if count and count > 0 then
                local bestPriority, bestIndex = -1, nil
                for i = 1, count do
                    local data = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, i)
                    if data and data.priority > bestPriority then
                        bestPriority = data.priority
                        bestIndex = i
                    end
                end
                if bestIndex and bestIndex ~= 0 then
                    local bestData = C_LossOfControl.GetActiveLossOfControlDataByUnit(unitToken, bestIndex)
                    if bestData and bestData.auraInstanceID then
                        local safeIcon = GetSafeAuraIcon(unitToken, bestData.auraInstanceID, nil)
                        if safeIcon then
                            local iconTexture = classIcon.icon or classIcon
                            if iconTexture and iconTexture.SetTexture then
                                pcall(function()
                                    iconTexture:SetTexture(safeIcon)
                                    iconTexture:SetTexCoord(0.1, 0.9, 0.1, 0.9)
                                end)
                                classIcon.ccActive = true
                                ClassIconAuras.ccActive[arenaIndex] = true
                                ClassIconAuras:ClearImportantAura(arenaIndex)
                            end
                        end
                    end
                end
            end
        end)

    end



    if debuffIcon then

        if debuffIcon.HookScript then

            debuffIcon:HookScript("OnHide", function()

                if classIcon.ccActive then

                    RestoreClassIcon(arenaIndex, classIcon, acFrame)

                end

            end)

        end



        if debuffIcon.SetShown then

            hooksecurefunc(debuffIcon, "SetShown", function(_, shown)

                if shown == false and classIcon.ccActive then

                    RestoreClassIcon(arenaIndex, classIcon, acFrame)

                end

            end)

        end



        if debuffIcon.SetAlpha then

            hooksecurefunc(debuffIcon, "SetAlpha", function(_, a)

                local alpha = tonumber(a)

                if alpha and alpha <= 0.01 and classIcon.ccActive then

                    RestoreClassIcon(arenaIndex, classIcon, acFrame)

                end

            end)

        end

    end



    self.hookedFrames[arenaIndex] = true

end



function ClassIconAuras:UpdateSpiralGlowLine()

    local enabled = IsSpiralGlowLineEnabled()

    local MFM = AC.MasterFrameManager

    if not MFM or not MFM.frames then return end



    for i = 1, MAX_ARENA_ENEMIES do

        local acFrame = MFM.frames[i]

        local classIcon = acFrame and acFrame.classIcon

        local cd = classIcon and classIcon.auraCooldown

        if cd and cd.SetDrawEdge then

            cd:SetDrawEdge(enabled)

        end

    end

end



function ClassIconAuras:UpdateImportantSpiralGlowLine()

    local enabled = IsImportantSpiralGlowLineEnabled()

    local MFM = AC.MasterFrameManager

    if not MFM or not MFM.frames then return end



    for i = 1, MAX_ARENA_ENEMIES do

        local acFrame = MFM.frames[i]

        local classIcon = acFrame and acFrame.classIcon

        local cd = classIcon and classIcon.importantAuraCooldown

        if cd and cd.SetDrawEdge then

            cd:SetDrawEdge(enabled)

        end

    end

end



function ClassIconAuras:UpdatePortraitGlowLine()

    local enabled = IsPortraitGlowLineEnabled()

    -- Update player, target, and focus portrait cooldowns
    local wrappers = {
        ClassIconAuras._playerWrapper,
        ClassIconAuras._targetWrapper,
        ClassIconAuras._focusWrapper,
    }

    for _, wrapper in ipairs(wrappers) do
        local cd = wrapper and wrapper.importantAuraCooldown
        if cd and cd.SetDrawEdge then
            cd:SetDrawEdge(enabled)
        end
    end

    -- Also update any registered non-arena frames (nameplates, etc.)
    for _, classIcon in pairs(ClassIconAuras.unitToClassIcon or {}) do
        local cd = classIcon and classIcon.importantAuraCooldown
        if cd and cd.SetDrawEdge then
            cd:SetDrawEdge(enabled)
        end
    end

end



function ClassIconAuras:UpdateFontSize()

    local fontSize = GetFontSize()

    local MFM = AC.MasterFrameManager

    if not MFM or not MFM.frames then return end



    for i = 1, MAX_ARENA_ENEMIES do

        local acFrame = MFM.frames[i]

        local classIcon = acFrame and acFrame.classIcon



        -- Update CC aura cooldown font size

        local ccCd = classIcon and classIcon.auraCooldown

        if ccCd then

            local r = { ccCd:GetRegions() }

            for j = 1, #r do

                local region = r[j]

                if region and region.GetObjectType and region:GetObjectType() == "FontString" and region.SetFont then

                    region:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", fontSize, "OUTLINE")

                end

            end

        end



        -- Update important aura cooldown font size

        local importantCd = classIcon and classIcon.importantAuraCooldown

        if importantCd then

            local r = { importantCd:GetRegions() }

            for j = 1, #r do

                local region = r[j]

                if region and region.GetObjectType and region:GetObjectType() == "FontString" and region.SetFont then

                    region:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", fontSize, "OUTLINE")

                end

            end

        end

    end

end



-- Priority-based aura detection system for Midnight
-- Scans auras by priority category, returning first valid match
-- Winner-takes-all priority: Kick (interrupt) > CC > Defensive > Important
local function ScanForPriorityAura(unit)
    local trackedInstanceIDs = {}

    -- Priority 0: Interrupts (highest - kick/silence/lockout effects)
    local interruptAuras = C_UnitAuras.GetUnitAuras(unit, "HARMFUL|INTERRUPT")
    if interruptAuras then
        for _, aura in ipairs(interruptAuras) do
            local instanceID = aura.auraInstanceID
            if not trackedInstanceIDs[instanceID] then
                trackedInstanceIDs[instanceID] = true
                if IsActiveLossOfControlAura(unit, instanceID) then
                    return aura.spellId, aura.icon, instanceID, aura.applications, aura
                end
            end
        end
    end

    -- Priority 1: Crowd Control effects
    local ccAuras = C_UnitAuras.GetUnitAuras(unit, "HARMFUL|CROWD_CONTROL")
    if ccAuras then
        for _, aura in ipairs(ccAuras) do
            local instanceID = aura.auraInstanceID
            if not trackedInstanceIDs[instanceID] then
                trackedInstanceIDs[instanceID] = true
                local isCC = C_Spell.IsSpellCrowdControl(aura.spellId)
                if IsTruthyOrSecret(isCC) and IsActiveLossOfControlAura(unit, instanceID) then
                    return aura.spellId, aura.icon, instanceID, aura.applications, aura
                end
            end
        end
    end

    -- Priority 2: Major defensives
    local bigDefensives = C_UnitAuras.GetUnitAuras(unit, "HELPFUL|BIG_DEFENSIVE")
    if bigDefensives then
        for _, aura in ipairs(bigDefensives) do
            local instanceID = aura.auraInstanceID
            if not trackedInstanceIDs[instanceID] then
                trackedInstanceIDs[instanceID] = true
                local isBigDefensive = C_UnitAuras.AuraIsBigDefensive(aura.spellId)
                if IsTruthyOrSecret(isBigDefensive) then
                    return aura.spellId, aura.icon, instanceID, aura.applications, aura
                end
            end
        end
    end

    -- Priority 3: External defensives
    local externalDefensives = C_UnitAuras.GetUnitAuras(unit, "HELPFUL|EXTERNAL_DEFENSIVE")
    if externalDefensives then
        for _, aura in ipairs(externalDefensives) do
            local instanceID = aura.auraInstanceID
            if not trackedInstanceIDs[instanceID] then
                trackedInstanceIDs[instanceID] = true
                local isExternalDefensive = C_UnitAuras.AuraIsExternalDefensive and C_UnitAuras.AuraIsExternalDefensive(aura.spellId)
                if IsTruthyOrSecret(isExternalDefensive) then
                    return aura.spellId, aura.icon, instanceID, aura.applications, aura
                end
            end
        end
    end

    -- Priority 4: Important buffs — disabled due to Blizzard API changes (HELPFUL|IMPORTANT filter removed)
    -- May come back in the future if the API is restored

    return nil
end



-- Update important aura for a specific arena unit

function ClassIconAuras:UpdateImportantAura(arenaIndex)

    if not IsImportantAurasEnabled() then
        self:ClearImportantAura(arenaIndex)
        return
    end

    

    local MFM = AC.MasterFrameManager

    local acFrame = MFM and MFM.frames and MFM.frames[arenaIndex]

    if not acFrame then return end

    

    local classIcon = acFrame.classIcon

    if not classIcon then return end

    

    local unitToken = "arena" .. arenaIndex

    -- Skip entirely if the unit doesn't exist yet (prevents garbage data scans).
    if not UnitExists(unitToken) then
        self:ClearImportantAura(arenaIndex)
        return
    end

    -- Validate the ccActive flag against live C_LossOfControl data on every pass.
    -- The SetTexture hook can miss the "CC ended" transition in Midnight when
    -- the question mark texture comes back as a secret value, leaving ccActive
    -- stuck as true even though the CC is long gone. Catching it here means
    -- UNIT_AURA (which always fires on removal) self-corrects the stale state.
    if classIcon.ccActive then
        local activeCount = C_LossOfControl.GetActiveLossOfControlDataCountByUnit(unitToken)
        if not activeCount or activeCount == 0 then
            classIcon.ccActive = false
            ClassIconAuras.ccActive[arenaIndex] = false
            if classIcon.auraCooldown then
                classIcon.auraCooldown:Clear()
            end
            -- Same as above: restore base texture before scanning so originalTexture
            -- is the real class icon, not the stale CC icon still sitting on the frame.
            RestoreClassIcon(arenaIndex, classIcon, acFrame)
        else
            self:ClearImportantAura(arenaIndex)
            return
        end
    end

    

    -- Scan for priority aura by category (CC > Important > Big Defensives > External)
    local foundImportant = false
    local spellID, texture, auraInstanceID, applications, auraData = ScanForPriorityAura(unitToken)

    if spellID then
        -- Midnight exposes secret number values on timing fields, so we pull a duration
        -- object instead - it travels through Blizzard's API without being tainted.
        local durationObj = nil
        local duration = 0
        local expirationTime = 0

        if AC:IsMidnight() and auraInstanceID then
            durationObj = C_UnitAuras.GetAuraDuration(unitToken, auraInstanceID)
            if durationObj then
                if classIcon.importantAuraCooldown then
                    classIcon.importantAuraCooldown:SetCooldownFromDurationObject(durationObj)
                    classIcon.importantAuraCooldown.durationObj = durationObj
                end
                local ok1, dur = pcall(durationObj.GetDuration, durationObj)
                local ok2, exp = pcall(durationObj.GetExpirationTime, durationObj)
                if ok1 and dur then
                    duration = dur
                    expirationTime = exp or 0
                end
                if AURA_DEBUG_ENABLED then
                    print("|cff00FF00[AuraDebug]|r duration object found for " .. (auraData and auraData.name or spellID))
                end
            else
                duration = auraData and auraData.duration or 0
                expirationTime = auraData and auraData.expirationTime or 0
                if AURA_DEBUG_ENABLED then
                    print("|cffFFAA00[AuraDebug]|r GetAuraDuration returned nil for " .. (auraData and auraData.name or spellID))
                end
            end
        else
            duration = auraData and auraData.duration or 0
            expirationTime = auraData and auraData.expirationTime or 0
        end

        -- Bundle everything the show function needs into one tidy package.
        local auraInfo = {
            spellId       = spellID,
            icon          = texture,
            applications  = applications or 0,
            auraInstanceID = auraInstanceID,
            duration      = duration,
            expirationTime = expirationTime,
        }

        if AC:IsMidnight() and durationObj then
            auraInfo.durationObj = durationObj
        end
        
        self:ShowImportantAura(arenaIndex, auraInfo)
        foundImportant = true
    end

    

    -- If no important aura found, clear it
    if not foundImportant then
        self:ClearImportantAura(arenaIndex)
    end

end



-- Show important aura on any classIcon frame (generic)
local function ShowImportantAuraOnFrame(classIcon, acFrame, auraInfo)
    if not classIcon then return end

    -- For raw Blizzard portraits (player, target, focus, etc.), use an overlay
    -- instead of overwriting the portrait texture. The portrait stays untouched
    -- underneath — like two sheets of paper stacked on top of each other.
    local isRawPortrait = classIcon.unit and not classIcon.UpdateClassIcon
    if isRawPortrait and classIcon.spellOverlay then
        classIcon.spellOverlay:SetTexture(auraInfo.icon)
        -- Crop the icon slightly so it fits inside the circular mask without showing corners
        classIcon.spellOverlay:SetTexCoord(0.1, 0.9, 0.1, 0.9)
        classIcon.spellOverlay:Show()
    else
        -- ArenaCore class icons: save original and swap the texture directly
        if not classIcon.originalTexture then
            classIcon.originalTexture = classIcon.icon and classIcon.icon:GetTexture()
        end
        if classIcon.icon and auraInfo.icon then
            classIcon.icon:SetTexture(auraInfo.icon)
            -- Crop the icon slightly so it fits inside the circular mask without showing corners
            classIcon.icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
        end
    end

    -- hide the border overlay when showing important aura because it looks cleaner that way
    if classIcon.overlay then
        classIcon.overlay:Hide()
    end

    -- Drive the cooldown swipe.  Midnight needs the duration object path to avoid
    -- touching secret number values; legacy builds use the plain SetCooldown call.
    if classIcon.importantAuraCooldown then
        if AC:IsMidnight() and auraInfo.durationObj then
            classIcon.importantAuraCooldown:SetCooldownFromDurationObject(auraInfo.durationObj)
            classIcon.importantAuraCooldown.durationObj = auraInfo.durationObj
        elseif auraInfo.expirationTime and auraInfo.duration then
            local durationGTZero
            if issecretvalue(auraInfo.duration) then
                durationGTZero = true
            else
                durationGTZero = auraInfo.duration > 0
            end

            if durationGTZero then
                if issecretvalue(auraInfo.duration) then
                    classIcon.importantAuraCooldown:Clear()
                    classIcon.importantAuraCooldown.durationObj = nil
                else
                    local startTime
                    if issecretvalue(auraInfo.expirationTime) then
                        startTime = 0
                    else
                        startTime = auraInfo.expirationTime - auraInfo.duration
                    end
                    classIcon.importantAuraCooldown:SetCooldown(startTime, auraInfo.duration)
                end
            else
                classIcon.importantAuraCooldown:Clear()
                classIcon.importantAuraCooldown.durationObj = nil
            end
        end

        if classIcon.importantAuraCooldown.SetHideCountdownNumbers then
            classIcon.importantAuraCooldown:SetHideCountdownNumbers(false)
        end
    end
end



-- Show important aura on class icon (arena wrapper, kept for compat)
function ClassIconAuras:ShowImportantAura(arenaIndex, auraInfo)

    return

end



-- Clear important aura from any classIcon frame (generic)
local function ClearImportantAuraFromFrame(classIcon, acFrame)
    if not classIcon then return end

    -- Clear cooldown (matches ShowImportantAura cleanup)
    if classIcon.importantAuraCooldown then
        classIcon.importantAuraCooldown:Clear()
        classIcon.importantAuraCooldown.durationObj = nil
    end

    -- For raw Blizzard portraits: just hide the spell overlay.
    -- The portrait underneath was never touched, so it shows through instantly.
    -- No need to "restore" anything — the cover just comes off.
    local isRawPortrait = classIcon.unit and not classIcon.UpdateClassIcon
    if isRawPortrait and classIcon.spellOverlay then
        classIcon.spellOverlay:SetTexture(nil)
        classIcon.spellOverlay:Hide()
    else
        -- Arena class icons: restore the swapped texture
        if classIcon.icon and classIcon.originalTexture then
            classIcon.icon:SetTexture(classIcon.originalTexture)
            -- Restore normal texture coordinates (full icon, not cropped)
            classIcon.icon:SetTexCoord(0, 1, 0, 1)
        elseif acFrame and classIcon.UpdateClassIcon then
            classIcon.UpdateClassIcon(acFrame.class)
        end
    end
    classIcon.originalTexture = nil

    -- Restore border to whatever style the user has selected
    if not classIcon.ccActive then
        if classIcon.ApplyBorderStyle then
            classIcon.ApplyBorderStyle()
        elseif classIcon.overlay then
            classIcon.overlay:Show()
        end
    end
end



-- Clear important aura from class icon (arena wrapper, kept for compat)
function ClassIconAuras:ClearImportantAura(arenaIndex)

    local MFM = AC.MasterFrameManager

    local acFrame = MFM and MFM.frames and MFM.frames[arenaIndex]

    if not acFrame then return end

    local classIcon = acFrame.classIcon

    if not classIcon then return end

    self.importantActive[arenaIndex] = false

    self.importantAuraData[arenaIndex] = nil

    ClearImportantAuraFromFrame(classIcon, acFrame)

end



-- Refresh all important auras
function ClassIconAuras:RefreshImportantAuras()

    if not IsImportantAurasEnabled() then
        -- Clear any registered non-arena frames (player, target, focus, nameplates)
        for unitToken, classIcon in pairs(self.unitToClassIcon) do
            local parent = classIcon._parentFrame
            ClearImportantAuraFromFrame(classIcon, parent)
        end
        return
    end

    if ShouldScanNonArenaPortraits() then
        -- Update any registered non-arena frames (nameplates, etc.)
        for unitToken, classIcon in pairs(self.unitToClassIcon) do
            if UnitExists(unitToken) then
                pcall(function()
                    self:UpdateImportantAuraForUnit(unitToken)
                end)
            else
                local parent = classIcon._parentFrame
                ClearImportantAuraFromFrame(classIcon, parent)
            end
        end

        -- Update player portrait spell overlay
        pcall(function()
            self:UpdateImportantAuraForUnit("player")
        end)

        -- Update target portrait spell overlay
        pcall(function()
            self:UpdateImportantAuraForUnit("target")
        end)

        -- Update focus portrait spell overlay
        pcall(function()
            self:UpdateImportantAuraForUnit("focus")
        end)

        -- Scan visible nameplates that have ArenaCore class icons
        for _, nameplate in ipairs(C_NamePlate.GetNamePlates()) do
            local unit = nameplate.unit or nameplate.namePlateUnitToken
            if unit and UnitExists(unit) then
                local classIcon, parent = FindClassIconForUnit(unit)
                if classIcon then
                    pcall(function()
                        self:UpdateImportantAuraForUnit(unit)
                    end)
                end
            end
        end
    else
        for unitToken, classIcon in pairs(self.unitToClassIcon) do
            local parent = classIcon._parentFrame
            ClearImportantAuraFromFrame(classIcon, parent)
        end

        ClearImportantAuraFromFrame(ClassIconAuras._playerWrapper, _G.PlayerFrame)
        ClearImportantAuraFromFrame(ClassIconAuras._targetWrapper, _G.TargetFrame)
        ClearImportantAuraFromFrame(ClassIconAuras._focusWrapper, _G.FocusFrame)
    end

end



function ClassIconAuras:Initialize()

    if self.initialized then return end

    local _, instanceType = IsInInstance()

    -- Blizzard debuff frame hooks only exist in arena; don't skip init outside arena
    -- because the portrait spell scanner works everywhere.
    if instanceType == "arena" then
        if not IsActiveBattlefieldArena or IsActiveBattlefieldArena() then
            for i = 1, MAX_ARENA_ENEMIES do
                self:HookDebuffFrame(i)
            end
        end
    end

    self.initialized = true

end



function ClassIconAuras:RefreshHooks()

    for i = 1, MAX_ARENA_ENEMIES do

        self:HookDebuffFrame(i)

    end

end



-- Generic update for any unit with a classIcon frame
function ClassIconAuras:UpdateImportantAuraForUnit(unitToken)
    if not unitToken then return end
    if not IsImportantAurasEnabled() or not ShouldScanNonArenaPortraits() then
        local classIcon, parent = FindClassIconForUnit(unitToken)
        if classIcon then
            ClearImportantAuraFromFrame(classIcon, parent)
        end
        return
    end

    local classIcon, parentFrame = FindClassIconForUnit(unitToken)
    if not classIcon then return end

    if not UnitExists(unitToken) then
        ClearImportantAuraFromFrame(classIcon, parentFrame)
        return
    end

    -- If this is an arena frame, let the original path handle ccActive validation
    -- (it has access to C_LossOfControl which only applies to arena units)
    local arenaIndex = parentFrame and parentFrame.unit and parentFrame.unit:match("arena(%d)")
    if arenaIndex then
        self:UpdateImportantAura(tonumber(arenaIndex))
        return
    end

    -- Non-arena path: simple priority scan
    local spellID, texture, auraInstanceID, applications, auraData = ScanForPriorityAura(unitToken)

    if spellID then
        local durationObj = nil
        local duration = 0
        local expirationTime = 0

        if AC:IsMidnight() and auraInstanceID then
            durationObj = C_UnitAuras.GetAuraDuration(unitToken, auraInstanceID)
            if durationObj then
                if classIcon.importantAuraCooldown then
                    classIcon.importantAuraCooldown:SetCooldownFromDurationObject(durationObj)
                    classIcon.importantAuraCooldown.durationObj = durationObj
                end
                local ok1, dur = pcall(durationObj.GetDuration, durationObj)
                local ok2, exp = pcall(durationObj.GetExpirationTime, durationObj)
                if ok1 and dur then
                    duration = dur
                    expirationTime = exp or 0
                end
            else
                duration = auraData and auraData.duration or 0
                expirationTime = auraData and auraData.expirationTime or 0
            end
        else
            duration = auraData and auraData.duration or 0
            expirationTime = auraData and auraData.expirationTime or 0
        end

        local auraInfo = {
            spellId        = spellID,
            icon           = texture,
            applications   = applications or 0,
            auraInstanceID = auraInstanceID,
            duration       = duration,
            expirationTime = expirationTime,
        }
        if AC:IsMidnight() and durationObj then
            auraInfo.durationObj = durationObj
        end

        ShowImportantAuraOnFrame(classIcon, parentFrame, auraInfo)
    else
        ClearImportantAuraFromFrame(classIcon, parentFrame)
    end
end



function ClassIconAuras:RestoreAll()

    local MFM = AC.MasterFrameManager

    if not MFM or not MFM.frames then return end



    local db = AC.DB and AC.DB.profile

    local showSpecInstead = db and db.classIcons and db.classIcons.showSpecInstead == true



    for i = 1, MAX_ARENA_ENEMIES do

        local acFrame = MFM.frames[i]

        if acFrame and acFrame.classIcon then

            local classIcon = acFrame.classIcon

            if classIcon.auraCooldown then

                classIcon.auraCooldown:Clear()

            end

            if classIcon.importantAuraCooldown then

                classIcon.importantAuraCooldown:Clear()

            end

            classIcon.ccActive = false

            self.ccActive[i] = false

            self.importantActive[i] = false

            self.importantAuraData[i] = nil



            if showSpecInstead then

                local specID = acFrame.specID

                if (not specID or specID <= 0) and GetArenaOpponentSpec then

                    specID = GetArenaOpponentSpec(i)

                end



                if specID and specID > 0 and GetSpecializationInfoByID then

                    acFrame.specID = specID

                    local _, _, _, specIcon = GetSpecializationInfoByID(specID)
                    acFrame.specIconTexture = specIcon

                    if specIcon and classIcon.icon and classIcon.icon.SetTexture then

                        classIcon.icon:SetTexture(specIcon)

                        if classIcon.icon.SetTexCoord then

                            classIcon.icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)

                        end

                    end

                    if classIcon.overlay and classIcon.overlay.SetTexture then

                        classIcon.overlay:SetTexture("Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Overlays\\orangeoverlay.tga")

                    end

                    classIcon:Show()

                else

                    classIcon:Hide()

                end



                if acFrame.specIcon then

                    acFrame.specIcon:Hide()

                end

            else

                local storedClass = acFrame.class

                if storedClass and classIcon.UpdateClassIcon then

                    classIcon.UpdateClassIcon(storedClass)

                end

            end

        end

    end

    -- Also clear any registered non-arena frames (nameplates, manual overrides)
    for unitToken, classIcon in pairs(self.unitToClassIcon) do
        local parent = classIcon._parentFrame
        ClearImportantAuraFromFrame(classIcon, parent)
    end

    -- Scan visible nameplates and clear their auras too
    for _, nameplate in ipairs(C_NamePlate.GetNamePlates()) do
        local unit = nameplate.unit or nameplate.namePlateUnitToken
        if unit then
            local classIcon, parent = FindClassIconForUnit(unit)
            if classIcon then
                ClearImportantAuraFromFrame(classIcon, parent)
            end
        end
    end

    -- Clear player portrait spell overlay
    local playerWrapper = ClassIconAuras._playerWrapper
    if playerWrapper then
        ClearImportantAuraFromFrame(playerWrapper, _G.PlayerFrame)
    end

end



-- EVENTS



local eventFrame = CreateFrame("Frame")

eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")

eventFrame:RegisterEvent("ARENA_PREP_OPPONENT_SPECIALIZATIONS")

eventFrame:RegisterEvent("ARENA_OPPONENT_UPDATE")

eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")

eventFrame:RegisterEvent("UNIT_AURA")

eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")

eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")

eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")

eventFrame:RegisterEvent("PLAYER_FOCUS_CHANGED")



eventFrame:SetScript("OnEvent", function(self, event, ...)

    local _, instanceType = IsInInstance()



    if event == "PLAYER_ENTERING_WORLD" then

        if instanceType == "arena" then

            C_Timer.After(0.5, function()

                if IsActiveBattlefieldArena and IsActiveBattlefieldArena() then

                    ClassIconAuras:RefreshHooks()

                    ClassIconAuras:RefreshImportantAuras()

                end

            end)

        else

            -- Outside arena: restore arena frames AND refresh any visible nameplates
            ClassIconAuras:RestoreAll()
            C_Timer.After(0.5, function()
                ClassIconAuras:RefreshImportantAuras()
            end)

        end

    elseif event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" then

        ClassIconAuras:RestoreAll()

        C_Timer.After(0.1, function()

            if IsActiveBattlefieldArena and IsActiveBattlefieldArena() then

                ClassIconAuras:RefreshHooks()

                ClassIconAuras:RefreshImportantAuras()

            end

        end)

    elseif event == "GROUP_ROSTER_UPDATE" then

        if instanceType == "arena" then
            ClassIconAuras:RestoreAll()
        else
            -- In BGs or open-world groups, refresh portrait spells on visible nameplates
            C_Timer.After(0.3, function()
                ClassIconAuras:RefreshImportantAuras()
            end)
        end

    elseif event == "ARENA_OPPONENT_UPDATE" then

        C_Timer.After(0.1, function()

            if IsActiveBattlefieldArena and IsActiveBattlefieldArena() then

                ClassIconAuras:RefreshHooks()

                ClassIconAuras:RefreshImportantAuras()

            end

        end)

    elseif event == "UNIT_AURA" then

        local unit, updateInfo = ...

        -- Skip if nothing actually changed (Midnight sends UNIT_AURA very frequently).
        if not AuraPayloadHasChanges(updateInfo) then return end

        if not unit then return end

        -- Arena units: use the original indexed path
        local arenaIndex = unit:match("^arena(%d)$")
        if arenaIndex then
            arenaIndex = tonumber(arenaIndex)
            if arenaIndex >= 1 and arenaIndex <= MAX_ARENA_ENEMIES then
                ClassIconAuras:UpdateImportantAura(arenaIndex)
            end
            return
        end

        -- Non-arena units: scan any nameplate/party/raid/target/focus with a classIcon
        local classIcon = FindClassIconForUnit(unit)
        if classIcon then
            ClassIconAuras:UpdateImportantAuraForUnit(unit)
        end

    elseif event == "NAME_PLATE_UNIT_ADDED" then

        local unitToken = ...
        if unitToken then
            C_Timer.After(0.1, function()
                ClassIconAuras:UpdateImportantAuraForUnit(unitToken)
            end)
        end

    elseif event == "NAME_PLATE_UNIT_REMOVED" then

        local unitToken = ...
        if unitToken then
            local classIcon, parent = FindClassIconForUnit(unitToken)
            if classIcon then
                ClearImportantAuraFromFrame(classIcon, parent)
            end
        end

    elseif event == "PLAYER_TARGET_CHANGED" then

        C_Timer.After(0.05, function()
            ClassIconAuras:UpdateImportantAuraForUnit("target")
        end)

    elseif event == "PLAYER_FOCUS_CHANGED" then

        C_Timer.After(0.05, function()
            ClassIconAuras:UpdateImportantAuraForUnit("focus")
        end)

    end

end)




C_Timer.After(0.5, function()

    if AC.Midnight and AC.Midnight.isMidnight then

        ClassIconAuras:Initialize()

        -- Refresh immediately if we're already in a group instance or open world
        -- so portrait spells show right away on visible nameplates.
        ClassIconAuras:RefreshImportantAuras()

    end

end)

