-- ============================================================================
-- File: ArenaCore/Core/PartyClassIndicators.lua (v2.0)
-- Purpose: Party class indicators
-- Rewritten Nov 1, 2025
-- ============================================================================

local AC = _G.ArenaCore
if not AC then return end

-- =========================================================================
-- TARGET GLOW (Orange) - only shows on current target
-- =========================================================================

local TARGET_GLOW_COLOR = { 1.0, 0.4, 0.0, 1.0 }

local function EnsureTargetGlow(classFrame)
    -- Old filled-circle glow removed — target glow now pulses the existing ring
    if not classFrame or classFrame.__acTargetGlowReady then return end
    classFrame.__acTargetGlowReady = true
end

-- =========================================================================
-- HEALER GLOW (Persistent) - shows on friendly healers when enabled
-- =========================================================================

local HEALER_GLOW_COLOR = { 0.35, 0.85, 1.0, 1.0 }

local function EnsureHealerGlow(classFrame)
    if not classFrame or classFrame.__acHealerGlowReady then return end

    classFrame.__acHealerGlowReady = true

    if not classFrame.ArenaCore_HealerGlowFrame then
        classFrame.ArenaCore_HealerGlowFrame = CreateFrame("Frame", nil, classFrame)
        local strata = (classFrame.GetFrameStrata and classFrame:GetFrameStrata()) or "MEDIUM"
        local level = (classFrame.GetFrameLevel and classFrame:GetFrameLevel()) or 1
        classFrame.ArenaCore_HealerGlowFrame:SetFrameStrata(strata)
        classFrame.ArenaCore_HealerGlowFrame:SetFrameLevel(level + 1)
    end

    if not classFrame.ArenaCore_HealerGlow then
        local glow = classFrame.ArenaCore_HealerGlowFrame:CreateTexture(nil, "ARTWORK")
        glow:SetAtlas("newplayertutorial-drag-slotgreen")
        glow:SetDesaturated(true)
        glow:Hide()

        classFrame.ArenaCore_HealerGlow = glow
    end

    if not classFrame.ArenaCore_HealerGlowMask then
        local mask = classFrame.ArenaCore_HealerGlowFrame:CreateMaskTexture()
        mask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
        mask:SetPoint("CENTER", classFrame, "CENTER", 0, 0)
        mask:SetSize(58, 58)
        classFrame.ArenaCore_HealerGlowMask = mask
    end

    if classFrame.ArenaCore_HealerGlow and classFrame.ArenaCore_HealerGlowMask then
        classFrame.ArenaCore_HealerGlow:AddMaskTexture(classFrame.ArenaCore_HealerGlowMask)
    end
end

local function ApplyHealerGlow(classFrame, shouldShow)
    if not classFrame then return end
    EnsureHealerGlow(classFrame)

    local glow = classFrame.ArenaCore_HealerGlow
    if not glow then return end

    if shouldShow then
        glow:ClearAllPoints()
        glow:SetPoint("TOPLEFT", classFrame, "TOPLEFT", -12, 12)
        glow:SetPoint("BOTTOMRIGHT", classFrame, "BOTTOMRIGHT", 12, -12)
        glow:SetVertexColor(HEALER_GLOW_COLOR[1], HEALER_GLOW_COLOR[2], HEALER_GLOW_COLOR[3], HEALER_GLOW_COLOR[4])
        glow:Show()
    else
        glow:Hide()
    end
end

local function ApplyTargetGlow(classFrame, shouldShow)
    if not classFrame then return end
    EnsureTargetGlow(classFrame)

    -- Hide old filled-circle glow if it still exists
    if classFrame.ArenaCore_TargetGlow then
        classFrame.ArenaCore_TargetGlow:Hide()
    end

    -- Respect the "Turn OFF Targetting Glow" setting on the Class Icons page
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    if db and db.disableTargetGlow then
        shouldShow = false
    end

    -- Setup pulse animation on classFrame if not already
    if not classFrame.__acPulseSetup then
        classFrame.__acPulseSetup = true
        classFrame:SetScript("OnUpdate", function(self, elapsed)
            if not self.__acPulseActive then return end
            self.__acPulseElapsed = (self.__acPulseElapsed or 0) + elapsed
            local t = self.__acPulseElapsed * 1.8
            -- Layered neon pulse: tight bright edge, mid bloom, outer gas cloud
            if self.glowTight and self.glowTight.SetAlpha then
                self.glowTight:SetAlpha(0.85 + 0.15 * math.sin(t))
            end
            if self.glowBloom and self.glowBloom.SetAlpha then
                self.glowBloom:SetAlpha(0.50 + 0.15 * math.sin(t + 0.7))
            end
            if self.glowHalo and self.glowHalo.SetAlpha then
                self.glowHalo:SetAlpha(0.28 + 0.10 * math.sin(t + 1.4))
            end
            if self.__acPulseTarget and self.__acPulseTarget.SetAlpha then
                self.__acPulseTarget:SetAlpha(0.85 + 0.15 * math.sin(t))
            end
        end)
    end

    local db = AC.DB.profile.moreGoodies.partyClassSpecs or {}
    local useClassColorBorders = db.useClassColorBorders

    if shouldShow then
        classFrame.__acPulseActive = true
        classFrame.__acPulseElapsed = 0

        local class = classFrame.class
        local r, g, b
        if useClassColorBorders and class and RAID_CLASS_COLORS[class] then
            classFrame.__acPulseTarget = classFrame.colorRing
            local classColor = RAID_CLASS_COLORS[class]
            r, g, b = classColor.r, classColor.g, classColor.b
            if class == "MONK" then
                r, g, b = 0.0, 0.83, 0.45
            end
        else
            classFrame.__acPulseTarget = classFrame.border
            r, g, b = 1.0, 0.4, 0.0
        end

        classFrame.glowTight:SetVertexColor(r, g, b, 1)
        classFrame.glowBloom:SetVertexColor(r, g, b, 1)
        classFrame.glowHalo:SetVertexColor(r, g, b, 1)
        classFrame.glowTight:Show()
        classFrame.glowBloom:Show()
        classFrame.glowHalo:Show()
    else
        classFrame.__acPulseActive = false
        if classFrame.__acPulseTarget then
            classFrame.__acPulseTarget:SetAlpha(1)
            classFrame.__acPulseTarget = nil
        end
        if classFrame.colorRing then classFrame.colorRing:SetAlpha(1) end
        if classFrame.border then classFrame.border:SetAlpha(1) end
        if classFrame.glowTight then classFrame.glowTight:Hide() end
        if classFrame.glowBloom then classFrame.glowBloom:Hide() end
        if classFrame.glowHalo then classFrame.glowHalo:Hide() end
    end
end

-- Class icon paths (ArenaCore Custom theme)
local CLASS_ICON_PATHS = {
    DEATHKNIGHT = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Deathknight",
    DEMONHUNTER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Demonhunter",
    DRUID = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Druid",
    EVOKER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Evoker",
    HUNTER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Hunter",
    MAGE = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Mage",
    MONK = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Monk",
    PALADIN = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Paladin",
    PRIEST = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Priest",
    ROGUE = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Rogue",
    SHAMAN = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Shaman",
    WARLOCK = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Warlock",
    WARRIOR = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\Warrior",
}

-- ColdClasses theme paths (WoW Default Style+Midnight Chill)
-- CRITICAL: Must include .png extension for PNG files
local COLDCLASSES_ICON_PATHS = {
    DEATHKNIGHT = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\deathknight.png",
    DEMONHUNTER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\demonhunter.png",
    DRUID = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\druid.png",
    EVOKER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\evoker.png",
    HUNTER = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\hunter.png",
    MAGE = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\mage.png",
    MONK = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\monk.png",
    PALADIN = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\paladin.png",
    PRIEST = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\priest.png",
    ROGUE = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\rogue.png",
    SHAMAN = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\shaman.png",
    WARLOCK = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\warlock.png",
    WARRIOR = "Interface\\AddOns\\ArenaCore\\Media\\Classicons\\ColdClasses\\warrior.png",
}

-- Get class icon path based on useCustomIcons setting
local function GetClassIconPath(classToken)
    if not classToken then return nil end
    
    -- Get settings from database
    local db = AC.DB and AC.DB.profile
    local useCustomIcons = db and db.moreGoodies and db.moreGoodies.partyClassSpecs and db.moreGoodies.partyClassSpecs.useCustomIcons
    
    -- When checkbox is ON: Use ArenaCore custom icons
    -- When checkbox is OFF: Use Midnight Chill (coldclasses) icons
    if useCustomIcons then
        -- Use ArenaCore Custom icons
        return CLASS_ICON_PATHS[classToken]
    else
        -- Use Midnight Chill (coldclasses) icons
        return COLDCLASSES_ICON_PATHS[classToken]
    end
end

-- ============================================================================
-- CLASS DATA CACHE (Solo Shuffle prep room fix)
-- ============================================================================
-- Cache class data BY PLAYER NAME to handle solo shuffle prep room
-- Unit tokens change between rounds, but player names persist
local classCache = {}

-- Cache class data for a player when available
local function CacheClassData(playerName, class)
    if playerName and class then
        local ok = pcall(function()
            classCache[playerName] = class
        end)
        if not ok then
            return
        end
    end
end

-- Get cached class data or try to get fresh data
local function GetCachedClass(unit)
    if not unit then return nil end
    
    -- Get player name for cache lookup (works even when UnitClass fails)
    local playerName = UnitName(unit)
    if playerName and issecretvalue(playerName) then playerName = nil end

    -- First try fresh data
    local _, class = UnitClass(unit)
    if class then
        -- Cache the fresh data by player name
        if playerName then
            CacheClassData(playerName, class)
        end
        return class
    end
    
    -- Fall back to cached data by player name (solo shuffle prep room case)
    if playerName then
        local ok, cached = pcall(function()
            return classCache[playerName]
        end)
        if ok then
            return cached
        end
    end
    
    return nil
end

-- Clear all class cache (when leaving arena entirely)
local function ClearAllClassCache()
    table.wipe(classCache)
end

-- ============================================================================
-- SPEC ICON CACHE (for Use Spec Icons Instead feature)
-- ============================================================================
-- GetInspectSpecialization only works after NotifyInspect + INSPECT_READY.
-- We pre-cache all party members proactively on GROUP_ROSTER_UPDATE so spec
-- icons are ready before nameplates even appear.
local specIconCache = {}
local specIconRequested = {}  -- throttle per player
local specIconFrame = CreateFrame("Frame")

-- Forward declare so the event closure captures this local, not a nil global
local PreCacheGroupSpecs

-- INSPECT_READY: cache spec icon when inspect data arrives
-- GROUP_ROSTER_UPDATE: pre-cache all party members proactively
specIconFrame:RegisterEvent("INSPECT_READY")
specIconFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
specIconFrame:SetScript("OnEvent", function(self, event, unit)
    if event == "INSPECT_READY" then
        if not unit then return end
        local playerName = UnitName(unit)
        if not playerName then return end
        -- Midnight: secret strings can't be used as table keys
        if issecretvalue(playerName) then return end

        local specID = GetInspectSpecialization(unit)
        if specID and specID > 0 and GetSpecializationInfoByID then
            local _, _, _, specIcon = GetSpecializationInfoByID(specID)
            if specIcon then
                specIconCache[playerName] = specIcon
                if AC.RefreshPartyClassIcons then
                    AC:RefreshPartyClassIcons()
                end
            end
        end
    elseif event == "GROUP_ROSTER_UPDATE" then
        C_Timer.After(0.5, PreCacheGroupSpecs)
    end
end)

-- Find a party/raid unit token by player name
local function FindUnitTokenByName(playerName)
    if not playerName then return nil end
    if issecretvalue(playerName) then return nil end

    for i = 1, 4 do
        local unit = "party" .. i
        if UnitExists(unit) then
            local ok, isMatch = pcall(function() return UnitName(unit) == playerName end)
            if ok and isMatch then
                return unit
            end
        end
    end

    for i = 1, 40 do
        local unit = "raid" .. i
        if UnitExists(unit) then
            local ok, isMatch = pcall(function() return UnitName(unit) == playerName end)
            if ok and isMatch then
                return unit
            end
        end
    end

    return nil
end

-- Try to read spec data from a unit without NotifyInspect (works if already cached)
local function TryReadSpecDirect(unit, playerName)
    if not unit or not playerName then return end
    if issecretvalue(playerName) then return end
    if specIconCache[playerName] then return end

    local specID = GetInspectSpecialization and GetInspectSpecialization(unit)
    if specID and specID > 0 and GetSpecializationInfoByID then
        local _, _, _, specIcon = GetSpecializationInfoByID(specID)
        if specIcon then
            specIconCache[playerName] = specIcon
        end
    end
end

-- Request spec data for a single unit (throttled per player, queued globally)
local inspectQueue = {}
local inspectQueueProcessing = false

local function ProcessInspectQueue()
    if #inspectQueue == 0 then
        inspectQueueProcessing = false
        return
    end

    local entry = table.remove(inspectQueue, 1)
    if not entry then
        inspectQueueProcessing = false
        return
    end

    -- Player may have logged off or left range, skip if unit gone
    if not UnitExists(entry.unit) then
        C_Timer.After(0.1, ProcessInspectQueue)
        return
    end

    -- Still cached from a previous response? skip
    if entry.playerName and not issecretvalue(entry.playerName) and specIconCache[entry.playerName] then
        C_Timer.After(0.1, ProcessInspectQueue)
        return
    end

    pcall(function() NotifyInspect(entry.unit) end)

    -- Wait 1s before next request so the server doesn't drop it
    C_Timer.After(1.0, ProcessInspectQueue)
end

local function EnqueueInspect(unit, playerName)
    if not unit or not playerName then return end
    if issecretvalue(playerName) then return end
    if specIconCache[playerName] then return end

    -- Throttle: 5 seconds per player before re-queuing
    local now = GetTime()
    if specIconRequested[playerName] and (now - specIconRequested[playerName]) < 5 then
        return
    end
    specIconRequested[playerName] = now

    -- Avoid duplicate queue entries for same player
    for i = 1, #inspectQueue do
        if inspectQueue[i].playerName and not issecretvalue(inspectQueue[i].playerName)
           and playerName and not issecretvalue(playerName)
           and inspectQueue[i].playerName == playerName then
            return
        end
    end

    table.insert(inspectQueue, { unit = unit, playerName = playerName })

    if not inspectQueueProcessing then
        inspectQueueProcessing = true
        C_Timer.After(0.1, ProcessInspectQueue)
    end
end

-- Pre-cache all current party/raid members via the inspect queue
function PreCacheGroupSpecs()
    local db = AC.DB and AC.DB.profile
    local useSpecIcons = db and db.moreGoodies and db.moreGoodies.partyClassSpecs and db.moreGoodies.partyClassSpecs.useSpecIcons
    if not useSpecIcons then return end

    for i = 1, 4 do
        local unit = "party" .. i
        if UnitExists(unit) and UnitIsPlayer(unit) and UnitIsFriend("player", unit) then
            local playerName = UnitName(unit)
            if playerName and not issecretvalue(playerName) and not specIconCache[playerName] then
                -- Try direct read first, queue if not available
                TryReadSpecDirect(unit, playerName)
                if not specIconCache[playerName] then
                    EnqueueInspect(unit, playerName)
                end
            end
        end
    end

    for i = 1, 40 do
        local unit = "raid" .. i
        if UnitExists(unit) and UnitIsPlayer(unit) and UnitIsFriend("player", unit) then
            local playerName = UnitName(unit)
            if playerName and not issecretvalue(playerName) and not specIconCache[playerName] then
                TryReadSpecDirect(unit, playerName)
                if not specIconCache[playerName] then
                    EnqueueInspect(unit, playerName)
                end
            end
        end
    end
end

-- Get cached spec icon for a player name
local function GetCachedSpecIcon(playerName)
    if not playerName then return nil end
    -- Midnight: playerName can be a secret string, can't index tables with it
    if issecretvalue(playerName) then return nil end
    return specIconCache[playerName]
end

-- ============================================================================
-- INDEPENDENT FRAME STORAGE (for custom nameplate addon compatibility)
-- ============================================================================
-- Store frames independently so they work with Platynator/Plater
-- These addons reparent Blizzard frames to hidden frames, so we need our own
local independentFrames = {}

-- Expose the independentFrames table for cleanup by other modules
AC.PartyClassIndicators = AC.PartyClassIndicators or {}
AC.PartyClassIndicators.independentFrames = independentFrames
AC.PartyClassIndicators.PreCacheGroupSpecs = PreCacheGroupSpecs

-- Throttled OnUpdate — no need to reposition every single frame render
local POSITION_UPDATE_INTERVAL = 0.05 -- ~20 updates/sec is plenty smooth, was running 60+
local cachedDB = nil
local cachedDBTime = 0

local function GetPartyClassDB()
    -- Cache the DB lookup for 1 second so we dont deep-traverse every OnUpdate
    local now = GetTime()
    if now - cachedDBTime > 1 then
        cachedDB = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
        cachedDBTime = now
    end
    return cachedDB
end

local function IsInPvPInstance()
    local _, instanceType = IsInInstance()
    return instanceType == "arena" or instanceType == "pvp"
end

local function UpdateFramePosition(self, elapsed)
    if not self.unit then return end

    -- Throttle: accumulate elapsed time, skip frames until interval reached
    self._elapsed = (self._elapsed or 0) + elapsed
    if self._elapsed < POSITION_UPDATE_INTERVAL then return end
    self._elapsed = 0

    local db = GetPartyClassDB()
    if not db or not db.mode or db.mode == "off" or (db.mode == "arena" and not IsInPvPInstance()) then
        self._anchoredNameplate = nil
        self._anchoredOffsetX = nil
        self._anchoredOffsetY = nil
        if self:IsShown() then
            self:Hide()
        end
        return
    end

    local nameplate = C_NamePlate.GetNamePlateForUnit(self.unit)
    if nameplate and nameplate:IsShown() then
        -- Guard against nameplate token recycling: WoW reuses unit tokens like
        -- "nameplate1" for different actors. If this token now belongs to an
        -- enemy, hide the frame so our friendly healer icon never appears over
        -- an enemy nameplate (conflicts with enemy healer indicator)
        if not UnitIsPlayer(self.unit) or not UnitIsFriend("player", self.unit) then
            self._anchoredNameplate = nil
            self._anchoredOffsetX = nil
            self._anchoredOffsetY = nil
            if self:IsShown() then
                self:Hide()
            end
            return
        end

        local offsetX = self.offsetX or 0
        local offsetY = self.offsetY or 0

        if self._anchoredNameplate ~= nameplate or self._anchoredOffsetX ~= offsetX or self._anchoredOffsetY ~= offsetY then
            self:ClearAllPoints()
            self:SetPoint("BOTTOM", nameplate, "TOP", offsetX, offsetY)
            self._anchoredNameplate = nameplate
            self._anchoredOffsetX = offsetX
            self._anchoredOffsetY = offsetY
        end

        if not self:IsShown() then
            self:Show()
        end
    else
        self._anchoredNameplate = nil
        self._anchoredOffsetX = nil
        self._anchoredOffsetY = nil
        if self:IsShown() then
            self:Hide()
        end
    end
end

-- ============================================================================
-- Nameplate lifecycle: single function called from NAME_PLATE_UNIT_ADDED
-- ============================================================================

function AC.UpdatePartyClassIndicator(frame)
    if not frame or not frame.unit then return end
    
    -- Don't check IsForbidden/IsProtected - we're using a dummy table now, not a real frame
    
    -- Get ArenaCore config
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    if not db or not db.mode or db.mode == "off" or (db.mode == "arena" and not IsInPvPInstance()) then
        -- Feature disabled - restore any hidden elements
        if frame.__acClassIndicatorActive then
            if frame.HealthBarsContainer then
                frame.HealthBarsContainer:SetAlpha(1)
            end
            if frame.name then
                frame.name:SetAlpha(1)
            end
            frame.__acClassIndicatorActive = nil
        end
        if frame.ArenaCore_ClassIndicator then
            frame.ArenaCore_ClassIndicator:Hide()
        end
        if frame.ArenaCore_Pointer then
            frame.ArenaCore_Pointer:Hide()
        end
        -- Restore raid target markers when feature is off
        if frame.RaidTargetFrame and frame.RaidTargetFrame.RaidTargetIcon then
            frame.RaidTargetFrame.RaidTargetIcon:SetAlpha(1)
        end
        return
    end
    
    -- Get unit info first
    local unitForData = frame.displayedUnit or frame.unit
    if not unitForData or not UnitExists(unitForData) then return end
    
    -- Only handle nameplates
    -- CRITICAL: For custom nameplate addons, frame.unit may not contain "nameplate"
    -- Check if the unit itself is a nameplate unit
    if not unitForData:find("nameplate") then return end
    
    -- CRITICAL: Only work on FRIENDLY PLAYERS, not NPCs
    if not UnitIsPlayer(unitForData) or not UnitIsFriend("player", unitForData) then
        -- NPC or enemy - don't touch anything
        if frame.__acClassIndicatorActive then
            if frame.HealthBarsContainer then
                frame.HealthBarsContainer:SetAlpha(1)
            end
            frame.__acClassIndicatorActive = nil
        end
        if frame.ArenaCore_ClassIndicator then
            frame.ArenaCore_ClassIndicator:Hide()
        end
        if frame.ArenaCore_Pointer then
            frame.ArenaCore_Pointer:Hide()
        end
        -- Restore raid target markers for NPCs/enemies
        if frame.RaidTargetFrame and frame.RaidTargetFrame.RaidTargetIcon then
            frame.RaidTargetFrame.RaidTargetIcon:SetAlpha(1)
        end
        return
    end
    
    -- Check mode setting (party vs all vs arena)
    local showOnThisUnit = false
    if db.mode == "all" then
        showOnThisUnit = true
    elseif db.mode == "party" then
        showOnThisUnit = UnitInParty(unitForData)
    elseif db.mode == "arena" then
        showOnThisUnit = UnitInParty(unitForData) and IsInPvPInstance()
    end
    
    -- If mode doesn't match, hide and return
    if not showOnThisUnit then
        -- Mode doesn't match - hide everything
        if frame.__acClassIndicatorActive then
            if frame.HealthBarsContainer then
                frame.HealthBarsContainer:SetAlpha(1)
            end
            frame.__acClassIndicatorActive = nil
        end
        if frame.ArenaCore_ClassIndicator then
            frame.ArenaCore_ClassIndicator:Hide()
        end
        if frame.ArenaCore_Pointer then
            frame.ArenaCore_Pointer:Hide()
        end
        -- Restore raid target markers for enemies/neutrals
        if frame.RaidTargetFrame and frame.RaidTargetFrame.RaidTargetIcon then
            frame.RaidTargetFrame.RaidTargetIcon:SetAlpha(1)
        end
        return
    end
    
    -- Don't show on self
    if UnitIsUnit(unitForData, "player") then
        if frame.ArenaCore_ClassIndicator then
            frame.ArenaCore_ClassIndicator:Hide()
        end
        if frame.ArenaCore_Pointer then
            frame.ArenaCore_Pointer:Hide()
        end
        -- Restore raid target markers for self
        if frame.RaidTargetFrame and frame.RaidTargetFrame.RaidTargetIcon then
            frame.RaidTargetFrame.RaidTargetIcon:SetAlpha(1)
        end
        return
    end
    
    -- Get class info using cache system (fixes solo shuffle prep room)
    local class = GetCachedClass(unitForData)
    if not class then return end
    
    -- ========================================================================
    -- CLASS INDICATOR (Icon above nameplate)
    -- ========================================================================
    
    -- Always show class icons when feature is enabled (mode != "off")
    if true then
        -- Get or create independent frame (not a child of nameplate)
        local classFrame = independentFrames[unitForData]
        if not classFrame then
            -- Create frame attached to UIParent (independent of nameplate)
            classFrame = CreateFrame("Frame", "ArenaCore_ClassIndicator_" .. unitForData, UIParent)
            classFrame:SetSize(32, 32)
            classFrame:SetFrameStrata("MEDIUM")
            classFrame:SetFrameLevel(10)
            independentFrames[unitForData] = classFrame
            
            -- Store unit for OnUpdate positioning
            classFrame.unit = unitForData
            
            -- Set up continuous positioning
            classFrame:SetScript("OnUpdate", UpdateFramePosition)
            
            -- Store reference on frame for compatibility
            frame.ArenaCore_ClassIndicator = classFrame
            
            -- Create the class icon texture
            classFrame.icon = classFrame:CreateTexture(nil, "ARTWORK")
            classFrame.icon:SetPoint("CENTER", classFrame)
            classFrame.icon:SetSize(28, 28)
            
            -- Create circular mask
            classFrame.mask = classFrame:CreateMaskTexture()
            classFrame.mask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
            classFrame.mask:SetSize(28, 28)
            classFrame.mask:SetPoint("CENTER", classFrame.icon)
            classFrame.icon:AddMaskTexture(classFrame.mask)
            
            -- Create border using BlackOutline.tga
            classFrame.border = classFrame:CreateTexture(nil, "OVERLAY")
            classFrame.border:SetAllPoints(classFrame)
            classFrame.border:SetTexture("Interface\\AddOns\\ArenaCore\\Media\\Classicons\\BlackOutline.tga")

            -- Create class-colored ring backdrop (white texture + circular mask)
            classFrame.colorRing = classFrame:CreateTexture(nil, "BACKGROUND")
            classFrame.colorRing:SetPoint("CENTER", classFrame)
            classFrame.colorRing:SetSize(34, 34)
            classFrame.colorRing:SetTexture("Interface\\BUTTONS\\WHITE8X8")
            classFrame.colorRing:Hide()

            local ringMask = classFrame:CreateMaskTexture()
            ringMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
            ringMask:SetSize(34, 34)
            ringMask:SetPoint("CENTER", classFrame.colorRing)
            classFrame.colorRing:AddMaskTexture(ringMask)

            -- Layer 1: Bright inner core — radial atlas fades on its own, no mask needed
            classFrame.glowTight = classFrame:CreateTexture(nil, "BACKGROUND", nil, 0)
            classFrame.glowTight:SetPoint("CENTER", classFrame)
            classFrame.glowTight:SetSize(54, 54)
            classFrame.glowTight:SetAtlas("OBJFX_Glow")
            classFrame.glowTight:SetDesaturated(true)
            classFrame.glowTight:SetBlendMode("ADD")
            classFrame.glowTight:Hide()

            -- Layer 2: Mid bloom — same soft blob, bigger and dimmer
            classFrame.glowBloom = classFrame:CreateTexture(nil, "BACKGROUND", nil, -1)
            classFrame.glowBloom:SetPoint("CENTER", classFrame)
            classFrame.glowBloom:SetSize(80, 80)
            classFrame.glowBloom:SetAtlas("OBJFX_Glow")
            classFrame.glowBloom:SetDesaturated(true)
            classFrame.glowBloom:SetBlendMode("ADD")
            classFrame.glowBloom:Hide()

            -- Layer 3: Outer gas cloud — the widest, faintest haze
            classFrame.glowHalo = classFrame:CreateTexture(nil, "BACKGROUND", nil, -2)
            classFrame.glowHalo:SetPoint("CENTER", classFrame)
            classFrame.glowHalo:SetSize(112, 112)
            classFrame.glowHalo:SetAtlas("OBJFX_Glow")
            classFrame.glowHalo:SetDesaturated(true)
            classFrame.glowHalo:SetBlendMode("ADD")
            classFrame.glowHalo:Hide()

            -- Create inner black circle for the border edge
            classFrame.innerBlack = classFrame:CreateTexture(nil, "BACKGROUND")
            classFrame.innerBlack:SetPoint("CENTER", classFrame)
            classFrame.innerBlack:SetSize(30, 30)
            classFrame.innerBlack:SetTexture("Interface\\BUTTONS\\WHITE8X8")
            classFrame.innerBlack:SetVertexColor(0.08, 0.08, 0.08, 1)
            classFrame.innerBlack:Hide()

            local innerMask = classFrame:CreateMaskTexture()
            innerMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
            innerMask:SetSize(30, 30)
            innerMask:SetPoint("CENTER", classFrame.innerBlack)
            classFrame.innerBlack:AddMaskTexture(innerMask)
        end

        -- Store class on frame for target glow to reference
        classFrame.class = class

        -- Apply class color border if useClassColorBorders is enabled
        local useClassColorBorders = db.useClassColorBorders
        if useClassColorBorders and class and RAID_CLASS_COLORS[class] then
            -- Use clean in-game circular backdrop colored with class color
            local classColor = RAID_CLASS_COLORS[class]
            local r, g, b = classColor.r, classColor.g, classColor.b
            if class == "MONK" then
                -- Deeper jade green — default mint is too bright/cyan for thin borders
                r, g, b = 0.0, 0.83, 0.45
            end
            classFrame.colorRing:SetVertexColor(r, g, b, 1)
            classFrame.glowTight:SetVertexColor(r, g, b, 1)
            classFrame.glowBloom:SetVertexColor(r, g, b, 1)
            classFrame.glowHalo:SetVertexColor(r, g, b, 1)
            classFrame.colorRing:Show()
            classFrame.innerBlack:Show()
            classFrame.border:Hide() -- Hide BlackOutline (orange) border
        else
            -- Default: show BlackOutline orange border, hide class color ring
            classFrame.border:Show()
            if classFrame.colorRing then
                classFrame.colorRing:Hide()
            end
            if classFrame.innerBlack then
                classFrame.innerBlack:Hide()
            end
            if classFrame.glowTight then classFrame.glowTight:Hide() end
            if classFrame.glowBloom then classFrame.glowBloom:Hide() end
            if classFrame.glowHalo then classFrame.glowHalo:Hide() end
        end
        
        -- Position frame above nameplate using world coordinates
        local nameplateFrame = C_NamePlate.GetNamePlateForUnit(unitForData)
        if nameplateFrame then
            classFrame._anchoredNameplate = nil
            classFrame._anchoredOffsetX = nil
            classFrame._anchoredOffsetY = nil
        end
        
        -- Apply scale from settings
        local finalScale
        do
            local pm = AC.ProfileManager
            local scalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.scale")
            if type(scalePixels) == "number" then
                local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(scalePixels, 50, 360, 1, 12) or 6.5
                finalScale = 0.5 + (currentScale - 1) * (2.5 / 11)
            else
                local rawScale = (db.scale) or 100
                if rawScale <= 12 then
                    finalScale = 0.5 + (rawScale - 1) * (2.5 / 11)
                else
                    finalScale = rawScale / 100
                end
            end
            finalScale = math.max(0.5, math.min(finalScale or 1.0, 3.0))
            classFrame:SetScale(finalScale)
        end
        
        -- Apply horizontal/vertical offsets
        local offX, offY = 0, 0
        do
            local pm = AC.ProfileManager
            local px = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetX")
            local py = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetY")
            if type(px) ~= "number" then
                px = db.offsetX or 0
            end
            if type(py) ~= "number" then
                py = db.offsetY or 0
            end
            offX, offY = tonumber(px) or 0, tonumber(py) or 0
        end
        
        local baseY = 8
        -- Store offsets for OnUpdate handler
        classFrame.offsetX = offX
        classFrame.offsetY = baseY + offY
        UpdateFramePosition(classFrame, POSITION_UPDATE_INTERVAL)
        
        -- Set class icon texture (use custom ArenaCore icons)
        -- CRITICAL FIX: Check if this is a healer and showHealerIcon is enabled
        local useHealerIcon = false
        if db.showHealerIcon then
            -- Check if this unit is a healer using role assignment
            local role = UnitGroupRolesAssigned(unitForData)
            if role == "HEALER" then
                useHealerIcon = true
            end
        end
        
        if useHealerIcon then
            -- Use custom ArenaCore healer icon
            classFrame.icon:SetTexture("Interface\\AddOns\\ArenaCore\\Media\\Classicons\\HealerPointer.tga")
            classFrame.icon:SetTexCoord(0, 1, 0, 1) -- Full texture for healer icon
        elseif db.useSpecIcons then
            -- Use in-game default spec icons dynamically
            local playerName = UnitName(unitForData)
            if playerName and issecretvalue(playerName) then playerName = nil end
            local specIcon = GetCachedSpecIcon(playerName)

            if not specIcon then
                -- Try direct read first (instant if client already has data)
                TryReadSpecDirect(unitForData, playerName)
                specIcon = GetCachedSpecIcon(playerName)
            end

            if not specIcon then
                -- Queue an inspect request (processed one at a time)
                local inspectUnit = unitForData
                if unitForData:find("nameplate") then
                    local partyUnit = FindUnitTokenByName(playerName)
                    if partyUnit then
                        inspectUnit = partyUnit
                        TryReadSpecDirect(inspectUnit, playerName)
                        specIcon = GetCachedSpecIcon(playerName)
                    end
                end
                if not specIcon then
                    EnqueueInspect(inspectUnit, playerName)
                end
            end

            if specIcon then
                classFrame.icon:SetTexture(specIcon)
                classFrame.icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
            else
                -- No spec data yet, fall back to class icon
                local iconPath = GetClassIconPath(class)
                if iconPath then
                    classFrame.icon:SetTexture(iconPath)
                    classFrame.icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
                end
            end
        else
            -- Use normal class icon (respects theme setting)
            local iconPath = GetClassIconPath(class)
            if iconPath then
                classFrame.icon:SetTexture(iconPath)
                classFrame.icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
            end
        end
        
        -- Hide raid target markers (skull, cross, moon, etc.) when showing class icons
        if frame.RaidTargetFrame and frame.RaidTargetFrame.RaidTargetIcon then
            frame.RaidTargetFrame.RaidTargetIcon:SetAlpha(0)
        end
        
        classFrame:Show()

        -- Persistent healer glow (different color than target)
        ApplyHealerGlow(classFrame, useHealerIcon)

        -- Target-only orange glow
        ApplyTargetGlow(classFrame, UnitExists("target") and UnitIsUnit(unitForData, "target"))
    else
        local classFrame = independentFrames[unitForData]
        if classFrame then
            ApplyHealerGlow(classFrame, false)
            ApplyTargetGlow(classFrame, false)
            classFrame:Hide()
        end
    end
    
    -- ========================================================================
    -- PARTY POINTER (Arrow above nameplate)
    -- ========================================================================
    
    -- Update pointer (triangle arrow) with separate positioning
    if db.showPointers then
        -- Get or create independent pointer frame
        local pointerFrame = independentFrames[unitForData .. "_pointer"]
        if not pointerFrame then
            -- Create frame attached to UIParent (independent of nameplate)
            pointerFrame = CreateFrame("Frame", "ArenaCore_Pointer_" .. unitForData, UIParent)
            pointerFrame:SetSize(34, 48)
            pointerFrame:SetFrameStrata("MEDIUM")
            pointerFrame:SetFrameLevel(11)
            independentFrames[unitForData .. "_pointer"] = pointerFrame
            
            -- Store unit for OnUpdate positioning
            pointerFrame.unit = unitForData
            
            -- Set up continuous positioning
            pointerFrame:SetScript("OnUpdate", UpdateFramePosition)
            
            -- Store reference on frame for compatibility
            frame.ArenaCore_Pointer = pointerFrame
            
            pointerFrame.icon = pointerFrame:CreateTexture(nil, "ARTWORK")
            pointerFrame.icon:SetAtlas("UI-QuestPoiImportant-QuestNumber-SuperTracked")
            pointerFrame.icon:SetSize(34, 48)
            pointerFrame.icon:SetPoint("BOTTOM", pointerFrame, "BOTTOM", 0, 5)
            pointerFrame.icon:SetDesaturated(true)
            pointerFrame.icon:SetTexelSnappingBias(0.0)
            pointerFrame.icon:SetSnapToPixelGrid(false)
        end
        
        -- Apply pointer scale
        local pointerScale
        do
            local pm = AC.ProfileManager
            local scalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerScale")
            if type(scalePixels) == "number" then
                local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(scalePixels, 50, 360, 1, 12) or 6.5
                pointerScale = 0.5 + (currentScale - 1) * (2.5 / 11)
            else
                local rawScale = db.pointerScale or 100
                if rawScale <= 12 then
                    pointerScale = 0.5 + (rawScale - 1) * (2.5 / 11)
                else
                    pointerScale = rawScale / 100
                end
            end
            pointerScale = math.max(0.5, math.min(pointerScale or 1.0, 3.0))
            pointerFrame:SetScale(pointerScale)
        end
        
        -- Apply pointer offsets
        local ptrOffX, ptrOffY = 0, 0
        do
            local pm = AC.ProfileManager
            
            -- Check if Link Horizontal is enabled - if so, use scale-compensated offset
            local linkHorizontal = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.linkHorizontal")
            if linkHorizontal == nil then linkHorizontal = db.linkHorizontal end
            
            local px, py
            if linkHorizontal then
                -- LINKED MODE: Calculate scale-compensated offset so icons stay visually aligned
                local classOffX = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetX")
                if type(classOffX) ~= "number" then classOffX = db.offsetX or 0 end
                
                -- Get class icon scale for compensation
                local classScalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.scale")
                local classScale = 1.0
                if type(classScalePixels) == "number" then
                    local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(classScalePixels, 50, 360, 1, 12) or 6.5
                    classScale = 0.5 + (currentScale - 1) * (2.5 / 11)
                else
                    local rawScale = db.scale or 100
                    if rawScale <= 12 then
                        classScale = 0.5 + (rawScale - 1) * (2.5 / 11)
                    else
                        classScale = rawScale / 100
                    end
                end
                classScale = math.max(0.5, math.min(classScale or 1.0, 3.0))
                
                -- Scale-compensated offset: ptrOffX = (classOffX * classScale) / pointerScale
                if pointerScale > 0 then
                    px = (classOffX * classScale) / pointerScale
                else
                    px = classOffX
                end
            else
                -- UNLINKED MODE: Use pointer's own horizontal offset
                px = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerOffsetX")
                if type(px) ~= "number" then px = db.pointerOffsetX or 0 end
            end
            
            py = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerOffsetY")
            if type(py) ~= "number" then py = db.pointerOffsetY or 0 end
            
            ptrOffX, ptrOffY = tonumber(px) or 0, tonumber(py) or 0
        end
        
        -- Position pointer above class icon (both share same horizontal center when offset=0)
        local basePointerY = 40
        -- Store offsets for OnUpdate handler
        pointerFrame.offsetX = ptrOffX
        pointerFrame.offsetY = basePointerY + ptrOffY
        
        -- Color pointer based on class
        if class and RAID_CLASS_COLORS[class] then
            local classColor = RAID_CLASS_COLORS[class]
            pointerFrame.icon:SetVertexColor(classColor.r, classColor.g, classColor.b, 1)
        end
        
        pointerFrame:Show()
    else
        local pointerFrame = independentFrames[unitForData .. "_pointer"]
        if pointerFrame then
            pointerFrame:Hide()
        end
    end
    
    -- ========================================================================
    -- HIDE HEALTH BARS (Enhanced for Midnight compatibility and reliability)
    -- ========================================================================
    
    if db.hideHealthBars then
        -- Use a flag to track state
        if not frame.__acClassIndicatorActive then
            -- ENHANCED: Multiple methods for better reliability
            -- Method 1: Hide HealthBarsContainer (standard approach)
            if frame.HealthBarsContainer then
                -- In Midnight, check if we can manipulate alpha
                local canUseAlpha = true
                if AC.IsMidnight and AC:IsMidnight() then
                    -- In Midnight, check if we're in restricted content
                    canUseAlpha = not AC:IsInRestrictedContent()
                end
                
                if canUseAlpha then
                    frame.HealthBarsContainer:SetAlpha(0)
                else
                    -- Fallback for Midnight restricted content: hide individual bars
                    if frame.HealthBarsContainer.HealthBar then
                        frame.HealthBarsContainer.HealthBar:Hide()
                    end
                    if frame.HealthBarsContainer.AbsorbBar then
                        frame.HealthBarsContainer.AbsorbBar:Hide()
                    end
                    if frame.HealthBarsContainer.HealPrediction then
                        frame.HealthBarsContainer.HealPrediction:Hide()
                    end
                    if frame.HealthBarsContainer.TempMaxHealthLoss then
                        frame.HealthBarsContainer.TempMaxHealthLoss:Hide()
                    end
                end
            end
            
            -- Method 2: Hide selection highlight (more reliable)
            if frame.selectionHighlight then
                frame.selectionHighlight:SetAlpha(0)
            end
            
            -- Method 3: Try to hide individual health bar components (fallback)
            if frame.healthBar then
                frame.healthBar:Hide()
            end

            -- Method 3.5: Hide friendly nameplate buff frame(s)
            -- FStack example: NamePlateX.UnitFrame.AurasFrame.BuffListFrame
            do
                local canTouchAuras = true
                if AC.IsMidnight and AC:IsMidnight() then
                    canTouchAuras = not AC:IsInRestrictedContent()
                end
                if canTouchAuras then
                    pcall(function()
                        local aurasFrame = frame.AurasFrame or frame.aurasFrame
                        if aurasFrame then
                            local buffFrame = aurasFrame.BuffListFrame or aurasFrame.BuffFrame or aurasFrame.BuffsFrame or aurasFrame.Buffs
                            if buffFrame then
                                buffFrame:Hide()
                                if buffFrame.SetAlpha then buffFrame:SetAlpha(0) end
                                frame.__acBuffsHidden = true
                            end
                        end
                    end)
                end
            end
            
            -- Method 4: Use CVar approach for additional reliability (if available)
            -- This affects all nameplates globally but provides consistent behavior
            if not frame.__acHealthBarCVarSet and not AC.IsMidnight then
                -- Store original value before changing
                frame.__acOriginalNameplateShowFriends = GetCVar("nameplateShowFriends")
                SetCVar("nameplateShowFriends", "1") -- Ensure friendly nameplates are shown
                frame.__acHealthBarCVarSet = true
            end
            
            frame.__acClassIndicatorActive = true  -- Set flag
        end
    else
        -- Restore if previously hidden
        if frame.__acClassIndicatorActive then
            -- Restore HealthBarsContainer
            if frame.HealthBarsContainer then
                local canUseAlpha = true
                if AC.IsMidnight and AC:IsMidnight() then
                    canUseAlpha = not AC:IsInRestrictedContent()
                end
                
                if canUseAlpha then
                    frame.HealthBarsContainer:SetAlpha(1)
                else
                    -- Restore individual bars for Midnight restricted content
                    if frame.HealthBarsContainer.HealthBar then
                        frame.HealthBarsContainer.HealthBar:Show()
                    end
                    if frame.HealthBarsContainer.AbsorbBar then
                        frame.HealthBarsContainer.AbsorbBar:Show()
                    end
                    if frame.HealthBarsContainer.HealPrediction then
                        frame.HealthBarsContainer.HealPrediction:Show()
                    end
                    if frame.HealthBarsContainer.TempMaxHealthLoss then
                        frame.HealthBarsContainer.TempMaxHealthLoss:Show()
                    end
                end
            end
            
            -- Restore selection highlight
            if frame.selectionHighlight then
                frame.selectionHighlight:SetAlpha(0.22)
            end
            
            -- Restore individual health bar
            if frame.healthBar then
                frame.healthBar:Show()
            end

            -- Restore friendly nameplate buff frame(s)
            if frame.__acBuffsHidden then
                local canTouchAuras = true
                if AC.IsMidnight and AC:IsMidnight() then
                    canTouchAuras = not AC:IsInRestrictedContent()
                end
                if canTouchAuras then
                    pcall(function()
                        local aurasFrame = frame.AurasFrame or frame.aurasFrame
                        if aurasFrame then
                            local buffFrame = aurasFrame.BuffListFrame or aurasFrame.BuffFrame or aurasFrame.BuffsFrame or aurasFrame.Buffs
                            if buffFrame then
                                if buffFrame.SetAlpha then buffFrame:SetAlpha(1) end
                                buffFrame:Show()
                            end
                        end
                    end)
                end
                frame.__acBuffsHidden = nil
            end
            
            -- Restore CVar if we changed it
            if frame.__acHealthBarCVarSet and frame.__acOriginalNameplateShowFriends then
                SetCVar("nameplateShowFriends", frame.__acOriginalNameplateShowFriends)
                frame.__acHealthBarCVarSet = nil
                frame.__acOriginalNameplateShowFriends = nil
            end
            
            frame.__acClassIndicatorActive = nil  -- Clear flag
        end
    end
    
    -- ========================================================================
    -- HIDE NAMES
    -- ========================================================================
    
    -- Hide names when class icons are showing OR when hideHealthBars is enabled
    local shouldHideName = (frame.ArenaCore_ClassIndicator and frame.ArenaCore_ClassIndicator:IsShown()) or db.hideHealthBars
    if shouldHideName then
        frame.acHideNameFlag = true
        if frame.name then
            frame.name:SetAlpha(0)
        end
    else
        frame.acHideNameFlag = false
        if frame.name then
            frame.name:SetAlpha(1)
        end
    end
end

-- ============================================================================
-- REFRESH FUNCTION: Called when settings change
-- ============================================================================

function AC:RefreshPartyClassIcons()
    -- Refresh all visible nameplates
    for _, nameplate in pairs(C_NamePlate.GetNamePlates()) do
        -- Universal nameplate addon compatibility
        -- Plater/Platynator use nameplate.unitFrame (lowercase 'u')
        -- Blizzard uses nameplate.UnitFrame (uppercase 'U')
        local frame = nameplate.unitFrame or nameplate.UnitFrame
        local forbidden = frame and frame.IsForbidden and frame:IsForbidden()
        if frame and not forbidden then
            AC.UpdatePartyClassIndicator(frame)
        end
    end
    
    -- ENHANCED: Start persistent health bar hiding refresh if enabled
    AC:EnsureHealthBarHidingPersistence()
end

-- ============================================================================
-- INDEPENDENT REFRESH FUNCTIONS: Update only specific elements without flickering
-- ============================================================================

-- Refresh ONLY class icon positioning/scale (doesn't touch pointers)
function AC:RefreshClassIconsOnly()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    if not db then return end
    
    -- Get current class icon settings
    local pm = AC.ProfileManager
    local offX = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetX")
    local offY = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetY")
    local scalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.scale")
    
    if type(offX) ~= "number" then offX = db.offsetX or 0 end
    if type(offY) ~= "number" then offY = db.offsetY or 0 end
    
    local baseY = 8
    
    -- Calculate scale
    local finalScale = 1.0
    if type(scalePixels) == "number" then
        local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(scalePixels, 50, 360, 1, 12) or 6.5
        finalScale = 0.5 + (currentScale - 1) * (2.5 / 11)
    else
        local rawScale = db.scale or 100
        if rawScale <= 12 then
            finalScale = 0.5 + (rawScale - 1) * (2.5 / 11)
        else
            finalScale = rawScale / 100
        end
    end
    finalScale = math.max(0.5, math.min(finalScale or 1.0, 3.0))
    
    -- Update all existing class icon frames directly (no recreation)
    for key, classFrame in pairs(independentFrames) do
        if not key:find("_pointer") and classFrame.offsetX ~= nil then
            classFrame.offsetX = offX
            classFrame.offsetY = baseY + offY
            classFrame._anchoredNameplate = nil
            classFrame._anchoredOffsetX = nil
            classFrame._anchoredOffsetY = nil
            classFrame:SetScale(finalScale)
            UpdateFramePosition(classFrame, POSITION_UPDATE_INTERVAL)
        end
    end
end

-- Refresh ONLY pointer positioning/scale (doesn't touch class icons)
function AC:RefreshPointersOnly()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    if not db then return end
    
    local pm = AC.ProfileManager
    
    -- Check if Link Horizontal is enabled
    local linkHorizontal = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.linkHorizontal")
    if linkHorizontal == nil then linkHorizontal = db.linkHorizontal end
    
    -- Get pointer scale first (needed for scale compensation)
    local ptrScalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerScale")
    local pointerScale = 1.0
    if type(ptrScalePixels) == "number" then
        local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(ptrScalePixels, 50, 360, 1, 12) or 6.5
        pointerScale = 0.5 + (currentScale - 1) * (2.5 / 11)
    else
        local rawScale = db.pointerScale or 100
        if rawScale <= 12 then
            pointerScale = 0.5 + (rawScale - 1) * (2.5 / 11)
        else
            pointerScale = rawScale / 100
        end
    end
    pointerScale = math.max(0.5, math.min(pointerScale or 1.0, 3.0))
    
    -- Get pointer offsets
    local ptrOffX, ptrOffY
    if linkHorizontal then
        -- LINKED MODE: Calculate scale-compensated offset so icons stay visually aligned
        -- Get class icon's horizontal offset and scale
        local classOffX = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.offsetX")
        if type(classOffX) ~= "number" then classOffX = db.offsetX or 0 end
        
        local classScalePixels = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.scale")
        local classScale = 1.0
        if type(classScalePixels) == "number" then
            local currentScale = AC.ConvertPixelsToScale and AC:ConvertPixelsToScale(classScalePixels, 50, 360, 1, 12) or 6.5
            classScale = 0.5 + (currentScale - 1) * (2.5 / 11)
        else
            local rawScale = db.scale or 100
            if rawScale <= 12 then
                classScale = 0.5 + (rawScale - 1) * (2.5 / 11)
            else
                classScale = rawScale / 100
            end
        end
        classScale = math.max(0.5, math.min(classScale or 1.0, 3.0))
        
        -- Scale-compensated offset: ensures both icons visually align at same horizontal position
        -- Formula: pointerVisualX = classVisualX → ptrOffX * ptrScale = classOffX * classScale
        -- So: ptrOffX = (classOffX * classScale) / ptrScale
        if pointerScale > 0 then
            ptrOffX = (classOffX * classScale) / pointerScale
        else
            ptrOffX = classOffX
        end
    else
        -- UNLINKED MODE: Use pointer's own horizontal offset
        ptrOffX = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerOffsetX")
        if type(ptrOffX) ~= "number" then ptrOffX = db.pointerOffsetX or 0 end
    end
    
    ptrOffY = pm and pm.GetSetting and pm:GetSetting("moreGoodies.partyClassSpecs.pointerOffsetY")
    if type(ptrOffY) ~= "number" then ptrOffY = db.pointerOffsetY or 0 end
    
    local basePointerY = 40
    
    -- Update all existing pointer frames directly (no recreation)
    for key, pointerFrame in pairs(independentFrames) do
        if key:find("_pointer") and pointerFrame.offsetX ~= nil then
            pointerFrame.offsetX = ptrOffX
            pointerFrame.offsetY = basePointerY + ptrOffY
            pointerFrame:SetScale(pointerScale)
        end
    end
end

-- ============================================================================
-- PERSISTENT HEALTH BAR HIDING (Enhanced reliability)
-- ============================================================================

-- Timer reference for persistent refresh
local healthBarRefreshTimer = nil

function AC:EnsureHealthBarHidingPersistence()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    
    if db and db.hideHealthBars then
        -- Start periodic refresh if health bar hiding is enabled
        if not healthBarRefreshTimer then
            healthBarRefreshTimer = C_Timer.NewTicker(0.5, function()
                AC:RefreshPartyClassIcons()
            end)
        end
    else
        -- Stop refresh if health bar hiding is disabled
        if healthBarRefreshTimer then
            healthBarRefreshTimer:Cancel()
            healthBarRefreshTimer = nil
        end
    end
end

-- Enhanced refresh with additional health bar checks
local originalUpdatePartyClassIndicator = AC.UpdatePartyClassIndicator
AC.UpdatePartyClassIndicator = function(frame)
    -- Call original function first
    if originalUpdatePartyClassIndicator then
        originalUpdatePartyClassIndicator(frame)
    end
    
    -- ENHANCED: Additional health bar hiding enforcement
    local db = AC.DB and AC.DB.profile and AC.DB.profile.moreGoodies and AC.DB.profile.moreGoodies.partyClassSpecs
    
    if db and db.hideHealthBars and frame.__acClassIndicatorActive then
        -- Re-apply health bar hiding to combat overrides from other addons
        if frame.HealthBarsContainer then
            local canUseAlpha = true
            if AC.IsMidnight and AC:IsMidnight() then
                canUseAlpha = not AC:IsInRestrictedContent()
            end
            
            if canUseAlpha and frame.HealthBarsContainer:GetAlpha() > 0 then
                -- Something overrode our alpha setting, re-apply it
                frame.HealthBarsContainer:SetAlpha(0)
            end
        end
        
        -- Check individual health bar visibility
        if frame.healthBar and frame.healthBar:IsShown() then
            frame.healthBar:Hide()
        end
    end
end

-- ============================================================================
-- HOOK: Prevent Blizzard from showing names when hideNameOverride is set
-- ============================================================================

-- Hook CompactUnitFrame_UpdateName to enforce acHideNameFlag
local function OnNameUpdate(frame)
    local ok = pcall(function()
        if not frame then return end
        if type(frame) ~= "table" and type(frame) ~= "userdata" then return end
        
        if frame.IsForbidden and frame:IsForbidden() then return end
        if frame.IsProtected and frame:IsProtected() then return end
        
        local unit = frame.unit or frame.displayedUnit
        if type(unit) ~= "string" or not unit:find("nameplate") then return end
        
        -- If we set acHideNameFlag, enforce it
        -- Only use SetText here; keep alpha handling within ArenaCore
        if frame.acHideNameFlag and frame.name then
            frame.name:SetText("")
        end
    end)

    if not ok then
        return
    end
end

-- Register the hook after a short delay to ensure CompactUnitFrame_UpdateName exists
C_Timer.After(0.1, function()
    if CompactUnitFrame_UpdateName then
        hooksecurefunc("CompactUnitFrame_UpdateName", OnNameUpdate)
    end
end)

-- ============================================================================
-- INTEGRATION: This will be called from BlackoutEngine's NAME_PLATE_UNIT_ADDED
-- ============================================================================

-- Module loaded (debug message removed for cleaner startup)

-- ============================================================================
-- CLEANUP: Ensure timer is stopped on addon shutdown
-- ============================================================================

-- Hook into addon shutdown to clean up timer
local function OnAddonShutdown()
    if healthBarRefreshTimer then
        healthBarRefreshTimer:Cancel()
        healthBarRefreshTimer = nil
    end
end

-- Register cleanup on addon reload or logout
local cleanupFrame = CreateFrame("Frame")
cleanupFrame:RegisterEvent("PLAYER_LOGOUT") -- Triggered on logout and reload
cleanupFrame:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_LOGOUT" then
        OnAddonShutdown()
    end
end)

-- ============================================================================
-- CACHE CLEANUP EVENT HANDLERS (Solo Shuffle support)
-- ============================================================================
-- Only clear class cache when LEAVING arena, not between rounds

local cacheCleanupFrame = CreateFrame("Frame")
cacheCleanupFrame:RegisterEvent("PLAYER_ENTERING_WORLD")

cacheCleanupFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        -- Only clear cache when leaving arena (not entering)
        local _, instanceType = IsInInstance()
        if instanceType ~= "arena" then
            ClearAllClassCache()
        end
    end
end)

local targetGlowEventFrame = CreateFrame("Frame")
targetGlowEventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
targetGlowEventFrame:SetScript("OnEvent", function()
    local hasTarget = UnitExists("target")
    for key, classFrame in pairs(independentFrames) do
        if not key:find("_pointer") and classFrame and classFrame.unit then
            ApplyTargetGlow(classFrame, hasTarget and UnitIsUnit(classFrame.unit, "target"))
        end
    end
end)
