--==============================================================================
-- RC PARTY ORBS 
-- Version: 1.7
-- Author: Claude
-- Notes: Replacement for party frames with animated fire orbs.
-- Features: Threat borders, debuff colors, click-to-dispel, click-to-rez
--           Configurable modifier key, adjustable font size
--==============================================================================

-----------------------------------------------------------
-- 1. CONSTANTS AND DEFAULTS
-----------------------------------------------------------
local FRAME_COUNT = 29
local FRAME_RATE = 0.05  -- seconds per frame (20 fps)
local ORB_SIZE = 80
local ORB_SPACING = 15

-- Tables to store our frames and states
local orbFrames = {}
local testMode = false
local debugMode = false 
local testPartyData = {}

-- Create the database if it doesn't exist
RCPartyOrbsDB = RCPartyOrbsDB or {}

-- Swirl sets configuration for orb interiors
local SWIRL_SETS = {
    ["bubbles"] = {
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_bubbles1.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_bubbles2.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_bubbles3.tga"
    },
    ["galaxy"] = {
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_galaxy1.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_galaxy2.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_galaxy3.tga"
    },
    ["iris"] = {
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_iris1.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_iris2.tga",
        "Interface\\AddOns\\RCPartyOrbs\\images\\orb_rotation_iris3.tga"
    }
}

local currentSwirlSet = "bubbles"

-- Default Global Database Template to prevent Startup Errors
local DB_DEFAULTS = {
    enabled = true,
    swirlSet = "bubbles",
    scale = 1.0,
    orbSize = 80,
    orbSpacing = 15,
    targetOrbScale = 75,  -- Target orb size as percentage (75 = 75% of normal orb size)
    showHealthPercentage = true,
    showHealthValue = true,
    showManaValue = true,
    showName = true,
    showThreatBorders = true,
    showDebuffColors = true,
    showPlayerOrbs = true,
    showPartyTargets = true,  -- Show party member target orbs (master toggle)
    showHelpOnLogin = true,
    framesLocked = true,
    partyOrientation = "horizontal",  -- "horizontal" or "vertical"
    fireColor = {r = 1, g = 1, b = 1},
    fireBorderColor = {r=1, g=0, b=0, a=1},
    greenBorderColor = {r=0, g=1, b=0, a=1},
    yellowBorderColor = {r=1, g=1, b=0, a=1},
    minimapButtonAngle = 45,
    clickModifier = "shift",  -- Modifier key for dispel/rez: "shift", "alt", or "ctrl"
    fontScale = 100,  -- Font size as percentage (50-200%)
    -- Individual orb visibility settings
    showPlayerOrb = true,
    showParty1Orb = true,
    showParty2Orb = true,
    showParty3Orb = true,
    showParty4Orb = true,
    showTargetOrb = true,
    showParty1Target = true,
    showParty2Target = true,
    showParty3Target = true,
    showParty4Target = true,
    debugMode = false
}

-- Class Color mapping for unit health bars
local CLASS_COLORS = {
    ["WARRIOR"]     = {0.78, 0.61, 0.43},
    ["PALADIN"]     = {0.96, 0.55, 0.73},
    ["HUNTER"]      = {0.67, 0.83, 0.45},
    ["ROGUE"]       = {1.0, 0.96, 0.41},
    ["PRIEST"]      = {1.0, 1.0, 1.0},
    ["DEATHKNIGHT"] = {0.77, 0.12, 0.23},
    ["SHAMAN"]      = {0.0, 0.44, 0.87},
    ["MAGE"]        = {0.41, 0.8, 0.94},
    ["WARLOCK"]     = {0.58, 0.51, 0.79},
    ["MONK"]        = {0.0, 1.0, 0.59},
    ["DRUID"]       = {1.0, 0.49, 0.04},
    ["DEMONHUNTER"] = {0.64, 0.19, 0.79},
}

-- Debuff type colors for dispel indicators (use Blizzard's colors as base, with brighter versions)
local DEBUFF_COLORS = {
    ["Poison"]  = {0.0, 0.6, 0.0},    -- Green (Blizzard default)
    ["Disease"] = {0.6, 0.4, 0.0},    -- Brown (Blizzard default)
    ["Curse"]   = {0.6, 0.0, 1.0},    -- Purple (Blizzard default)
    ["Magic"]   = {0.2, 0.6, 1.0},    -- Blue (Blizzard default)
}

-- Override with Blizzard's DebuffTypeColor if available (more accurate)
if DebuffTypeColor then
    if DebuffTypeColor["Poison"] then DEBUFF_COLORS["Poison"] = {DebuffTypeColor["Poison"].r, DebuffTypeColor["Poison"].g, DebuffTypeColor["Poison"].b} end
    if DebuffTypeColor["Disease"] then DEBUFF_COLORS["Disease"] = {DebuffTypeColor["Disease"].r, DebuffTypeColor["Disease"].g, DebuffTypeColor["Disease"].b} end
    if DebuffTypeColor["Curse"] then DEBUFF_COLORS["Curse"] = {DebuffTypeColor["Curse"].r, DebuffTypeColor["Curse"].g, DebuffTypeColor["Curse"].b} end
    if DebuffTypeColor["Magic"] then DEBUFF_COLORS["Magic"] = {DebuffTypeColor["Magic"].r, DebuffTypeColor["Magic"].g, DebuffTypeColor["Magic"].b} end
end

-- Dynamic dispel list - updated when spells change
local playerDispelList = {}

-- Spell IDs for dispel abilities (MoP Classic)
-- Format: spellID = { dispelTypes = {...}, checkSpellID = spellID or nil for talent check }
local DISPEL_SPELLS = {
    -- Paladin
    [4987]  = { class = "PALADIN", types = {"Poison", "Disease"}, name = "Cleanse" },  -- Cleanse (baseline)
    [53551] = { class = "PALADIN", types = {"Magic"}, name = "Sacred Cleansing", isTalent = true },  -- Sacred Cleansing (Holy talent)
    -- Priest
    [527]   = { class = "PRIEST", types = {"Magic"}, name = "Purify" },  -- Purify (was Dispel Magic)
    [528]   = { class = "PRIEST", types = {"Disease"}, name = "Cure Disease" },  -- Cure Disease
    -- Shaman
    [51886] = { class = "SHAMAN", types = {"Curse"}, name = "Cleanse Spirit" },  -- Cleanse Spirit (baseline)
    [77130] = { class = "SHAMAN", types = {"Curse", "Magic"}, name = "Purify Spirit" },  -- Purify Spirit (Resto)
    -- Mage
    [475]   = { class = "MAGE", types = {"Curse"}, name = "Remove Curse" },  -- Remove Curse
    -- Druid
    [2782]  = { class = "DRUID", types = {"Curse", "Poison"}, name = "Remove Corruption" },  -- Remove Corruption (baseline)
    [88423] = { class = "DRUID", types = {"Curse", "Poison", "Magic"}, name = "Nature's Cure" },  -- Nature's Cure (Resto)
    -- Monk
    [115450] = { class = "MONK", types = {"Poison", "Disease", "Magic"}, name = "Detox" },  -- Detox (Mistweaver)
    [218164] = { class = "MONK", types = {"Poison", "Disease"}, name = "Detox" },  -- Detox (BM/WW)
}

-- Dispel spell names for click-to-dispel (primary dispel per class)
local CLASS_DISPEL_SPELLS = {
    ["PALADIN"]  = "Cleanse",
    ["PRIEST"]   = "Purify",
    ["SHAMAN"]   = "Cleanse Spirit",
    ["MAGE"]     = "Remove Curse",
    ["DRUID"]    = "Remove Corruption",
    ["MONK"]     = "Detox",
}

-- Battle rez spells per class (MoP Classic) - combat only, no regular rez
local CLASS_REZ_SPELLS = {
    ["DRUID"]       = { combat = "Rebirth" },
    ["DEATHKNIGHT"] = { combat = "Raise Ally" },
    ["WARLOCK"]     = { combat = "Soulstone" },
    ["HUNTER"]      = { combat = "Eternal Guardian" },  -- Quilen pet ability
}

-- Get the player's battle rez spell (combat only)
local function GetPlayerRezSpell()
    local _, playerClass = UnitClass("player")
    local rezData = CLASS_REZ_SPELLS[playerClass]
    if not rezData then return nil end
    return rezData.combat
end

-- Helper function to check if a spell is known
local function IsSpellKnownByPlayer(spellID)
    -- Try multiple methods to check spell availability
    if IsSpellKnown then
        if IsSpellKnown(spellID) then return true end
    end
    if IsPlayerSpell then
        if IsPlayerSpell(spellID) then return true end
    end
    -- Check spellbook
    local name = GetSpellInfo(spellID)
    if name then
        local spellBookID = FindSpellBookSlotBySpellID(spellID)
        if spellBookID then return true end
        -- Also check by name
        local _, _, _, _, _, _, actualSpellID = GetSpellInfo(name)
        if actualSpellID == spellID then return true end
    end
    return false
end

-- Update what the player can dispel (called on login and talent changes)
local function UpdatePlayerDispels()
    wipe(playerDispelList)
    local _, playerClass = UnitClass("player")
    
    for spellID, data in pairs(DISPEL_SPELLS) do
        if data.class == playerClass then
            -- Check if player has this spell
            local hasSpell = IsSpellKnownByPlayer(spellID)
            if hasSpell then
                for _, dispelType in ipairs(data.types) do
                    playerDispelList[dispelType] = true
                end
                if debugMode then
                    print("|cFF00FF00RCPartyOrbs:|r Found dispel spell: " .. (data.name or spellID) .. " - can dispel: " .. table.concat(data.types, ", "))
                end
            end
        end
    end
    
    if debugMode then
        local dispelTypes = {}
        for k, _ in pairs(playerDispelList) do table.insert(dispelTypes, k) end
        if #dispelTypes > 0 then
            print("|cFF00FF00RCPartyOrbs:|r Player can dispel: " .. table.concat(dispelTypes, ", "))
        else
            print("|cFFFF0000RCPartyOrbs:|r Player cannot dispel anything (no dispel spells found)")
        end
    end
end

-- Offensive dispel/purge spell names for each class (removes buffs from enemies)
local CLASS_PURGE_SPELLS = {
    ["SHAMAN"]   = "Purge",
    ["PRIEST"]   = "Dispel Magic",
    ["MAGE"]     = "Spellsteal",
    ["WARLOCK"]  = "Devour Magic",  -- Felhunter ability
    ["HUNTER"]   = "Tranquilizing Shot",
}

-- Color for purgeable buffs on enemies (bright white/gold)
local PURGEABLE_BUFF_COLOR = {1.0, 0.8, 0.2}  -- Gold/yellow for stealable buffs

-- Store original colors for restoration after dispel
local originalOrbColors = {}

-- Test debuff mode
local testDebuffMode = false
local testDebuffType = nil

-----------------------------------------------------------
-- 2. UTILITY & DATA FUNCTIONS
-----------------------------------------------------------

 local function SafeGetNormalizedRealmName()
     if type(GetNormalizedRealmName) == "function" then
         return GetNormalizedRealmName()
     end
     return GetRealmName and GetRealmName() or ""
 end

 local function SafeUnitGroupRolesAssigned(unit)
     if type(UnitGroupRolesAssigned) == "function" then
         return UnitGroupRolesAssigned(unit)
     end
     return "NONE"
 end

 local function IsInInstanceGroup()
     if type(IsInGroup) ~= "function" then return false end
     if LE_PARTY_CATEGORY_INSTANCE ~= nil then
         return IsInGroup(LE_PARTY_CATEGORY_INSTANCE)
     end
     return false
 end

 local function TryLFGTeleport(out)
     if type(LFGTeleport) == "function" then
         LFGTeleport(out and true or false)
         return true
     end
     return false
 end

 local function TryLeaveInstanceGroup()
     if C_PartyInfo and type(C_PartyInfo.LeaveParty) == "function" and LE_PARTY_CATEGORY_INSTANCE ~= nil then
         C_PartyInfo.LeaveParty(LE_PARTY_CATEGORY_INSTANCE)
         return true
     end
     return false
 end

-- Formats health values (e.g., 5500 -> 5.5K)
local function FormatHealth(val)
    if not val then return "0" end
    if val >= 1000000 then
        return string.format("%.1fM", val / 1000000)
    elseif val >= 1000 then
        return string.format("%.1fK", val / 1000)
    else
        return tostring(val)
    end
end

-- Helper to safely get class RGB
local function GetClassColor(unit)
    local _, class = UnitClass(unit)
    if class and CLASS_COLORS[class] then
        return unpack(CLASS_COLORS[class])
    end
    return 0.5, 0.5, 0.5 
end

-- Get what the player can dispel (returns the dynamic list)
local function GetPlayerDispelTypes()
    -- If list is empty, try to update it
    if not next(playerDispelList) then
        UpdatePlayerDispels()
    end
    return playerDispelList
end

-- Get the dispel spell name for the player's class
local function GetPlayerDispelSpell()
    local _, playerClass = UnitClass("player")
    return CLASS_DISPEL_SPELLS[playerClass]
end

-- Get the purge/offensive dispel spell name for the player's class
local function GetPlayerPurgeSpell()
    local _, playerClass = UnitClass("player")
    return CLASS_PURGE_SPELLS[playerClass]
end

-- Check if a unit (enemy) has a purgeable/stealable buff
-- Returns: true, r, g, b if purgeable buff found, nil otherwise
local function GetPurgeableBuff(unit)
    if not UnitExists(unit) then return nil end
    if not UnitCanAttack("player", unit) then return nil end  -- Only for enemies
    
    local purgeSpell = GetPlayerPurgeSpell()
    if not purgeSpell then return nil end  -- Player can't purge
    
    -- Scan all buffs on the unit
    for i = 1, 40 do
        local name, icon, count, debuffType, duration, expirationTime, source, isStealable, 
              nameplateShowPersonal, spellId = UnitBuff(unit, i)
        
        if not name then break end  -- No more buffs
        
        -- Check if this buff is stealable/purgeable (Magic type buffs)
        if isStealable or debuffType == "Magic" then
            return true, PURGEABLE_BUFF_COLOR[1], PURGEABLE_BUFF_COLOR[2], PURGEABLE_BUFF_COLOR[3]
        end
    end
    
    return nil  -- No purgeable buffs found
end

-- Check if a unit has a dispellable debuff that the player can remove
-- Returns: debuffType, r, g, b or nil if no dispellable debuff
local function GetDispellableDebuff(unit)
    if not UnitExists(unit) then return nil end
    
    -- Check for test debuff mode first
    if testDebuffMode and testDebuffType then
        local color = DEBUFF_COLORS[testDebuffType]
        if color then
            return testDebuffType, color[1], color[2], color[3]
        end
    end
    
    local canDispel = GetPlayerDispelTypes()
    if not next(canDispel) then 
        if debugMode then
            print("|cFFFF0000RCPartyOrbs:|r GetDispellableDebuff - player can't dispel anything")
        end
        return nil 
    end
    
    -- Scan all debuffs on the unit
    -- Use UnitDebuff (or UnitAura with HARMFUL filter as fallback)
    local getDebuff = UnitDebuff or function(u, i) return UnitAura(u, i, "HARMFUL") end
    
    for i = 1, 40 do
        local name, icon, count, debuffType, duration, expirationTime, source, isStealable, 
              nameplateShowPersonal, spellId = getDebuff(unit, i)
        
        if not name then break end  -- No more debuffs
        
        -- Debug: show all debuffs found
        if debugMode then
            print("|cFFFFFF00RCPartyOrbs:|r " .. unit .. " debuff " .. i .. ": " .. name .. " type=" .. tostring(debuffType) .. " canDispel=" .. tostring(canDispel[debuffType] or false))
        end
        
        -- Check if this debuff type can be dispelled by the player
        if debuffType and canDispel[debuffType] then
            local color = DEBUFF_COLORS[debuffType]
            if color then
                if debugMode then
                    print("|cFF00FF00RCPartyOrbs:|r Found dispellable " .. debuffType .. " on " .. unit .. ": " .. name)
                end
                return debuffType, color[1], color[2], color[3]
            end
        end
    end
    
    return nil  -- No dispellable debuffs found
end

-- Update orb color based on dispellable debuffs or purgeable buffs
local function UpdateOrbDebuffColor(frame)
    if not frame or not frame.unit then return end
    if not RCPartyOrbsDB.showDebuffColors then return end
    
    local unit = frame.unit
    local debuffType, r, g, b = nil, nil, nil, nil
    local isPurgeable = false
    
    -- Check for dispellable debuffs (friendly units)
    if UnitIsFriend("player", unit) or not UnitExists(unit) then
        debuffType, r, g, b = GetDispellableDebuff(unit)
    else
        -- Check for purgeable buffs (enemy units - target orb)
        isPurgeable, r, g, b = GetPurgeableBuff(unit)
    end
    
    if debuffType or isPurgeable then
        -- Store original color if not already stored
        if not originalOrbColors[unit] then
            originalOrbColors[unit] = {GetClassColor(unit)}
        end
        
        -- Make a lighter/brighter version of the debuff color for galaxies
        local lightR = math.min(1.0, r + 0.3)
        local lightG = math.min(1.0, g + 0.3)
        local lightB = math.min(1.0, b + 0.3)
        
        -- Apply debuff color to health orb filling
        if frame.healthOrb then
            if frame.healthOrb.filling then
                frame.healthOrb.filling:SetVertexColor(r, g, b, 1)
            end
            -- Apply lighter debuff color to galaxies
            if frame.healthOrb.galaxy1 and frame.healthOrb.galaxy1.texture then 
                frame.healthOrb.galaxy1.texture:SetVertexColor(lightR, lightG, lightB, 1) 
            end
            if frame.healthOrb.galaxy2 and frame.healthOrb.galaxy2.texture then 
                frame.healthOrb.galaxy2.texture:SetVertexColor(lightR, lightG, lightB, 1) 
            end
            if frame.healthOrb.galaxy3 and frame.healthOrb.galaxy3.texture then 
                frame.healthOrb.galaxy3.texture:SetVertexColor(lightR, lightG, lightB, 1) 
            end
        end
        
        -- Tint the mana orb to the debuff color
        if frame.manaOrb and frame.manaOrb.filling then
            frame.manaOrb.filling:SetVertexColor(r * 0.8, g * 0.8, b * 0.8, 1)
        end
        
        frame.hasDebuffColor = true
        frame.hasPurgeableBuff = isPurgeable
    else
        -- Restore original color if we had a debuff color applied
        if frame.hasDebuffColor then
            local origColor = originalOrbColors[unit]
            if origColor then
                local cr, cg, cb = origColor[1], origColor[2], origColor[3]
                -- Restore health orb filling to class color
                if frame.healthOrb then
                    if frame.healthOrb.filling then
                        frame.healthOrb.filling:SetVertexColor(cr, cg, cb, 1)
                    end
                    -- Restore galaxy colors to class color
                    if frame.healthOrb.galaxy1 and frame.healthOrb.galaxy1.texture then 
                        frame.healthOrb.galaxy1.texture:SetVertexColor(cr, cg, cb, 1) 
                    end
                    if frame.healthOrb.galaxy2 and frame.healthOrb.galaxy2.texture then 
                        frame.healthOrb.galaxy2.texture:SetVertexColor(cr, cg, cb, 1) 
                    end
                    if frame.healthOrb.galaxy3 and frame.healthOrb.galaxy3.texture then 
                        frame.healthOrb.galaxy3.texture:SetVertexColor(cr, cg, cb, 1) 
                    end
                end
                -- Restore mana orb to blue
                if frame.manaOrb and frame.manaOrb.filling then
                    frame.manaOrb.filling:SetVertexColor(0.0, 0.5, 1.0, 1)
                end
            end
            frame.hasDebuffColor = false
            frame.hasPurgeableBuff = false
            originalOrbColors[unit] = nil
        end
    end
end

-- Global flag to prevent reposition loops
local isRepositioning = false
local isDragging = false

-- Calculates position and anchor for orbs (now uses PositionOrbs from section 7)
function RCPartyOrbs_RepositionOrbs()
    if isDragging then return end
    isRepositioning = true
    
    -- Call the new positioning function if orbFrames exists
    if orbFrames and PositionOrbs then
        PositionOrbs()
    end
    
    isRepositioning = false
end

-----------------------------------------------------------
-- 3. VISIBILITY LOGIC (Party vs Raid or Toggle)
-----------------------------------------------------------

-- Track if we need to update visibility after combat
local pendingVisibilityUpdate = false

function RCPartyOrbs_UpdateVisibility()
    local debug = RCPartyOrbsDB and RCPartyOrbsDB.debugMode
    
    -- Don't modify secure frames during combat
    if InCombatLockdown() then
        pendingVisibilityUpdate = true
        if debug then print("UpdateVisibility: In combat, queued for later") end
        return
    end
    
    if debug then print("UpdateVisibility called, showPlayerOrbs=" .. tostring(RCPartyOrbsDB.showPlayerOrbs)) end
    
    -- In test mode, show all orbs (respecting individual visibility settings)
    if testMode then
        if debug then print("  -> Test mode active, showing based on visibility settings") end
        -- Player orb
        if orbFrames and orbFrames[1] then
            if RCPartyOrbsDB.showPlayerOrb ~= false then
                orbFrames[1]:Show()
            else
                orbFrames[1]:Hide()
            end
        end
        -- Party orbs 1-4
        local partyOrbSettings = {
            RCPartyOrbsDB.showParty1Orb,
            RCPartyOrbsDB.showParty2Orb,
            RCPartyOrbsDB.showParty3Orb,
            RCPartyOrbsDB.showParty4Orb
        }
        for i = 2, 5 do
            if orbFrames and orbFrames[i] then
                if partyOrbSettings[i-1] ~= false then
                    orbFrames[i]:Show()
                else
                    orbFrames[i]:Hide()
                end
            end
        end
        -- Target orb
        if orbFrames and orbFrames[6] then
            if RCPartyOrbsDB.showTargetOrb ~= false then
                orbFrames[6]:Show()
            else
                orbFrames[6]:Hide()
            end
        end
        -- Party target orbs
        local partyTargetSettings = {
            RCPartyOrbsDB.showParty1Target,
            RCPartyOrbsDB.showParty2Target,
            RCPartyOrbsDB.showParty3Target,
            RCPartyOrbsDB.showParty4Target
        }
        local showPartyTargets = RCPartyOrbsDB.showPartyTargets ~= false
        for i = 7, 10 do
            if orbFrames and orbFrames[i] then
                local partyNum = i - 6
                if showPartyTargets and partyTargetSettings[partyNum] ~= false then
                    orbFrames[i]:Show()
                else
                    orbFrames[i]:Hide()
                end
            end
        end
        RCPartyOrbs_HideBlizzardFrames()
        return
    end
    
    if not RCPartyOrbsDB or not RCPartyOrbsDB.enabled then
        if debug then print("  -> Addon disabled, hiding all") end
        for i = 1, 10 do
            if orbFrames and orbFrames[i] then
                orbFrames[i]:Hide()
            end
        end
        RCPartyOrbs_ShowBlizzardFrames()
        return
    end
    
    -- Check if orbs are toggled off via Hide Orbs button
    if RCPartyOrbsDB.showPlayerOrbs == false then
        if debug then print("  -> showPlayerOrbs=false, hiding all orbs") end
        -- Hide ALL orbs
        for i = 1, 10 do
            if orbFrames and orbFrames[i] then
                orbFrames[i]:Hide()
            end
        end
        -- Show Blizzard frames
        RCPartyOrbs_ShowBlizzardFrames()
        return
    end
    
    if debug then print("  -> Normal mode, showing based on units and visibility settings") end
    
    -- In raid, hide party orbs
    if IsInRaid() then
        if debug then print("  -> In raid, hiding party orbs") end
        for i = 2, 10 do
            if orbFrames and orbFrames[i] then
                orbFrames[i]:Hide()
            end
        end
        RCPartyOrbs_ShowBlizzardFrames()
    else
        -- In party or solo, show orbs based on individual visibility settings
        
        -- Player orb - show if setting enabled
        if orbFrames and orbFrames[1] then
            if RCPartyOrbsDB.showPlayerOrb ~= false then
                orbFrames[1]:Show()
                if debug then print("  -> Showing player orb") end
            else
                orbFrames[1]:Hide()
            end
        end
        
        -- Party orbs - show if party member exists AND setting enabled
        local partyOrbSettings = {
            RCPartyOrbsDB.showParty1Orb,
            RCPartyOrbsDB.showParty2Orb,
            RCPartyOrbsDB.showParty3Orb,
            RCPartyOrbsDB.showParty4Orb
        }
        for i = 2, 5 do
            if orbFrames and orbFrames[i] then
                local partyNum = i - 1
                if partyOrbSettings[partyNum] ~= false and UnitExists("party" .. partyNum) then
                    orbFrames[i]:Show()
                    if debug then print("  -> Showing party" .. partyNum .. " orb") end
                else
                    orbFrames[i]:Hide()
                end
            end
        end
        
        -- Target orb - show if target exists AND setting enabled
        if orbFrames and orbFrames[6] then
            if RCPartyOrbsDB.showTargetOrb ~= false and UnitExists("target") then
                orbFrames[6]:Show()
                if debug then print("  -> Showing target orb") end
            else
                orbFrames[6]:Hide()
            end
        end
        
        -- Party target orbs (7-10) - show if:
        -- 1. Master toggle (showPartyTargets) is enabled
        -- 2. Individual toggle is enabled
        -- 3. Party member exists
        -- 4. Party member has a target
        local showPartyTargets = RCPartyOrbsDB.showPartyTargets ~= false
        local partyTargetSettings = {
            RCPartyOrbsDB.showParty1Target,
            RCPartyOrbsDB.showParty2Target,
            RCPartyOrbsDB.showParty3Target,
            RCPartyOrbsDB.showParty4Target
        }
        for i = 7, 10 do
            if orbFrames and orbFrames[i] then
                local partyNum = i - 6
                local showThis = showPartyTargets 
                    and partyTargetSettings[partyNum] ~= false
                    and UnitExists("party" .. partyNum) 
                    and UnitExists("party" .. partyNum .. "target")
                if showThis then
                    orbFrames[i]:Show()
                    if debug then print("  -> Showing party" .. partyNum .. "target orb") end
                else
                    orbFrames[i]:Hide()
                end
            end
        end
        
        -- Hide Blizzard frames
        RCPartyOrbs_HideBlizzardFrames()
    end
end

function RCPartyOrbs_HideBlizzardFrames()
    if PlayerFrame then
        PlayerFrame:Hide()
        PlayerFrame:UnregisterAllEvents()
        PlayerFrame:SetAlpha(0)
    end
    -- Hide party member frames
    for i = 1, 4 do 
        local pFrame = _G["PartyMemberFrame"..i]
        if pFrame then 
            pFrame:Hide()
            pFrame:UnregisterAllEvents()
        end 
    end
    
        
        
    -- Also hide the compact party frame if it exists (MoP)
    if CompactPartyFrame then
        CompactPartyFrame:Hide()
        CompactPartyFrame:UnregisterAllEvents()
    end
    -- Hide party member frame container
    if PartyMemberFrame1 then
        PartyMemberFrame1:Hide()
    end
    if PartyMemberFrame2 then
        PartyMemberFrame2:Hide()
    end
    if PartyMemberFrame3 then
        PartyMemberFrame3:Hide()
    end
    if PartyMemberFrame4 then
        PartyMemberFrame4:Hide()
    end
    -- Hide target frame
    if TargetFrame then
        TargetFrame:Hide()
        TargetFrame:UnregisterAllEvents()
        TargetFrame:SetAlpha(0)
    end
    -- Hide target of target frame
    if TargetFrame and TargetFrame.toT then
        TargetFrame.toT:Hide()
        TargetFrame.toT:UnregisterAllEvents()
        TargetFrame.toT:SetAlpha(0)
    end
end

function RCPartyOrbs_ShowBlizzardFrames()
    if PlayerFrame then
        PlayerFrame:SetAlpha(1)
        PlayerFrame:Show()
        PlayerFrame:RegisterAllEvents()
        if PlayerFrame_Update then PlayerFrame_Update() end
    end
    -- Only show party frames if actually in a party
    for i = 1, 4 do 
        local pFrame = _G["PartyMemberFrame"..i]
        if pFrame then 
            pFrame:SetAlpha(1)
            pFrame:RegisterAllEvents()
            -- Only show if the party member exists
            if UnitExists("party"..i) then
                pFrame:Show()
            else
                pFrame:Hide()
            end
        end
    end
    
    -- Show target frame only if target exists
    if TargetFrame then
        TargetFrame:SetAlpha(1)
        TargetFrame:RegisterAllEvents()
        if UnitExists("target") then
            TargetFrame:Show()
        end
    end
    -- Show target of target frame
    if TargetFrame and TargetFrame.toT then
        TargetFrame.toT:SetAlpha(1)
        TargetFrame.toT:RegisterAllEvents()
    end
end

-----------------------------------------------------------
-- 4. SWIRL & ANIMATION LOGIC
-----------------------------------------------------------

-- Refreshes the galaxy/swirl textures for an orb (RCDiabloOrbs style)
function UpdateOrbSwirls(orb)
    -- Skip swirls for target orb
    if orb.unit == "target" then return end
    
    local swirlTextures = SWIRL_SETS[currentSwirlSet] or SWIRL_SETS["bubbles"]
    
    -- Update existing galaxy textures on the health orb
    if orb.healthOrb then
        if orb.healthOrb.galaxy1 and orb.healthOrb.galaxy1.texture then
            orb.healthOrb.galaxy1.texture:SetTexture(swirlTextures[1])
        end
        if orb.healthOrb.galaxy2 and orb.healthOrb.galaxy2.texture then
            orb.healthOrb.galaxy2.texture:SetTexture(swirlTextures[2])
        end
        if orb.healthOrb.galaxy3 and orb.healthOrb.galaxy3.texture then
            orb.healthOrb.galaxy3.texture:SetTexture(swirlTextures[3])
        end
    end
end

-----------------------------------------------------------
-- 5. CUSTOM MENU LOGIC
-----------------------------------------------------------

-- Create a custom dropdown function that adds dungeon options to the standard menu
local function CustomUnitMenu(self, unit)
    -- First determine which menu to use
    local dropdown = nil
    
    if unit == "player" then
        dropdown = PlayerFrameDropDown
    elseif unit == "pet" then
        dropdown = PetFrameDropDown
    elseif unit:match("^party%d$") then
        dropdown = _G["PartyMemberFrame" .. unit:match("%d+") .. "DropDown"]
        if not dropdown then dropdown = PartyMemberFrame1DropDown end
    elseif unit:match("^raid%d+$") then
        dropdown = _G["RaidUnitFrameDropDown"]
    elseif unit:match("^target$") then
        dropdown = TargetFrameDropDown
    elseif unit:match("^focus$") then
        dropdown = _G["FocusFrameDropDown"]
    else
        dropdown = PartyMemberFrame1DropDown
    end

    if dropdown == nil then
        dropdown = PartyMemberFrame1DropDown
    end
    
    -- Set the unit for the dropdown
    if dropdown then
        dropdown.unit = unit
        
        -- Show the standard menu
        ToggleDropDownMenu(1, nil, dropdown, self, 0, 0)
        
        -- Check if we're in a dungeon group from LFG
        local inLFG = IsInLFGDungeon and IsInLFGDungeon() or (IsInGroup(LE_PARTY_CATEGORY_INSTANCE) or false)
        if inLFG then
            -- Add our custom options on next frame
            C_Timer.After(0, function()
                -- Add a separator
                if UIDropDownMenu_AddSeparator then
                    UIDropDownMenu_AddSeparator()
                end
                
                -- Add "Teleport In/Out of Dungeon" option
                local info = UIDropDownMenu_CreateInfo()
                if IsInLFGDungeon and IsInLFGDungeon() then
                    info.text = "Teleport Out of Dungeon"
                    info.func = function() 
                        if LFGTeleport then
                            LFGTeleport(true) -- true = teleport out
                        end
                    end
                else
                    info.text = "Teleport to Dungeon"
                    info.func = function() 
                        if LFGTeleport then
                            LFGTeleport(false) -- false = teleport in
                        end
                    end
                end
                info.notCheckable = true
                UIDropDownMenu_AddButton(info)
                
                -- Add "Leave Dungeon Group" option
                info = UIDropDownMenu_CreateInfo()
                info.text = "Leave Instance Group"
                info.func = function() 
                    if LeaveInstanceGroup then
                        LeaveInstanceGroup()
                    elseif C_LFGInfo and C_LFGInfo.LeaveInstanceGroup then
                        C_LFGInfo.LeaveInstanceGroup()
                    end
                end
                info.notCheckable = true
                UIDropDownMenu_AddButton(info)
            end)
        end
    end
end

-- Creates and sets up the unit dropdown menu
local function SetupUnitMenu(frame, unit)
    -- Skip if this is a non-unit frame
    if not unit then return end
    
    frame:RegisterForClicks("AnyUp")
    frame:SetAttribute("*type2", "menu")
    
    -- Use our custom menu function
    frame.menu = function(self)
        CustomUnitMenu(self, frame.unit)
    end
    
    SecureUnitButton_OnLoad(frame, unit, frame.menu)
end

-----------------------------------------------------------
-- 6. ORB FRAME CONSTRUCTION
-----------------------------------------------------------

local images = "Interface\\AddOns\\RCPartyOrbs\\images\\"
local fonts = "Interface\\AddOns\\RCPartyOrbs\\fonts\\"

-- Creates a rotating galaxy/swirl texture (RCDiabloOrbs style)
local function CreateGalaxy(orb, file, duration, size, x, y)
    local galaxy = CreateFrame("Frame", nil, orb)
    galaxy:SetHeight(size)
    galaxy:SetWidth(size)
    galaxy:SetPoint("CENTER", x, y)
    galaxy:SetFrameLevel(4)
    
    local galTex = galaxy:CreateTexture()
    galTex:SetAllPoints(galaxy)
    galTex:SetTexture(file)
    galTex:SetBlendMode("ADD")
    galTex:SetAlpha(0.6)
    galaxy.texture = galTex
    
    local animGroup = galaxy:CreateAnimationGroup()
    galaxy.animGroup = animGroup
    
    local anim1 = animGroup:CreateAnimation("Rotation")
    anim1:SetDegrees(360)
    anim1:SetDuration(duration)
    galaxy.animGroup.anim1 = anim1
    
    animGroup:Play()
    animGroup:SetLooping("REPEAT")
    
    return galaxy
end

-- Create a single orb (health OR mana) - RCDiabloOrbs style
local function CreateSingleOrb(parent, name, size, fillTexture, galaxyTextures, skipGalaxies)
    local orb = CreateFrame("Frame", name, parent)
    
    orb:SetHeight(size)
    orb:SetWidth(size)
    orb:SetFrameStrata("LOW")
    
    -- Create background gloss (top layer)
    orb.bg = orb:CreateTexture(nil, "OVERLAY")
    orb.bg:SetTexture(images .. "orb_gloss3.tga")
    orb.bg:SetAllPoints(orb)
    
    -- Create the fill texture
    orb.filling = orb:CreateTexture(nil, "BACKGROUND")
    orb.filling:SetTexture(images .. fillTexture)
    orb.filling:SetPoint("BOTTOMLEFT", 3, 3)
    orb.filling:SetWidth(size - 6)
    orb.filling:SetHeight(size - 6)
    orb.filling:SetAlpha(1)
    
    -- Create gloss overlay holder (RCDiabloOrbs style)
    local orbGlossHolder = CreateFrame("Frame", nil, orb)
    orbGlossHolder:SetAllPoints(orb)
    orbGlossHolder:SetFrameStrata("BACKGROUND")
    local orbGlossOverlay = orbGlossHolder:CreateTexture(nil, "BACKGROUND")
    orbGlossOverlay:SetTexture(images .. "orb_gloss3.tga")
    orbGlossOverlay:SetAllPoints(orbGlossHolder)
    
    -- Create rotating galaxy swirls (only for health orb, not mana)
    if not skipGalaxies and galaxyTextures then
        local newRand = math.random(20, 50)
        orb.galaxy1 = CreateGalaxy(orb, galaxyTextures[1], newRand, size - 5, 0, 0)
        newRand = math.random(30, 70)
        orb.galaxy2 = CreateGalaxy(orb, galaxyTextures[2], newRand, size - 5, 0, 0)
        newRand = math.random(18, 27)
        orb.galaxy3 = CreateGalaxy(orb, galaxyTextures[3], newRand, size - 5, 0, 0)
    end
    
    orb:Show()
    return orb
end

-- Create a stacked orb unit frame (mana orb larger, health orb centered on top)
local function CreateStackedOrbFrame(unit, index)
    local baseSize = RCPartyOrbsDB.orbSize or ORB_SIZE
    
    -- Target orb uses its own scale setting
    if unit == "target" then
        local targetScale = RCPartyOrbsDB.targetOrbScale or 75
        baseSize = baseSize * (targetScale / 100)
    end
    
    local manaSize = baseSize * 1.3  -- Mana orb is 30% BIGGER than health
    local healthSize = baseSize  -- Health orb is base size
    
    local swirlTextures = SWIRL_SETS[currentSwirlSet] or SWIRL_SETS["bubbles"]
    
    -- Create main container (clickable for unit interaction)
    local container = CreateFrame("Button", "RCPartyOrbFrame"..index, UIParent, "SecureUnitButtonTemplate")
    container:SetSize(manaSize, manaSize)  -- Container is size of mana orb
    container:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
    container:SetFrameStrata("BACKGROUND")
    container:SetAttribute("unit", unit)
    container:RegisterForClicks("AnyUp")
    container.unit = unit
    SecureUnitButton_OnLoad(container, unit)
    
    -- Get player's spells
    local dispelSpell = GetPlayerDispelSpell()
    local purgeSpell = GetPlayerPurgeSpell()
    local combatRez = GetPlayerRezSpell()
    
    -- Store spells for reference
    container.dispelSpell = dispelSpell
    container.purgeSpell = purgeSpell
    container.combatRez = combatRez
    
    -- Build the macro for dispel/rez using @unit syntax (like Decursive does)
    -- Format: /cast [@unit, help, nodead] Dispel; [@unit, help, dead, combat] BattleRez; [@unit, harm] Purge
    -- This explicitly targets the frame's unit regardless of current target
    local macroParts = {}
    
    -- Add dispel for living friendly targets
    if dispelSpell then
        table.insert(macroParts, string.format("[@%s,help,nodead] %s", unit, dispelSpell))
    end
    
    -- Add battle rez for dead friendly targets (combat only)
    if combatRez then
        table.insert(macroParts, string.format("[@%s,help,dead,combat] %s", unit, combatRez))
    end
    
    -- Add purge for enemy targets (target orb)
    if purgeSpell then
        table.insert(macroParts, string.format("[@%s,harm,nodead] %s", unit, purgeSpell))
    end
    
    -- Left-click always targets the unit
    container:SetAttribute("type1", "target")
    
    -- Set up ALL modifier attributes - the active one casts, others just target
    -- This allows changing the modifier without requiring /reload
    local macroText = ""
    if #macroParts > 0 then
        macroText = "/cast " .. table.concat(macroParts, "; ")
    end
    
    local currentMod = RCPartyOrbsDB.clickModifier or "shift"
    
    -- Set each modifier - active one gets macro, others get target
    for _, mod in ipairs({"shift", "alt", "ctrl"}) do
        if mod == currentMod and macroText ~= "" then
            container:SetAttribute(mod .. "-type1", "macro")
            container:SetAttribute(mod .. "-macrotext1", macroText)
        else
            container:SetAttribute(mod .. "-type1", "target")
            container:SetAttribute(mod .. "-macrotext1", nil)
        end
    end
    
    -- Store the macro text for later updates
    container.castMacroText = macroText
    
    if debugMode and macroText ~= "" then
        print("|cFF00FF00RCPartyOrbs:|r Macro for " .. unit .. " (" .. currentMod .. "+click): " .. macroText)
    end
    
    -- Right-click opens unit menu
    container:SetAttribute("type2", "togglemenu")
    
    -- Make container movable (when frames are unlocked)
    container:SetMovable(true)
    container:EnableMouse(true)
    container:RegisterForDrag("LeftButton")
    container:SetScript("OnDragStart", function(self)
        if RCPartyOrbsDB.framesLocked then
            -- Frames are locked, show message
            print("RC Party Orbs: Frames are locked. Click 'Unlock Frames' in the menu to move orbs.")
            return
        end
        
        -- Save all positions before moving for undo
        if not RCPartyOrbsDB.lastOrbPositions then RCPartyOrbsDB.lastOrbPositions = {} end
        for i = 1, 6 do
            if orbFrames and orbFrames[i] then
                local pt, rel, relPt, x, y = orbFrames[i]:GetPoint()
                if pt then
                    RCPartyOrbsDB.lastOrbPositions[orbFrames[i].unit] = {
                        point = pt,
                        relativeTo = rel and rel:GetName() or "UIParent",
                        relativePoint = relPt,
                        x = x,
                        y = y
                    }
                end
            end
        end
        
        if IsShiftKeyDown() then
            -- Shift+drag: move individual orb
            self:StartMoving()
            self.movingIndividual = true
        else
            -- Normal drag: move all orbs together as a group
            -- First, calculate offsets of all orbs relative to this orb
            local myX, myY = self:GetCenter()
            if not myX or not myY then return end
            
            self.groupOffsets = {}
            for i = 1, 6 do
                if orbFrames and orbFrames[i] then
                    local cx, cy = orbFrames[i]:GetCenter()
                    if cx and cy then
                        self.groupOffsets[i] = {x = cx - myX, y = cy - myY}
                    end
                end
            end
            
            -- Start moving this orb (we'll update others in OnUpdate)
            self:StartMoving()
            self.movingGroup = true
            self.groupLeader = self  -- Track which orb is being dragged
            
            -- Set up OnUpdate to move all orbs together
            self:SetScript("OnUpdate", function(frame)
                if frame.movingGroup and frame.groupOffsets then
                    local cx, cy = frame:GetCenter()
                    if cx and cy then
                        for i = 1, 6 do
                            if orbFrames and orbFrames[i] and orbFrames[i] ~= frame and frame.groupOffsets[i] then
                                orbFrames[i]:ClearAllPoints()
                                orbFrames[i]:SetPoint("CENTER", UIParent, "BOTTOMLEFT", cx + frame.groupOffsets[i].x, cy + frame.groupOffsets[i].y)
                            end
                        end
                    end
                end
            end)
        end
    end)
    container:SetScript("OnDragStop", function(self)
        if self.movingIndividual then
            self:StopMovingOrSizing()
            self.movingIndividual = false
            -- Save position for this specific unit
            local point, relativeTo, relativePoint, xOfs, yOfs = self:GetPoint()
            if not RCPartyOrbsDB.orbPositions then RCPartyOrbsDB.orbPositions = {} end
            RCPartyOrbsDB.orbPositions[unit] = {
                point = point,
                relativeTo = relativeTo and relativeTo:GetName() or "UIParent",
                relativePoint = relativePoint,
                x = xOfs,
                y = yOfs
            }
        elseif self.movingGroup then
            self:StopMovingOrSizing()
            self:SetScript("OnUpdate", nil)  -- Remove OnUpdate
            self.movingGroup = false
            self.groupOffsets = nil
            
            -- Save all orb positions
            if not RCPartyOrbsDB.orbPositions then RCPartyOrbsDB.orbPositions = {} end
            for i = 1, 6 do
                if orbFrames[i] then
                    local point, relativeTo, relativePoint, xOfs, yOfs = orbFrames[i]:GetPoint()
                    RCPartyOrbsDB.orbPositions[orbFrames[i].unit] = {
                        point = point,
                        relativeTo = relativeTo and relativeTo:GetName() or "UIParent",
                        relativePoint = relativePoint,
                        x = xOfs,
                        y = yOfs
                    }
                end
            end
        end
    end)
    
    -- Tooltip
    container:SetScript("OnEnter", function()
        GameTooltip_SetDefaultAnchor(GameTooltip, UIParent)
        GameTooltip:SetUnit(unit)
        GameTooltip:Show()
    end)
    container:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
    
    -- Create MANA orb FIRST (LARGER, background, NO galaxies)
    container.manaOrb = CreateSingleOrb(container, "RCPartyManaOrb"..index, manaSize, "MDO_orb_filling8.tga", swirlTextures, true)
    container.manaOrb:SetPoint("CENTER", container, "CENTER", 0, 0)
    
    -- Create HEALTH orb (SMALLER, centered on mana orb, on top, WITH galaxies)
    container.healthOrb = CreateSingleOrb(container, "RCPartyHealthOrb"..index, healthSize, "MDO_orb_filling6.tga", swirlTextures, false)
    container.healthOrb:SetPoint("CENTER", container.manaOrb, "CENTER", 0, 0)
    container.healthOrb:SetFrameLevel(container.manaOrb:GetFrameLevel() + 5)  -- Health on top of mana
    
    -- Set initial colors
    local r, g, b = GetClassColor(unit)
    container.healthOrb.filling:SetVertexColor(r or 0.5, g or 0.5, b or 0.5, 1)
    if container.healthOrb.galaxy1 then container.healthOrb.galaxy1.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
    if container.healthOrb.galaxy2 then container.healthOrb.galaxy2.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
    if container.healthOrb.galaxy3 then container.healthOrb.galaxy3.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
    
    container.manaOrb.filling:SetVertexColor(0.0, 0.5, 1.0, 1)
    
    -- Create animated FIRE BORDER for threat (around mana orb)
    -- Create a frame for the fire border - put it BEHIND the orbs
    container.fireBorderFrame = CreateFrame("Frame", nil, container)
    container.fireBorderFrame:SetFrameLevel(container:GetFrameLevel() - 1)
    container.fireBorderFrame:SetAllPoints(container)
    
    local fireBorderSize = manaSize * 1.6  -- 60% larger than mana orb
    container.fireBorder = container.fireBorderFrame:CreateTexture(nil, "BACKGROUND")
    container.fireBorder:SetTexture(images .. "fire_frame_01.tga")
    container.fireBorder:SetSize(fireBorderSize, fireBorderSize)
    container.fireBorder:SetPoint("CENTER", container.manaOrb, "CENTER", 0, 0)
    container.fireBorder:SetVertexColor(1, 0, 0, 1)  -- Red for high threat
    container.fireBorder:Hide()  -- Hidden by default, shown on threat
    
    -- Animation state for fire border
    container.fireElapsed = 0
    container.fireCurrentFrame = 1
    
    -- Create font holder for ALL text (on top of orbs but below Blizzard UI)
    container.fontHolder = CreateFrame("Frame", nil, container)
    container.fontHolder:SetFrameStrata("LOW")
    container.fontHolder:SetFrameLevel(container:GetFrameLevel() + 50)
    container.fontHolder:SetAllPoints(container)
    
    -- HEALTH PERCENTAGE (large, top of health orb) - "100%"
    container.healthPct = container.fontHolder:CreateFontString(nil, "OVERLAY")
    container.healthPct:SetFont(fonts .. "crash-a-like.ttf", 32, "THINOUTLINE")
    container.healthPct:SetPoint("CENTER", container.healthOrb, "CENTER", 0, 20)
    container.healthPct:SetText("100%")
    container.healthPct:SetTextColor(1, 0.82, 0, 1)  -- Gold color
    
    -- HEALTH VALUE (medium, middle of health orb) - "9.1K"
    container.healthVal = container.fontHolder:CreateFontString(nil, "OVERLAY")
    container.healthVal:SetFont(fonts .. "crash-a-like.ttf", 18, "THINOUTLINE")
    container.healthVal:SetPoint("CENTER", container.healthOrb, "CENTER", 0, -5)
    container.healthVal:SetText("9.1K")
    container.healthVal:SetTextColor(0.7, 0.7, 0.9, 1)  -- Light blue-gray
    
    -- MANA VALUE (at BOTTOM of mana orb) - "980"
    container.manaVal = container.fontHolder:CreateFontString(nil, "OVERLAY")
    container.manaVal:SetFont(fonts .. "crash-a-like.ttf", 14, "THINOUTLINE")
    container.manaVal:SetPoint("BOTTOM", container.manaOrb, "BOTTOM", 0, 10)
    container.manaVal:SetText("980")
    container.manaVal:SetTextColor(0.4, 0.7, 1.0, 1)  -- Blue for mana
    
    -- NAMEPLATE (above mana orb) - Diablo 3 style banner
    local nameplateWidth = manaSize  -- Same width as mana orb
    local nameplateHeight = manaSize * 0.25
    
    -- Nameplate background/border
    container.nameplate = container.fontHolder:CreateTexture(nil, "ARTWORK")
    container.nameplate:SetTexture(images .. "d3_nameplate_border.tga")
    container.nameplate:SetSize(nameplateWidth, nameplateHeight)
    container.nameplate:SetPoint("BOTTOM", container.manaOrb, "TOP", 0, -8)
    container.nameplate:SetVertexColor(0.1, 0.1, 0.3, 1)  -- Deep blue
    
    -- Nameplate glow (matches nameplate size)
    container.nameplateGlow = container.fontHolder:CreateTexture(nil, "BACKGROUND")
    container.nameplateGlow:SetTexture(images .. "d3_nameplate_border_glow.tga")
    container.nameplateGlow:SetSize(nameplateWidth, nameplateHeight)
    container.nameplateGlow:SetPoint("CENTER", container.nameplate, "CENTER", 0, 0)
    container.nameplateGlow:SetVertexColor(0.1, 0.1, 0.2, 0.5)  -- Dark blue glow
    
    -- NAME TEXT (on the nameplate) - larger font
    container.nameText = container.fontHolder:CreateFontString(nil, "OVERLAY")
    container.nameText:SetFont(fonts .. "crash-a-like.ttf", 20, "OUTLINE")
    container.nameText:SetPoint("CENTER", container.nameplate, "CENTER", 0, 1)
    container.nameText:SetText("")
    container.nameText:SetTextColor(1, 1, 1, 1)  -- White text
    
    container:Show()
    return container
end

-- Create player orb (index 1)
orbFrames[1] = CreateStackedOrbFrame("player", 1)
orbFrames[1]:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 50, -50)

-- Create party orbs (index 2-5) - hidden by default
for i = 1, 4 do
    orbFrames[i+1] = CreateStackedOrbFrame("party"..i, i+1)
    orbFrames[i+1]:Hide()
end

-- Create target orb (index 6) - hidden by default
orbFrames[6] = CreateStackedOrbFrame("target", 6)
orbFrames[6]:Hide()

-- Create party target orbs (index 7-10) - these show what each party member is targeting
-- party1target, party2target, party3target, party4target
for i = 1, 4 do
    orbFrames[i+6] = CreateStackedOrbFrame("party"..i.."target", i+6)
    orbFrames[i+6]:Hide()
end

-----------------------------------------------------------
-- 7. LAYOUT AND POSITIONING
-----------------------------------------------------------

-- Position orbs based on orientation setting or saved positions
function PositionOrbs()
    local baseSize = RCPartyOrbsDB.orbSize or ORB_SIZE
    local manaSize = baseSize * 1.3  -- Match the creation size
    local spacing = RCPartyOrbsDB.orbSpacing or 10
    local isVertical = RCPartyOrbsDB.partyOrientation == "vertical"
    
    -- Player orb position (restore saved or default to top-left)
    if RCPartyOrbsDB.orbPositions and RCPartyOrbsDB.orbPositions["player"] then
        local pos = RCPartyOrbsDB.orbPositions["player"]
        local relFrame = _G[pos.relativeTo] or UIParent
        orbFrames[1]:ClearAllPoints()
        orbFrames[1]:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
    else
        orbFrames[1]:ClearAllPoints()
        orbFrames[1]:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 50, -50)
    end
    
    -- Position party orbs - check for saved positions first
    for i = 2, 5 do
        if orbFrames[i] then
            local unitName = "party" .. (i - 1)
            orbFrames[i]:ClearAllPoints()
            
            if RCPartyOrbsDB.orbPositions and RCPartyOrbsDB.orbPositions[unitName] then
                -- Restore saved position
                local pos = RCPartyOrbsDB.orbPositions[unitName]
                local relFrame = _G[pos.relativeTo] or UIParent
                orbFrames[i]:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
            else
                -- Default position relative to previous orb
                if isVertical then
                    orbFrames[i]:SetPoint("TOP", orbFrames[i-1], "BOTTOM", 0, -spacing)
                else
                    orbFrames[i]:SetPoint("LEFT", orbFrames[i-1], "RIGHT", spacing, 0)
                end
            end
        end
    end
    
    -- Calculate target orb spacing (used for both player target and party targets)
    local targetSpacing = spacing * 0.5  -- Smaller spacing between member and their target
    
    -- Position target orb (player's target) - check for saved position first
    if orbFrames[6] then
        orbFrames[6]:ClearAllPoints()
        
        if RCPartyOrbsDB.orbPositions and RCPartyOrbsDB.orbPositions["target"] then
            -- Restore saved position
            local pos = RCPartyOrbsDB.orbPositions["target"]
            local relFrame = _G[pos.relativeTo] or UIParent
            orbFrames[6]:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
        else
            -- Default position: align with player orb same way party targets align
            if isVertical then
                -- Vertical party layout: target goes to the right of player
                orbFrames[6]:SetPoint("LEFT", orbFrames[1], "RIGHT", targetSpacing, 0)
            else
                -- Horizontal party layout: target goes below player
                orbFrames[6]:SetPoint("TOP", orbFrames[1], "BOTTOM", 0, -targetSpacing)
            end
        end
    end
    
    -- Position party target orbs (index 7-10) - each anchored to its corresponding party member
    -- party1target anchors to party1, party2target to party2, etc.
    
    for i = 7, 10 do
        if orbFrames[i] then
            local partyNum = i - 6  -- 1, 2, 3, 4
            local unitName = "party" .. partyNum .. "target"
            local parentOrb = orbFrames[partyNum + 1]  -- party1 is index 2, party2 is index 3, etc.
            
            orbFrames[i]:ClearAllPoints()
            
            if RCPartyOrbsDB.orbPositions and RCPartyOrbsDB.orbPositions[unitName] then
                -- Restore saved position
                local pos = RCPartyOrbsDB.orbPositions[unitName]
                local relFrame = _G[pos.relativeTo] or UIParent
                orbFrames[i]:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
            else
                -- Default position: anchor to corresponding party member's orb
                -- If horizontal layout, target goes below; if vertical, target goes to the right
                if parentOrb then
                    if isVertical then
                        -- Vertical party layout: targets go to the right of their party member
                        orbFrames[i]:SetPoint("LEFT", parentOrb, "RIGHT", targetSpacing, 0)
                    else
                        -- Horizontal party layout: targets go below their party member
                        orbFrames[i]:SetPoint("TOP", parentOrb, "BOTTOM", 0, -targetSpacing)
                    end
                end
            end
        end
    end
end

-- Toggle party orb orientation
function RCPartyOrbs_ToggleOrientation()
    if RCPartyOrbsDB.partyOrientation == "vertical" then
        RCPartyOrbsDB.partyOrientation = "horizontal"
    else
        RCPartyOrbsDB.partyOrientation = "vertical"
    end
    PositionOrbs()
    print("RC Party Orbs: Orientation set to " .. RCPartyOrbsDB.partyOrientation)
end

-----------------------------------------------------------
-- 8. DATA UPDATE ENGINE
-----------------------------------------------------------

-- Update a single orb fill
local function UpdateOrbFill(orb, value, maxValue, r, g, b)
    if not orb or not orb.filling then return end
    
    local pct = maxValue > 0 and (value / maxValue) or 0
    -- Clamp pct to 0-1 range (can exceed 1 with temporary HP buffs)
    if pct > 1 then pct = 1 end
    if pct < 0 then pct = 0 end
    
    local size = orb:GetWidth()
    
    local fillHeight = (size - 6) * pct
    if fillHeight < 1 then fillHeight = 1 end
    
    orb.filling:SetHeight(fillHeight)
    orb.filling:SetTexCoord(0, 1, 1 - pct, 1)
    if r and g and b then
        orb.filling:SetVertexColor(r, g, b, 1)
    end
    
    -- Update galaxy alpha (pct already clamped 0-1)
    local alpha = 0.3 + (pct * 0.7)
    if orb.galaxy1 then orb.galaxy1:SetAlpha(alpha) end
    if orb.galaxy2 then orb.galaxy2:SetAlpha(alpha) end
    if orb.galaxy3 then orb.galaxy3:SetAlpha(alpha) end
end

-- Update a stacked orb frame
function UpdateOrbHealth(frame)
    if not frame then return end
    
    local unit = frame.unit
    if not unit then return end
    if not UnitExists(unit) and not testMode then return end
    
    local h, maxH, p, maxP, name, r, g, b
    local powerType = UnitPowerType(unit) or 0
    
    if testMode and testPartyData[unit] and not UnitExists(unit) then
        local data = testPartyData[unit]
        h, maxH, p, maxP, name = data.h, data.maxH, data.p, data.maxP, data.name
        r, g, b = data.r, data.g, data.b
        frame:Show()
    else
        h, maxH = UnitHealth(unit), UnitHealthMax(unit)
        p = UnitPower(unit, powerType) or 0
        maxP = UnitPowerMax(unit, powerType) or 0
        name = UnitName(unit)
        r, g, b = GetClassColor(unit)
    end
    
    local hPct = maxH > 0 and (h / maxH) or 0
    local pPct = maxP > 0 and (p / maxP) or 0
    
    -- Update health orb
    if frame.healthOrb then
        UpdateOrbFill(frame.healthOrb, h, maxH, r, g, b)
        -- Color galaxies
        if frame.healthOrb.galaxy1 then frame.healthOrb.galaxy1.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
        if frame.healthOrb.galaxy2 then frame.healthOrb.galaxy2.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
        if frame.healthOrb.galaxy3 then frame.healthOrb.galaxy3.texture:SetVertexColor(r or 1, g or 1, b or 1, 1) end
    end
    
    -- Update mana orb with power-type color (no galaxies on mana orb)
    if frame.manaOrb then
        local pr, pg, pb = 0.0, 0.5, 1.0  -- Default blue
        if powerType == 1 then pr, pg, pb = 1.0, 0.0, 0.0 end  -- Rage
        if powerType == 3 then pr, pg, pb = 1.0, 1.0, 0.0 end  -- Energy
        if powerType == 6 then pr, pg, pb = 0.0, 0.8, 1.0 end  -- Runic
        
        UpdateOrbFill(frame.manaOrb, p, maxP, pr, pg, pb)
    end
    
    -- Update text displays based on settings
    if frame.healthPct then
        if RCPartyOrbsDB.showHealthPercentage then
            frame.healthPct:SetText(math.floor(hPct * 100) .. "%")
            frame.healthPct:Show()
        else
            frame.healthPct:Hide()
        end
    end
    if frame.healthVal then
        if RCPartyOrbsDB.showHealthValue ~= false then
            frame.healthVal:SetText(FormatHealth(h))
            frame.healthVal:Show()
        else
            frame.healthVal:Hide()
        end
    end
    if frame.manaVal then
        if RCPartyOrbsDB.showManaValue ~= false then
            frame.manaVal:SetText(FormatHealth(p))
            -- Color mana text by power type
            local pr, pg, pb = 0.4, 0.7, 1.0
            if powerType == 1 then pr, pg, pb = 1.0, 0.4, 0.4 end
            if powerType == 3 then pr, pg, pb = 1.0, 1.0, 0.4 end
            frame.manaVal:SetTextColor(pr, pg, pb, 1)
            frame.manaVal:Show()
        else
            frame.manaVal:Hide()
        end
    end
    if frame.nameText then
        if RCPartyOrbsDB.showName then
            frame.nameText:SetText(name or "")
            frame.nameText:SetTextColor(1, 0.85, 0.5, 1)  -- Gold text on nameplate
            frame.nameText:Show()
            -- Also show nameplate
            if frame.nameplate then frame.nameplate:Show() end
            if frame.nameplateGlow then frame.nameplateGlow:Show() end
        else
            frame.nameText:Hide()
            -- Also hide nameplate
            if frame.nameplate then frame.nameplate:Hide() end
            if frame.nameplateGlow then frame.nameplateGlow:Hide() end
        end
    end
    
    -- Check for dispellable debuffs and update orb color
    if not testMode then
        UpdateOrbDebuffColor(frame)
    end
end

function UpdateOrbThreat(frame)
    if not frame then return end
    if not RCPartyOrbsDB.showThreatBorders then 
        if frame.fireBorder then frame.fireBorder:Hide() end
        return 
    end
    
    -- In test mode, don't update threat based on actual threat data
    -- The test mode will set colors directly
    if testMode then
        return
    end
    
    local unit = frame.unit
    if not unit or not UnitExists(unit) then 
        if frame.fireBorder then frame.fireBorder:Hide() end
        return 
    end
    
    -- Check threat against all visible nameplates (enemies in combat)
    local highestStatus = 0
    local highestThreatPct = 0
    
    -- Helper function to check threat against a mob unit
    local function CheckThreat(mobUnit)
        if UnitExists(mobUnit) and UnitCanAttack("player", mobUnit) and UnitAffectingCombat(mobUnit) then
            local isTanking, status, threatPct = UnitDetailedThreatSituation(unit, mobUnit)
            if status and status > highestStatus then
                highestStatus = status
            end
            if threatPct and threatPct > highestThreatPct then
                highestThreatPct = threatPct
            end
        end
    end
    
    -- Check against current target
    CheckThreat("target")
    
    -- Check against focus
    CheckThreat("focus")
    
    -- Check against target's target (useful if targeting a party member)
    CheckThreat("targettarget")
    
    -- Check against boss frames (1-5)
    for i = 1, 5 do
        CheckThreat("boss" .. i)
    end
    
    -- Check against arena frames (1-5) 
    for i = 1, 5 do
        CheckThreat("arena" .. i)
    end
    
    -- Check against all nameplate units (enemies in combat range)
    for i = 1, 40 do
        CheckThreat("nameplate" .. i)
    end
    
    if frame.fireBorder then
        if highestStatus >= 2 then
            -- Has aggro (tanking) - RED
            local color = RCPartyOrbsDB.fireBorderColor or {r=1, g=0, b=0, a=1}
            frame.fireBorder:SetVertexColor(color.r, color.g, color.b, color.a or 1)
            frame.fireBorder:Show()
        elseif highestThreatPct >= 75 then
            -- 75% to just before aggro - YELLOW
            local color = RCPartyOrbsDB.yellowBorderColor or {r=1, g=1, b=0, a=1}
            frame.fireBorder:SetVertexColor(color.r, color.g, color.b, color.a or 1)
            frame.fireBorder:Show()
        elseif highestThreatPct >= 50 then
            -- 50% to 74% - GREEN
            local color = RCPartyOrbsDB.greenBorderColor or {r=0, g=1, b=0, a=1}
            frame.fireBorder:SetVertexColor(color.r, color.g, color.b, color.a or 1)
            frame.fireBorder:Show()
        else
            -- Below 50% threat - hide border
            frame.fireBorder:Hide()
        end
    end
end

-- Update threat border visibility based on setting
function RCPartyOrbs_UpdateThreatVisibility()
    if not RCPartyOrbsDB.showThreatBorders then
        -- Hide all fire borders
        for i = 1, 10 do
            if orbFrames[i] and orbFrames[i].fireBorder then
                orbFrames[i].fireBorder:Hide()
            end
        end
    elseif testMode then
        -- In test mode, show fire borders with their test colors
        for i = 1, 10 do
            if orbFrames[i] and orbFrames[i].fireBorder then
                orbFrames[i].fireBorder:Show()
            end
        end
    end
end

function UpdateAllOrbs()
    if not RCPartyOrbsDB or not RCPartyOrbsDB.enabled then return end
    
    for i = 1, 10 do
        if orbFrames[i] then
            UpdateOrbHealth(orbFrames[i])
            UpdateOrbThreat(orbFrames[i])
        end
    end
end

-- Dedicated test mode update
function UpdateTestModeOrbs()
    if not testMode then return end
    for i = 1, 6 do
        if orbFrames[i] then
            orbFrames[i]:Show()
            UpdateOrbHealth(orbFrames[i])
        end
    end
end

-----------------------------------------------------------
-- 9. GUI HANDLERS (Sliders & Fixes)
-----------------------------------------------------------

local menuUpdating = false

function RCPartyOrbs_InitSwirlDropdown()
    local d = RCPartyOrbsSwirlDropdown
    if not d then return end
    UIDropDownMenu_Initialize(d, function(self, level)
        local swirlNames = { ["bubbles"] = "Bubbles", ["galaxy"] = "Galaxy", ["iris"] = "Iris" }
        for k, v in pairs(swirlNames) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = v; info.value = k
            info.func = function(self)
                currentSwirlSet = self.value
                RCPartyOrbsDB.swirlSet = self.value
                UIDropDownMenu_SetText(d, self:GetText())
                for _, orb in ipairs(orbFrames) do UpdateOrbSwirls(orb) end
            end
            info.checked = (k == currentSwirlSet)
            UIDropDownMenu_AddButton(info, level)
        end
    end)
    UIDropDownMenu_SetText(d, currentSwirlSet:gsub("^%l", string.upper))
end

-- Initialize the click modifier dropdown
function RCPartyOrbs_InitModifierDropdown()
    local d = RCPartyOrbsModifierDropdown
    if not d then return end
    
    local modifierNames = {
        ["shift"] = "Shift + Click",
        ["alt"] = "Alt + Click",
        ["ctrl"] = "Ctrl + Click"
    }
    local modifierOrder = {"shift", "alt", "ctrl"}
    
    UIDropDownMenu_Initialize(d, function(self, level)
        for _, k in ipairs(modifierOrder) do
            local v = modifierNames[k]
            local info = UIDropDownMenu_CreateInfo()
            info.text = v
            info.value = k
            info.func = function(self)
                RCPartyOrbsDB.clickModifier = self.value
                UIDropDownMenu_SetText(d, self:GetText())
                -- Update all orb modifier attributes immediately
                RCPartyOrbs_UpdateOrbModifiers()
            end
            info.checked = (k == (RCPartyOrbsDB.clickModifier or "shift"))
            UIDropDownMenu_AddButton(info, level)
        end
    end)
    
    local currentMod = RCPartyOrbsDB.clickModifier or "shift"
    UIDropDownMenu_SetText(d, modifierNames[currentMod] or "Shift + Click")
end

-- Update all orb modifier attributes (called when dropdown changes)
function RCPartyOrbs_UpdateOrbModifiers()
    if InCombatLockdown() then
        print("|cFFFF0000RCPartyOrbs:|r Cannot change modifier during combat. Change will apply after combat.")
        return
    end
    
    local currentMod = RCPartyOrbsDB.clickModifier or "shift"
    local modNames = {["shift"]="Shift", ["alt"]="Alt", ["ctrl"]="Ctrl"}
    
    for _, orb in ipairs(orbFrames) do
        local container = orb
        local macroText = container.castMacroText or ""
        
        -- Update each modifier - active one gets macro, others get target
        for _, mod in ipairs({"shift", "alt", "ctrl"}) do
            if mod == currentMod and macroText ~= "" then
                container:SetAttribute(mod .. "-type1", "macro")
                container:SetAttribute(mod .. "-macrotext1", macroText)
            else
                container:SetAttribute(mod .. "-type1", "target")
                container:SetAttribute(mod .. "-macrotext1", nil)
            end
        end
    end
    
    print("|cFF00FF00RCPartyOrbs:|r Click modifier changed to " .. (modNames[currentMod] or "Shift") .. "+Click")
end

function RCPartyOrbs_UpdateMenu()
    menuUpdating = true
    RCPartyOrbs_InitSwirlDropdown()
    RCPartyOrbs_InitModifierDropdown()
    
    -- DATABASE SANITY CHECK
    local scale = RCPartyOrbsDB.scale or 1.0
    local size = RCPartyOrbsDB.orbSize or 80
    local spacing = RCPartyOrbsDB.orbSpacing or 15
    
    -- Update Sliders
    if RCPartyOrbsScaleSlider then 
        RCPartyOrbsScaleSlider:SetValue(scale * 100) 
    end
    if RCPartyOrbsSizeSlider then 
        RCPartyOrbsSizeSlider:SetValue(size) 
    end
    if RCPartyOrbsSpacingSlider then 
        RCPartyOrbsSpacingSlider:SetValue(spacing) 
    end
    
    -- Update Font Size Slider
    local fontScale = RCPartyOrbsDB.fontScale or 100
    if RCPartyOrbsFontSizeSlider then
        RCPartyOrbsFontSizeSlider:SetValue(fontScale)
    end
    if RCPartyOrbsFontSizeValueText then
        RCPartyOrbsFontSizeValueText:SetText(fontScale .. "%")
    end
    
    -- Update Checkboxes
    if ShowNameCheckButton then 
        ShowNameCheckButton:SetChecked(RCPartyOrbsDB.showName) 
    end
    if ShowHealthPercentageCheckButton then 
        ShowHealthPercentageCheckButton:SetChecked(RCPartyOrbsDB.showHealthPercentage) 
    end
    if EnableOrbsCheckButton then 
        EnableOrbsCheckButton:SetChecked(RCPartyOrbsDB.enabled) 
    end
    if ShowThreatBordersCheckButton then
        ShowThreatBordersCheckButton:SetChecked(RCPartyOrbsDB.showThreatBorders)
    end
    if ShowDebuffColorsCheckButton then
        ShowDebuffColorsCheckButton:SetChecked(RCPartyOrbsDB.showDebuffColors ~= false)
    end
    if ShowHelpOnLoginCheckButton then
        ShowHelpOnLoginCheckButton:SetChecked(RCPartyOrbsDB.showHelpOnLogin ~= false)
    end
    if TogglePlayerOrbsButton then
        if RCPartyOrbsDB.showPlayerOrbs ~= false then
            TogglePlayerOrbsButton:SetText("Hide Orbs")
        else
            TogglePlayerOrbsButton:SetText("Show Orbs")
        end
    end
    menuUpdating = false
end

function RCPartyOrbs_IsMenuUpdating()
    return menuUpdating
end

function Handle_RCPartyOrbsGUI()
    if RCPartyOrbsGUI:IsVisible() then
        RCPartyOrbsGUI:Hide()
        PlaySound(830)
        
        -- Also close the test menu
        if RCPartyOrbsTestGUI and RCPartyOrbsTestGUI:IsVisible() then
            RCPartyOrbsTestGUI:Hide()
        end
        
        -- Exit test mode when closing GUI
        if testMode then
            testPartyData = {}
            testMode = false
            
            -- Update button text
            if RCPartyOrbsTestModeButton then
                RCPartyOrbsTestModeButton:SetText("Toggle Test Mode")
            end
            
            RCPartyOrbs_UpdateVisibility()
            UpdateAllOrbs()
            print("RC Party Orbs: Test mode DISABLED")
        end
    else
        RCPartyOrbsGUI:Show()
        PlaySound(829)
        RCPartyOrbs_UpdateMenu()
    end
end

function RCPartyOrbs_SetScale(val)
    if menuUpdating then return end
    RCPartyOrbsDB.scale = (val / 100)
    for _, orb in ipairs(orbFrames) do
        orb:SetScale(RCPartyOrbsDB.scale)
    end
    -- Re-register unit watch to ensure orbs stay visible after scale change
    RCPartyOrbs_UpdateVisibility()
end

function RCPartyOrbs_SetOrbSize(val)
    if menuUpdating then return end
    RCPartyOrbsDB.orbSize = val
    RCPartyOrbs_ApplyOrbSize(val)
end

function RCPartyOrbs_ApplyOrbSize(val)
    for _, orb in ipairs(orbFrames) do
        local baseSize = val
        
        -- Target orb and party target orbs use the same scale setting
        if orb.unit == "target" or (orb.unit and orb.unit:match("party%dtarget")) then
            local targetScale = RCPartyOrbsDB.targetOrbScale or 75
            baseSize = val * (targetScale / 100)
        end
        
        local manaSize = baseSize * 1.3  -- Mana orb is 30% bigger
        local healthSize = baseSize
        local fireBorderSize = manaSize * 1.6  -- Fire border 60% larger than mana
        
        -- Resize main container
        orb:SetSize(manaSize, manaSize)
        
        -- Resize mana orb and its components
        if orb.manaOrb then
            orb.manaOrb:SetSize(manaSize, manaSize)
            if orb.manaOrb.bg then
                orb.manaOrb.bg:SetSize(manaSize, manaSize)
            end
            if orb.manaOrb.filling then
                orb.manaOrb.filling:SetSize(manaSize - 6, manaSize - 6)
            end
            if orb.manaOrb.gloss then
                orb.manaOrb.gloss:SetSize(manaSize - 4, manaSize - 4)
            end
        end
        
        -- Resize health orb and its components
        if orb.healthOrb then
            orb.healthOrb:SetSize(healthSize, healthSize)
            if orb.healthOrb.bg then
                orb.healthOrb.bg:SetSize(healthSize, healthSize)
            end
            if orb.healthOrb.filling then
                orb.healthOrb.filling:SetSize(healthSize - 6, healthSize - 6)
            end
            if orb.healthOrb.gloss then
                orb.healthOrb.gloss:SetSize(healthSize - 4, healthSize - 4)
            end
            -- Resize galaxies
            local galaxySize = healthSize - 5
            if orb.healthOrb.galaxy1 then
                orb.healthOrb.galaxy1:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy1.texture then
                    orb.healthOrb.galaxy1.texture:SetSize(galaxySize, galaxySize)
                end
            end
            if orb.healthOrb.galaxy2 then
                orb.healthOrb.galaxy2:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy2.texture then
                    orb.healthOrb.galaxy2.texture:SetSize(galaxySize, galaxySize)
                end
            end
            if orb.healthOrb.galaxy3 then
                orb.healthOrb.galaxy3:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy3.texture then
                    orb.healthOrb.galaxy3.texture:SetSize(galaxySize, galaxySize)
                end
            end
        end
        
        -- Resize fire border
        if orb.fireBorder then
            orb.fireBorder:SetSize(fireBorderSize, fireBorderSize)
        end
        
        -- Resize font sizes based on orb size AND user font scale setting
        local orbFontScale = baseSize / 100
        local userFontScale = (RCPartyOrbsDB.fontScale or 100) / 100
        local fontScale = orbFontScale * userFontScale
        
        if orb.healthPct then
            orb.healthPct:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 32 * fontScale, "THINOUTLINE")
        end
        if orb.healthVal then
            orb.healthVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 18 * fontScale, "THINOUTLINE")
        end
        if orb.manaVal then
            orb.manaVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 14 * fontScale, "THINOUTLINE")
        end
        
        -- Resize nameplate
        local nameplateWidth = manaSize  -- Same width as mana orb
        local nameplateHeight = manaSize * 0.25
        if orb.nameplate then
            orb.nameplate:SetSize(nameplateWidth, nameplateHeight)
        end
        if orb.nameplateGlow then
            orb.nameplateGlow:SetSize(nameplateWidth, nameplateHeight)
        end
        if orb.nameText then
            orb.nameText:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 20 * fontScale, "OUTLINE")
        end
        
        UpdateOrbHealth(orb)
    end
    
    -- Reposition orbs
    PositionOrbs()
end

function RCPartyOrbs_SetOrbSpacing(val)
    if menuUpdating then return end
    RCPartyOrbsDB.orbSpacing = val
    -- Reposition orbs with new spacing
    PositionOrbs()
end

function RCPartyOrbs_SetFontScale(val)
    if menuUpdating then return end
    RCPartyOrbsDB.fontScale = val
    
    -- Update all orb fonts
    local baseOrbSize = RCPartyOrbsDB.orbSize or 80
    local orbFontScale = baseOrbSize / 100
    local userFontScale = val / 100
    local fontScale = orbFontScale * userFontScale
    
    for _, orb in ipairs(orbFrames) do
        if orb.healthPct then
            orb.healthPct:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 32 * fontScale, "THINOUTLINE")
        end
        if orb.healthVal then
            orb.healthVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 18 * fontScale, "THINOUTLINE")
        end
        if orb.manaVal then
            orb.manaVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 14 * fontScale, "THINOUTLINE")
        end
        if orb.nameText then
            orb.nameText:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 20 * fontScale, "OUTLINE")
        end
    end
end

function RCPartyOrbs_ApplyTargetOrbSize(scalePercent)
    -- scalePercent is 25-150, representing percentage of normal orb size
    -- Apply to target orb (index 6) and party target orbs (indices 7-10)
    local baseOrbSize = RCPartyOrbsDB.orbSize or 80
    local baseSize = baseOrbSize * (scalePercent / 100)
    
    local manaSize = baseSize * 1.3  -- Mana orb is 30% bigger
    local healthSize = baseSize
    local fireBorderSize = manaSize * 1.6  -- Fire border 60% larger than mana
    
    -- Helper function to resize a single orb
    local function ResizeTargetOrb(orb)
        if not orb then return end
        
        -- Resize main container
        orb:SetSize(manaSize, manaSize)
        
        -- Resize mana orb and its components
        if orb.manaOrb then
            orb.manaOrb:SetSize(manaSize, manaSize)
            if orb.manaOrb.bg then
                orb.manaOrb.bg:SetSize(manaSize, manaSize)
            end
            if orb.manaOrb.filling then
                orb.manaOrb.filling:SetSize(manaSize - 6, manaSize - 6)
            end
            if orb.manaOrb.gloss then
                orb.manaOrb.gloss:SetSize(manaSize - 4, manaSize - 4)
            end
        end
        
        -- Resize health orb and its components
        if orb.healthOrb then
            orb.healthOrb:SetSize(healthSize, healthSize)
            if orb.healthOrb.bg then
                orb.healthOrb.bg:SetSize(healthSize, healthSize)
            end
            if orb.healthOrb.filling then
                orb.healthOrb.filling:SetSize(healthSize - 6, healthSize - 6)
            end
            if orb.healthOrb.gloss then
                orb.healthOrb.gloss:SetSize(healthSize - 4, healthSize - 4)
            end
            -- Resize galaxies
            local galaxySize = healthSize - 5
            if orb.healthOrb.galaxy1 then
                orb.healthOrb.galaxy1:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy1.texture then
                    orb.healthOrb.galaxy1.texture:SetSize(galaxySize, galaxySize)
                end
            end
            if orb.healthOrb.galaxy2 then
                orb.healthOrb.galaxy2:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy2.texture then
                    orb.healthOrb.galaxy2.texture:SetSize(galaxySize, galaxySize)
                end
            end
            if orb.healthOrb.galaxy3 then
                orb.healthOrb.galaxy3:SetSize(galaxySize, galaxySize)
                if orb.healthOrb.galaxy3.texture then
                    orb.healthOrb.galaxy3.texture:SetSize(galaxySize, galaxySize)
                end
            end
        end
        
        -- Resize fire border
        if orb.fireBorder then
            orb.fireBorder:SetSize(fireBorderSize, fireBorderSize)
        end
        
        -- Resize font sizes based on orb size
        local fontScale = baseSize / 100
        if orb.healthPct then
            orb.healthPct:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 32 * fontScale, "THINOUTLINE")
        end
        if orb.healthVal then
            orb.healthVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 18 * fontScale, "THINOUTLINE")
        end
        if orb.manaVal then
            orb.manaVal:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 14 * fontScale, "THINOUTLINE")
        end
        
        -- Resize nameplate
        local nameplateWidth = manaSize
        local nameplateHeight = manaSize * 0.25
        if orb.nameplate then
            orb.nameplate:SetSize(nameplateWidth, nameplateHeight)
        end
        if orb.nameplateGlow then
            orb.nameplateGlow:SetSize(nameplateWidth, nameplateHeight)
        end
        if orb.nameText then
            orb.nameText:SetFont("Interface\\AddOns\\RCPartyOrbs\\fonts\\crash-a-like.ttf", 20 * fontScale, "OUTLINE")
        end
        
        UpdateOrbHealth(orb)
    end
    
    -- Resize target orb (index 6) and party target orbs (indices 7-10)
    for i = 6, 10 do
        ResizeTargetOrb(orbFrames[i])
    end
    
    -- Reposition party target orbs since sizes changed
    PositionOrbs()
end

function RCPartyOrbs_ToggleTestMode()
    local debug = RCPartyOrbsDB and RCPartyOrbsDB.debugMode
    if debug then print("ToggleTestMode called, testMode was: " .. tostring(testMode)) end
    
    testMode = not testMode
    if debug then print("testMode is now: " .. tostring(testMode)) end
    
    if testMode then
        -- Create fake data for player and 4 party members
        local classColors = { 
            {0.78, 0.61, 0.43},  -- Warrior (player)
            {0.0, 0.44, 0.87},   -- Shaman (party1)
            {1.0, 1.0, 1.0},     -- Priest (party2)
            {0.67, 0.83, 0.45},  -- Hunter (party3)
            {1.0, 0.49, 0.04}    -- Druid (party4)
        }
        local names = {"Player", "Shaman", "Priest", "Hunter", "Druid"}
        local units = {"player", "party1", "party2", "party3", "party4"}
        
        for i = 1, 5 do
            testPartyData[units[i]] = { 
                h = math.random(3000, 10000), maxH = 10000, 
                p = math.random(1000, 5000), maxP = 5000, 
                name = names[i], 
                r = classColors[i][1], g = classColors[i][2], b = classColors[i][3] 
            }
        end
        
        -- Add target data (enemy style - red)
        testPartyData["target"] = { 
            h = math.random(5000, 15000), maxH = 15000, 
            p = math.random(2000, 8000), maxP = 8000, 
            name = "Target Mob", r = 0.9, g = 0.1, b = 0.1 
        }
        
        -- Add party target data (what each party member is targeting)
        local targetNames = {"Grunt", "Peon", "Overseer", "Warlock"}
        for i = 1, 4 do
            testPartyData["party" .. i .. "target"] = {
                h = math.random(2000, 8000), maxH = 8000,
                p = math.random(1000, 4000), maxP = 4000,
                name = targetNames[i], r = 0.8, g = 0.2, b = 0.2
            }
        end
        
        if RCPartyOrbsTestModeButton then
            RCPartyOrbsTestModeButton:SetText("Exit Test")
        end
        
        -- UNREGISTER unit watch so it doesn't hide our test orbs
        for i = 2, 10 do
            if orbFrames[i] then
                UnregisterUnitWatch(orbFrames[i])
            end
        end
        
        -- Force show ALL orbs including party, target, and party targets
        for i = 1, 10 do
            if orbFrames[i] then
                orbFrames[i]:Show()
                orbFrames[i]:SetAlpha(1.0)
            end
        end
        
        -- Show fire borders with different threat colors for testing
        -- Player (index 1) - RED (high threat/tanking)
        if orbFrames[1] and orbFrames[1].fireBorder then
            orbFrames[1].fireBorder:Show()
            orbFrames[1].fireBorder:SetVertexColor(1, 0, 0, 1)
        end
        
        -- Shaman/Party1 (index 2) - YELLOW (medium threat)
        if orbFrames[2] and orbFrames[2].fireBorder then
            orbFrames[2].fireBorder:Show()
            orbFrames[2].fireBorder:SetVertexColor(1, 1, 0, 1)
        end
        
        -- Priest/Party2 (index 3) - YELLOW (medium threat)
        if orbFrames[3] and orbFrames[3].fireBorder then
            orbFrames[3].fireBorder:Show()
            orbFrames[3].fireBorder:SetVertexColor(1, 1, 0, 1)
        end
        
        -- Hunter/Party3 (index 4) - YELLOW (medium threat)
        if orbFrames[4] and orbFrames[4].fireBorder then
            orbFrames[4].fireBorder:Show()
            orbFrames[4].fireBorder:SetVertexColor(1, 1, 0, 1)
        end
        
        -- Druid/Party4 (index 5) - GREEN (low threat)
        if orbFrames[5] and orbFrames[5].fireBorder then
            orbFrames[5].fireBorder:Show()
            orbFrames[5].fireBorder:SetVertexColor(0, 1, 0, 0.7)
        end
        
        -- Target (index 6) - no threat border
        if orbFrames[6] and orbFrames[6].fireBorder then
            orbFrames[6].fireBorder:Hide()
        end
        
        -- Position orbs properly
        PositionOrbs()
        
        -- Update all orbs with test data
        UpdateAllOrbs()
        
        print("RC Party Orbs: Test mode ENABLED - showing fake party")
    else
        testPartyData = {}
        testMode = false
        
        if RCPartyOrbsTestModeButton then
            RCPartyOrbsTestModeButton:SetText("Toggle Test Mode")
        end
        
        -- Hide fire borders
        for i = 1, 10 do
            if orbFrames[i] and orbFrames[i].fireBorder then
                orbFrames[i].fireBorder:Hide()
            end
        end
        
        RCPartyOrbs_UpdateVisibility()
        UpdateAllOrbs()
        
        print("RC Party Orbs: Test mode DISABLED")
    end
end

-- Toggle orientation between vertical and horizontal
function RCPartyOrbs_ToggleLayoutOrientation()
    if RCPartyOrbsDB.partyOrientation == "vertical" then
        RCPartyOrbsDB.partyOrientation = "horizontal"
        print("RC Party Orbs: Layout set to HORIZONTAL")
    else
        RCPartyOrbsDB.partyOrientation = "vertical"
        print("RC Party Orbs: Layout set to VERTICAL")
    end
    PositionOrbs()
    
    -- Update button text if it exists
    if RCPartyOrbsOrientationButton then
        if RCPartyOrbsDB.partyOrientation == "vertical" then
            RCPartyOrbsOrientationButton:SetText("Layout: Vertical")
        else
            RCPartyOrbsOrientationButton:SetText("Layout: Horizontal")
        end
    end
end

function RCPartyOrbs_ResetPositions()
    if not isDragging then
        -- Clear saved positions
        RCPartyOrbsDB.orbPositions = {}
        RCPartyOrbsDB.lastOrbPositions = {}
        RCPartyOrbs_RepositionOrbs()
        print("RC Party Orbs: Positions reset to default")
    end
end

-- Undo last move
function RCPartyOrbs_UndoLastMove()
    if RCPartyOrbsDB.lastOrbPositions then
        -- Swap current and last positions
        local currentPositions = {}
        for i = 1, 6 do
            if orbFrames[i] then
                local unit = orbFrames[i].unit
                local point, relativeTo, relativePoint, xOfs, yOfs = orbFrames[i]:GetPoint()
                currentPositions[unit] = {
                    point = point,
                    relativeTo = relativeTo and relativeTo:GetName() or "UIParent",
                    relativePoint = relativePoint,
                    x = xOfs,
                    y = yOfs
                }
            end
        end
        
        -- Restore last positions
        for i = 1, 6 do
            if orbFrames[i] then
                local unit = orbFrames[i].unit
                if RCPartyOrbsDB.lastOrbPositions[unit] then
                    local pos = RCPartyOrbsDB.lastOrbPositions[unit]
                    local relFrame = _G[pos.relativeTo] or UIParent
                    orbFrames[i]:ClearAllPoints()
                    orbFrames[i]:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
                    
                    -- Update saved positions
                    if not RCPartyOrbsDB.orbPositions then RCPartyOrbsDB.orbPositions = {} end
                    RCPartyOrbsDB.orbPositions[unit] = pos
                end
            end
        end
        
        -- Store current as last (so you can redo)
        RCPartyOrbsDB.lastOrbPositions = currentPositions
        
        print("RC Party Orbs: Undo - Restored previous positions")
    else
        print("RC Party Orbs: Nothing to undo")
    end
end

-- Toggle frame lock/unlock
function RCPartyOrbs_ToggleFrameLock()
    RCPartyOrbsDB.framesLocked = not RCPartyOrbsDB.framesLocked
    
    if RCPartyOrbsDB.framesLocked then
        print("RC Party Orbs: Frames LOCKED")
    else
        print("RC Party Orbs: Frames UNLOCKED - Drag orbs to reposition")
    end
    
    -- Update button text
    if LockFramesButton then
        if RCPartyOrbsDB.framesLocked then
            LockFramesButton:SetText("Unlock Frames")
        else
            LockFramesButton:SetText("Lock Frames")
        end
    end
end


-----------------------------------------------------------
-- 9. MINIMAP BUTTON LOGIC
-----------------------------------------------------------


-----------------------------------------------------------
-- 10. EVENT ENGINE-- PARTY FRAME INITIALIZATION REMOVED
-----------------------------------------------------------

local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("UNIT_HEALTH")
frame:RegisterEvent("UNIT_MAXHEALTH")
frame:RegisterEvent("UNIT_POWER_UPDATE")
frame:RegisterEvent("UNIT_THREAT_SITUATION_UPDATE")
frame:RegisterEvent("PLAYER_TARGET_CHANGED")
frame:RegisterEvent("UNIT_TARGET")  -- For ToT updates
frame:RegisterEvent("UNIT_AURA")    -- For debuff tracking
frame:RegisterEvent("PLAYER_REGEN_ENABLED")  -- For combat lockdown release
frame:RegisterEvent("NAME_PLATE_UNIT_ADDED")  -- For threat on new enemies
frame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")  -- For threat cleanup
frame:RegisterEvent("PLAYER_REGEN_DISABLED")  -- Combat started
frame:RegisterEvent("SPELLS_CHANGED")  -- For dispel spell detection
frame:RegisterEvent("PLAYER_TALENT_UPDATE")  -- For spec changes affecting dispels
frame:RegisterEvent("LEARNED_SPELL_IN_TAB")  -- For learning new dispel spells

frame:SetScript("OnEvent", function(self, event, arg1)
    if event == "ADDON_LOADED" and arg1 == "RCPartyOrbs" then
        -- STARTUP SANITIZATION
        RCPartyOrbsDB = RCPartyOrbsDB or {}
        for k, v in pairs(DB_DEFAULTS) do
            if RCPartyOrbsDB[k] == nil then RCPartyOrbsDB[k] = v end
        end
        
        currentSwirlSet = RCPartyOrbsDB.swirlSet or "bubbles"
        
        -- Scale menus to 75%
        if RCPartyOrbsGUI then
            RCPartyOrbsGUI:SetScale(0.75)
        end
        if RCPartyOrbsTestGUI then
            RCPartyOrbsTestGUI:SetScale(0.75)
        end
        
        -- Icon set to Blizzard Golden Egg
        if RCPartyOrbsMiniMapButton_Icon then
            RCPartyOrbsMiniMapButton_Icon:SetTexture("Interface\\Icons\\INV_Egg_03")
        end
        RCPartyOrbsMiniMapButton_Reposition()

        print("RC Party Orbs Initialized. Type /fpo for settings.")
        
    elseif event == "PLAYER_LOGIN" then
        -- Apply saved orb size
        local savedSize = RCPartyOrbsDB.orbSize or 80
        RCPartyOrbs_ApplyOrbSize(savedSize)
        
        -- Initialize dispel detection
        UpdatePlayerDispels()
        
        -- Delay to ensure UI is ready, then update visibility
        C_Timer.After(0.5, function()
            RCPartyOrbs_UpdateVisibility()
            UpdateAllOrbs()
        end)
        -- Additional delayed calls to catch any frames that re-show
        C_Timer.After(1.5, function()
            RCPartyOrbs_UpdateVisibility()
        end)
        C_Timer.After(3.0, function()
            RCPartyOrbs_UpdateVisibility()
        end)
        
        -- Show help menu on login if enabled
        C_Timer.After(2.0, function()
            if RCPartyOrbsDB.showHelpOnLogin and RCPartyOrbsHelpGUI then
                RCPartyOrbsHelpGUI:Show()
            end
        end)
    elseif event == "PLAYER_ENTERING_WORLD" then
        -- Delay after entering world to ensure frames stay hidden
        C_Timer.After(0.2, function()
            RCPartyOrbs_UpdateVisibility()
            UpdateAllOrbs()
        end)
        C_Timer.After(1.0, function()
            RCPartyOrbs_UpdateVisibility()
        end)
    elseif event == "GROUP_ROSTER_UPDATE" then
        RCPartyOrbs_UpdateVisibility()
        -- Party frames may re-show after roster update
        C_Timer.After(0.3, function()
            RCPartyOrbs_UpdateVisibility()
        end)
    elseif event == "PLAYER_TARGET_CHANGED" then
        -- Target changed, update visibility and orbs
        RCPartyOrbs_UpdateVisibility()
    elseif event == "UNIT_TARGET" then
        -- A unit changed their target - update visibility for party target orbs
        -- arg1 is the unit that changed targets (e.g., "party1", "party2", etc.)
        if arg1 and arg1:match("^party%d$") then
            RCPartyOrbs_UpdateVisibility()
        end
    elseif event == "UNIT_AURA" then
        -- Debuff changed, update the specific orb for debuff colors
        -- arg1 is the unit that had aura change
        if arg1 and RCPartyOrbsDB.showDebuffColors then
            -- Find the orb for this unit and update it
            for _, orb in ipairs(orbFrames) do
                if orb.unit == arg1 then
                    UpdateOrbDebuffColor(orb)
                    break
                end
            end
        end
    elseif event == "SPELLS_CHANGED" or event == "PLAYER_TALENT_UPDATE" or event == "LEARNED_SPELL_IN_TAB" then
        -- Player's spells changed, update what they can dispel
        C_Timer.After(0.5, function()
            UpdatePlayerDispels()
            UpdateAllOrbs()
        end)
    elseif event == "PLAYER_REGEN_ENABLED" then
        -- Combat ended, process any queued visibility updates
        if pendingVisibilityUpdate then
            pendingVisibilityUpdate = false
            RCPartyOrbs_UpdateVisibility()
        end
    elseif event == "NAME_PLATE_UNIT_ADDED" or event == "NAME_PLATE_UNIT_REMOVED" then
        -- Nameplate added/removed, update threat for all orbs
        -- (threat will be recalculated in UpdateAllOrbs)
    elseif event == "PLAYER_REGEN_DISABLED" then
        -- Combat started, threat updates will happen via other events
    end
    UpdateAllOrbs()
end)

-- Animation Engine (Swirls and Fire Borders)
local animFrame = CreateFrame("Frame")
animFrame:SetScript("OnUpdate", function(self, delta)
    for _, orb in ipairs(orbFrames) do
        if orb:IsShown() then
            -- Galaxies rotate automatically via AnimationGroup created in CreateGalaxy
            -- No manual rotation needed
            
            -- Animate fire border (uses frames 01-29)
            if orb.fireBorder and orb.fireBorder:IsShown() then
                orb.fireElapsed = (orb.fireElapsed or 0) + delta
                if orb.fireElapsed >= FRAME_RATE then
                    orb.fireElapsed = 0
                    orb.fireCurrentFrame = ((orb.fireCurrentFrame or 0) % FRAME_COUNT) + 1
                    local path = string.format("Interface\\AddOns\\RCPartyOrbs\\images\\fire_frame_%02d.tga", orb.fireCurrentFrame)
                    orb.fireBorder:SetTexture(path)
                end
            end
        end
    end
end)

-----------------------------------------------------------
-- 10. COLOR PICKER FUNCTION
-----------------------------------------------------------

function RCPartyOrbs_OpenColorPicker(colorType)
    -- Open color picker for different colors
    local currentColor
    local colorInfo
    
    if colorType == "fire" then
        currentColor = RCPartyOrbsDB.fireBorderColor or {r=1, g=0, b=0, a=1}
        colorInfo = "Fire Border (Red Threat)"
    elseif colorType == "green" then
        currentColor = RCPartyOrbsDB.greenBorderColor or {r=0, g=1, b=0, a=1}
        colorInfo = "Green Border (Low Threat)"
    elseif colorType == "yellow" then
        currentColor = RCPartyOrbsDB.yellowBorderColor or {r=1, g=1, b=0, a=1}
        colorInfo = "Yellow Border (Medium Threat)"
    elseif colorType == "health" then
        currentColor = RCPartyOrbsDB.healthColor or {r=1, g=0.5, b=0.5, a=1}
        colorInfo = "Health Orb"
    elseif colorType == "mana" then
        currentColor = RCPartyOrbsDB.manaColor or {r=0, g=0.5, b=1, a=1}
        colorInfo = "Mana Orb"
    else
        return
    end
    
    -- Color picker setup for Classic WoW
    ColorPickerFrame.hasOpacity = true
    ColorPickerFrame.opacity = currentColor.a or 1
    ColorPickerFrame.previousValues = {r = currentColor.r, g = currentColor.g, b = currentColor.b, a = currentColor.a or 1}
    ColorPickerFrame.func = function()
        local r, g, b = ColorPickerFrame:GetColorRGB()
        local a = 1 - OpacitySliderFrame:GetValue()
        
        -- Update the appropriate color in database
        if colorType == "fire" then
            RCPartyOrbsDB.fireBorderColor = {r=r, g=g, b=b, a=a}
            -- Apply to player orb (red = high threat)
            if orbFrames[1] and orbFrames[1].fireBorder then
                orbFrames[1].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "green" then
            RCPartyOrbsDB.greenBorderColor = {r=r, g=g, b=b, a=a}
            -- Apply to druid/party4 orb (green = low threat)
            if orbFrames[5] and orbFrames[5].fireBorder then
                orbFrames[5].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "yellow" then
            RCPartyOrbsDB.yellowBorderColor = {r=r, g=g, b=b, a=a}
            -- Apply to shaman, priest, hunter (yellow = medium threat)
            if orbFrames[2] and orbFrames[2].fireBorder then
                orbFrames[2].fireBorder:SetVertexColor(r, g, b, a)
            end
            if orbFrames[3] and orbFrames[3].fireBorder then
                orbFrames[3].fireBorder:SetVertexColor(r, g, b, a)
            end
            if orbFrames[4] and orbFrames[4].fireBorder then
                orbFrames[4].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "health" then
            RCPartyOrbsDB.healthColor = {r=r, g=g, b=b, a=a}
        elseif colorType == "mana" then
            RCPartyOrbsDB.manaColor = {r=r, g=g, b=b, a=a}
        end
        
        -- Apply the color to all relevant elements
        for _, orb in ipairs(orbFrames) do
            if colorType == "health" then
                -- Update health orb filling and galaxies
                if orb.healthOrb and orb.healthOrb.filling then
                    orb.healthOrb.filling:SetVertexColor(r, g, b, a)
                end
                if orb.healthOrb and orb.healthOrb.galaxy1 then
                    orb.healthOrb.galaxy1.texture:SetVertexColor(r, g, b, 1)
                end
                if orb.healthOrb and orb.healthOrb.galaxy2 then
                    orb.healthOrb.galaxy2.texture:SetVertexColor(r, g, b, 1)
                end
                if orb.healthOrb and orb.healthOrb.galaxy3 then
                    orb.healthOrb.galaxy3.texture:SetVertexColor(r, g, b, 1)
                end
            elseif colorType == "mana" then
                -- Update mana orb filling
                if orb.manaOrb and orb.manaOrb.filling then
                    orb.manaOrb.filling:SetVertexColor(r, g, b, a)
                end
            end
        end
    end
    
    ColorPickerFrame.cancelFunc = function()
        local r = ColorPickerFrame.previousValues.r
        local g = ColorPickerFrame.previousValues.g
        local b = ColorPickerFrame.previousValues.b
        local a = ColorPickerFrame.previousValues.a
        
        -- Restore previous color
        if colorType == "fire" then
            RCPartyOrbsDB.fireBorderColor = {r=r, g=g, b=b, a=a}
            if orbFrames[1] and orbFrames[1].fireBorder then
                orbFrames[1].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "green" then
            RCPartyOrbsDB.greenBorderColor = {r=r, g=g, b=b, a=a}
            if orbFrames[5] and orbFrames[5].fireBorder then
                orbFrames[5].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "yellow" then
            RCPartyOrbsDB.yellowBorderColor = {r=r, g=g, b=b, a=a}
            if orbFrames[2] and orbFrames[2].fireBorder then
                orbFrames[2].fireBorder:SetVertexColor(r, g, b, a)
            end
            if orbFrames[3] and orbFrames[3].fireBorder then
                orbFrames[3].fireBorder:SetVertexColor(r, g, b, a)
            end
            if orbFrames[4] and orbFrames[4].fireBorder then
                orbFrames[4].fireBorder:SetVertexColor(r, g, b, a)
            end
        elseif colorType == "health" then
            RCPartyOrbsDB.healthColor = {r=r, g=g, b=b, a=a}
        elseif colorType == "mana" then
            RCPartyOrbsDB.manaColor = {r=r, g=g, b=b, a=a}
        end
        
        -- Apply the restored color to health/mana
        for _, orb in ipairs(orbFrames) do
            if colorType == "health" then
                if orb.healthOrb and orb.healthOrb.filling then
                    orb.healthOrb.filling:SetVertexColor(r, g, b, a)
                end
                if orb.healthOrb and orb.healthOrb.galaxy1 then
                    orb.healthOrb.galaxy1.texture:SetVertexColor(r, g, b, 1)
                end
                if orb.healthOrb and orb.healthOrb.galaxy2 then
                    orb.healthOrb.galaxy2.texture:SetVertexColor(r, g, b, 1)
                end
                if orb.healthOrb and orb.healthOrb.galaxy3 then
                    orb.healthOrb.galaxy3.texture:SetVertexColor(r, g, b, 1)
                end
            elseif colorType == "mana" then
                if orb.manaOrb and orb.manaOrb.filling then
                    orb.manaOrb.filling:SetVertexColor(r, g, b, a)
                end
            end
        end
    end
    
    -- For Classic WoW, we need to set up the swatch function
    ColorPickerFrame.swatchFunc = ColorPickerFrame.func
    ColorPickerFrame.cancelFunc = ColorPickerFrame.cancelFunc
    
    -- Open the color picker
    ColorPickerFrame:SetColorRGB(currentColor.r, currentColor.g, currentColor.b)
    ColorPickerFrame:Show()
end

function RCPartyOrbs_OpenTestMenu()
    -- Open the test menu
    RCPartyOrbsTestGUI:Show()
    PlaySound(829)
    
    -- Update the test menu state
    if RCPartyOrbsTestGUI:IsVisible() then
        -- Update button text based on test mode state
        if testMode then
            RCPartyOrbsTestModeButton:SetText("Exit Test")
        else
            RCPartyOrbsTestModeButton:SetText("Toggle Test Mode")
        end
    end
end

function RCPartyOrbs_OpenVisibilityMenu()
    -- Open the visibility menu
    if RCPartyOrbsVisibilityGUI then
        RCPartyOrbsVisibilityGUI:Show()
        PlaySound(829)
    end
end

function RCPartyOrbs_OpenHelpMenu()
    -- Open the help menu
    if RCPartyOrbsHelpGUI then
        RCPartyOrbsHelpGUI:Show()
        PlaySound(829)
    end
end

function RCPartyOrbs_PopulateHelpText()
    if not RCPartyOrbsHelpText then return end
    
    local _, playerClass = UnitClass("player")
    local dispelSpell = CLASS_DISPEL_SPELLS[playerClass] or "None"
    local purgeSpell = CLASS_PURGE_SPELLS[playerClass] or "None"
    local rezData = CLASS_REZ_SPELLS[playerClass]
    local combatRez = (rezData and rezData.combat) or "None"
    
    -- Get current modifier key setting
    local modifierNames = {
        ["shift"] = "Shift",
        ["alt"] = "Alt",
        ["ctrl"] = "Ctrl"
    }
    local currentMod = RCPartyOrbsDB.clickModifier or "shift"
    local modifierDisplay = modifierNames[currentMod] or "Shift"
    
    -- Get actual dispel types from playerDispelList
    local canDispelList = ""
    for dispelType, _ in pairs(playerDispelList) do
        if canDispelList ~= "" then canDispelList = canDispelList .. ", " end
        canDispelList = canDispelList .. dispelType
    end
    if canDispelList == "" then canDispelList = "None (check /fpo dispelcheck)" end
    
    local helpText = [[|cFFFFD700RC Party Orbs|r
Diablo-style health & mana orbs for you and your party.

|cFF00FF00== FEATURES ==|r

|cFFFFFF00Orb Display:|r
• Health orb (inner) shows HP with class color
• Mana orb (outer ring) shows power
• Animated swirl effects inside orbs
• Nameplate shows unit name above orb

|cFFFFFF00Threat Borders:|r
• |cFF00FF00Green|r - Low threat (50-74%)
• |cFFFFFF00Yellow|r - Medium threat (75-99%)
• |cFFFF0000Red|r - High threat / Tanking (100%)

|cFF00FF00== DEBUFF COLORS ==|r

When a party member has a dispellable debuff:
• |cFF00CC00Poison|r - Green orb
• |cFF996600Disease|r - Brown orb
• |cFF9900CCCurse|r - Purple orb
• |cFF3399FFMagic|r - Blue orb

|cFF00FF00== ENEMY BUFFS ==|r

When your target (enemy) has a purgeable buff:
• |cFFFFCC33Stealable/Purgeable|r - Gold orb

|cFF00FF00== CLICK ACTIONS ==|r

|cFFFFFF00Left-click|r on any orb:
• Targets the unit

|cFFFFFF00]] .. modifierDisplay .. [[+Left-click|r on party orb (alive):
• Casts your dispel spell

|cFFFFFF00]] .. modifierDisplay .. [[+Left-click|r on party orb (dead, in combat):
• Casts your battle rez

|cFFFFFF00]] .. modifierDisplay .. [[+Left-click|r on target orb (enemy):
• Casts your purge spell

|cFFFFFF00Right-click|r on any orb:
• Opens unit menu (invite, whisper, etc.)

|cFF00FF00== YOUR CLASS INFO ==|r

Class: |cFFFFFF00]] .. (playerClass or "Unknown") .. [[|r
Dispel: |cFFFFFF00]] .. dispelSpell .. [[|r
Battle Rez: |cFFFFFF00]] .. combatRez .. [[|r
Can Dispel: |cFFFFFF00]] .. canDispelList .. [[|r

|cFF00FF00== MOVING ORBS ==|r

|cFFFFFF001.|r Open menu with |cFFFFFF00/fpo|r
|cFFFFFF002.|r Click |cFFFFFF00"Unlock Frames"|r button
|cFFFFFF003.|r Drag any orb to move ALL orbs together
|cFFFFFF004.|r |cFFFFFF00Shift+Drag|r to move a single orb individually
|cFFFFFF005.|r Click |cFFFFFF00"Lock Frames"|r when done
|cFFFFFF006.|r Use |cFFFFFF00"Undo Move"|r if you make a mistake
|cFFFFFF007.|r Use |cFFFFFF00"Reset Positions"|r to restore defaults

|cFF00FF00== SLASH COMMANDS ==|r

|cFFFFFF00/fpo|r - Open settings menu
|cFFFFFF00/fpo test|r - Toggle test mode (show fake party)
|cFFFFFF00/fpo debuff|r - Cycle debuff color test
|cFFFFFF00/fpo debuffoff|r - Turn off debuff test
|cFFFFFF00/fpo dispelcheck|r - Show what you can dispel
|cFFFFFF00/fpo debug|r - Toggle debug mode
|cFFFFFF00/fpo reset|r - Reset orb positions
|cFFFFFF00/fpo toggle|r - Enable/disable addon (reloads UI)

|cFF00FF00== MENU OPTIONS ==|r

• |cFFFFFF00Swirl Type|r - Change orb interior animation
• |cFFFFFF00Orb Size|r - Adjust size of all orbs
• |cFFFFFF00Layout|r - Horizontal or Vertical party arrangement
• |cFFFFFF00Show Name/Health %/Values|r - Toggle text displays
• |cFFFFFF00Show Threat Borders|r - Toggle fire border on threat
• |cFFFFFF00Show Debuff Colors|r - Toggle dispel color indicators
• |cFFFFFF00Font Size|r - Adjust text size (50%-200%)
• |cFFFFFF00Hide/Show Orbs|r - Toggle orb visibility
• |cFFFFFF00Advanced|r - Color pickers, test mode, and more

|cFF00FF00== ADVANCED MENU ==|r

• |cFFFFFF00Toggle Test Mode|r - Show fake party to preview
• |cFFFFFF00Layout|r - Switch Horizontal/Vertical arrangement
• |cFFFFFF00Health/Mana/Border Colors|r - Customize colors
• |cFFFFFF00Target Orb Size|r - Scale target orbs (25%-150%)
• |cFFFFFF00Dispel/Rez Modifier|r - Choose modifier key (Shift/Alt/Ctrl)
• |cFFFFFF00Orb Visibility...|r - Show/hide individual orbs

|cFF00FF00== YOUR SETTINGS ==|r

Current Modifier: |cFFFFFF00]] .. modifierDisplay .. [[+Click|r to Dispel/Rez
]]

    RCPartyOrbsHelpText:SetText(helpText)
end

-----------------------------------------------------------
-- 11. SLASH COMMANDS
-----------------------------------------------------------

SLASH_FIREPARTYORBS1 = "/fpo"
SLASH_FIREPARTYORBS2 = "/firepartyorbs"
SlashCmdList["FIREPARTYORBS"] = function(msg)
    local cmd = msg:lower()
    
    if cmd == "test" then 
        RCPartyOrbs_ToggleTestMode()
    elseif cmd == "reset" then 
        if not isDragging then
            RCPartyOrbs_RepositionOrbs()
        end
    elseif cmd == "debug" then 
        RCPartyOrbsDB.debugMode = not RCPartyOrbsDB.debugMode
        print("RCPartyOrbs debug mode:", RCPartyOrbsDB.debugMode and "ON" or "OFF")
        if RCPartyOrbsDB.debugMode then
            print("=== RCPartyOrbs Debug Info ===")
            print("Addon enabled:", tostring(RCPartyOrbsDB.enabled))
            print("showPlayerOrbs:", tostring(RCPartyOrbsDB.showPlayerOrbs))
            print("Test mode:", tostring(testMode))
            print("In raid:", tostring(IsInRaid()))
            print("In party:", tostring(GetNumGroupMembers() > 1))
            print("Party members:", GetNumGroupMembers() - 1)
            print("Orb count:", #orbFrames)
            for i, orb in ipairs(orbFrames) do
                print("  Orb", i, "unit:", orb.unit, "visible:", orb:IsShown())
            end
            print("=== End Debug ===")
        end
    elseif cmd == "toggle" then
        RCPartyOrbsDB.enabled = not RCPartyOrbsDB.enabled
        ReloadUI() 
    elseif cmd == "firecolor" then
        RCPartyOrbs_OpenColorPicker("fire")
    elseif cmd == "greencolor" then
        RCPartyOrbs_OpenColorPicker("green")
    elseif cmd == "yellowcolor" then
        RCPartyOrbs_OpenColorPicker("yellow")
    elseif cmd == "debuff" or cmd == "debufftest" then
        -- Cycle through debuff test colors: off -> poison -> disease -> curse -> magic -> off
        if not testDebuffMode then
            testDebuffMode = true
            testDebuffType = "Poison"
            print("RCPartyOrbs: Debuff test - POISON (Green)")
        elseif testDebuffType == "Poison" then
            testDebuffType = "Disease"
            print("RCPartyOrbs: Debuff test - DISEASE (Brown)")
        elseif testDebuffType == "Disease" then
            testDebuffType = "Curse"
            print("RCPartyOrbs: Debuff test - CURSE (Purple)")
        elseif testDebuffType == "Curse" then
            testDebuffType = "Magic"
            print("RCPartyOrbs: Debuff test - MAGIC (Blue)")
        else
            testDebuffMode = false
            testDebuffType = nil
            print("RCPartyOrbs: Debuff test OFF")
        end
        UpdateAllOrbs()
    elseif cmd == "debuff off" or cmd == "debuffoff" then
        testDebuffMode = false
        testDebuffType = nil
        print("RCPartyOrbs: Debuff test OFF")
        UpdateAllOrbs()
    elseif cmd == "dispelcheck" or cmd == "dispel" then
        -- Force refresh dispel detection and show what player can dispel
        print("|cFF00FF00=== RCPartyOrbs Spell Check ===|r")
        local _, playerClass = UnitClass("player")
        print("Player Class: |cFFFFFF00" .. playerClass .. "|r")
        
        -- Force refresh
        wipe(playerDispelList)
        debugMode = true
        UpdatePlayerDispels()
        debugMode = false
        
        -- Show dispel summary
        local types = {}
        for k, _ in pairs(playerDispelList) do table.insert(types, k) end
        if #types > 0 then
            print("|cFF00FF00Can dispel:|r " .. table.concat(types, ", "))
        else
            print("|cFFFF0000Cannot dispel anything|r - check if you have a dispel spell learned")
        end
        print("Dispel Spell: |cFFFFFF00" .. (CLASS_DISPEL_SPELLS[playerClass] or "None") .. "|r")
        
        -- Show battle rez
        local rezData = CLASS_REZ_SPELLS[playerClass]
        if rezData and rezData.combat then
            print("Battle Rez: |cFFFFFF00" .. rezData.combat .. "|r")
        else
            print("Battle Rez: |cFFFF0000None|r")
        end
        
        -- Show current modifier
        local modNames = {["shift"]="Shift", ["alt"]="Alt", ["ctrl"]="Ctrl"}
        local currentMod = RCPartyOrbsDB.clickModifier or "shift"
        print("Click Modifier: |cFFFFFF00" .. (modNames[currentMod] or "Shift") .. "+Click|r to cast")
        print("|cFF888888Left-click = Target, " .. (modNames[currentMod] or "Shift") .. "+Click = Dispel/Rez|r")
    else 
        Handle_RCPartyOrbsGUI() 
    end
end