-- =============================================================================
-- Core/TargetOfTargets.lua - Target of Targets tracking system
-- Shows who your party is targeting on arena frames, and who arena enemies are targeting on party frames
-- Rebuilt for proper arena instance detection and dynamic bracket sizing
-- =============================================================================

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

-- ============================================================================
-- MODULE INITIALIZATION
-- ============================================================================

AC.TargetOfTargets = AC.TargetOfTargets or {}
local TOT = AC.TargetOfTargets

-- ============================================================================
-- LOCAL VARIABLES
-- ============================================================================

local eventFrame = nil
local arenaIcons = {} -- Icons on arena frames showing party targets
local partyIcons = {} -- Icons on party frames showing arena targets

local MAX_ICONS = 4
local ICON_SIZE = 16

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

-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================

-- Check if currently in arena instance
-- Trust ArenaCore's own state machine over raw API (avoids race conditions on zone entry)
local function IsInArena()
    local inInstance, instanceType = IsInInstance()
    return instanceType == "arena" or (AC.MasterFrameManager and AC.MasterFrameManager.isInArena)
end

-- Get number of arena opponents (dynamic bracket sizing)
local function GetArenaOpponentCount()
    return GetNumArenaOpponents() or 3
end

local function GetArenaFrames()
    if AC.MasterFrameManager and AC.MasterFrameManager.GetFrames then
        local frames = AC.MasterFrameManager:GetFrames()
        if frames and #frames > 0 then
            return frames
        end
    end

    if AC.FrameManager and AC.FrameManager.GetFrames then
        local frames = AC.FrameManager:GetFrames()
        if frames and #frames > 0 then
            return frames
        end
    end

    if AC.MasterFrameManager and AC.MasterFrameManager.frames and #AC.MasterFrameManager.frames > 0 then
        return AC.MasterFrameManager.frames
    end

    if AC.FrameManager and AC.FrameManager.frames and #AC.FrameManager.frames > 0 then
        return AC.FrameManager.frames
    end
end

local function AddFriendlyUnit(units, seen, unit)
    if not unit or not UnitExists(unit) then return end
    if seen[unit] then return end
    seen[unit] = true
    table.insert(units, unit)
end

local function GetFrameUnit(frame)
    if not frame then return nil end
    return (frame.GetUnit and frame:GetUnit()) or frame.unit or frame.displayedUnit or (frame.GetAttribute and frame:GetAttribute("unit"))
end

local function IsUsingCompactPartyFrames()
    if EditModeManagerFrame and EditModeManagerFrame.UseRaidStylePartyFrames and EditModeManagerFrame:UseRaidStylePartyFrames() then
        return true
    end

    return C_CVar and C_CVar.GetCVarBool and C_CVar.GetCVarBool("useCompactPartyFrames") or false
end

local function FrameMatchesUnit(frame, unit)
    if not frame or not unit then return false end

    local frameUnit = GetFrameUnit(frame)
    if not frameUnit then return false end
    if frameUnit == unit then return true end

    if UnitExists(frameUnit) and UnitExists(unit) and UnitIsUnit(frameUnit, unit) then
        return true
    end

    return false
end

local function FindDefaultPartyFrame(unit)
    if PartyFrame and PartyFrame.PartyMemberFramePool and PartyFrame.PartyMemberFramePool.EnumerateActive then
        for memberFrame in PartyFrame.PartyMemberFramePool:EnumerateActive() do
            if FrameMatchesUnit(memberFrame, unit) then
                return memberFrame
            end
        end
    end
end

local function FindCompactPartyFrame(unit)
    if CompactPartyFrame and CompactPartyFrame.memberUnitFrames then
        for _, memberFrame in ipairs(CompactPartyFrame.memberUnitFrames) do
            if FrameMatchesUnit(memberFrame, unit) then
                return memberFrame
            end
        end
    end

    if CompactRaidFrameContainer and CompactRaidFrameContainer.flowFrames then
        for _, memberFrame in ipairs(CompactRaidFrameContainer.flowFrames) do
            if FrameMatchesUnit(memberFrame, unit) then
                return memberFrame
            end
        end
    end

    for i = 1, 5 do
        local memberFrame = _G["CompactPartyFrameMember" .. i] or _G["CompactRaidFrame" .. i]
        if FrameMatchesUnit(memberFrame, unit) then
            return memberFrame
        end
    end
end

local function GetPartyFrame(index)
    local unit = "party" .. index

    if IsUsingCompactPartyFrames() then
        local compactFrame = FindCompactPartyFrame(unit)
        if compactFrame then
            return compactFrame
        end
    end

    local defaultFrame = FindDefaultPartyFrame(unit)
    if defaultFrame then
        return defaultFrame
    end

    local namedPartyFrame = PartyFrame and PartyFrame["MemberFrame" .. index]
    if FrameMatchesUnit(namedPartyFrame, unit) then
        return namedPartyFrame
    end

    local legacyPartyFrame = _G["PartyMemberFrame" .. index]
    if FrameMatchesUnit(legacyPartyFrame, unit) then
        return legacyPartyFrame
    end

    return FindCompactPartyFrame(unit)
end

local function GetSoloRaidFrame()
    if CompactRaidFrame1 and (CompactRaidFrame1.unit == "player" or CompactRaidFrame1.displayedUnit == "player") and CompactRaidFrame1:IsShown() then
        return CompactRaidFrame1
    end

    if CompactPartyFrame and CompactPartyFrame.memberUnitFrames then
        for _, frame in ipairs(CompactPartyFrame.memberUnitFrames) do
            if frame and (frame.unit == "player" or frame.displayedUnit == "player") and frame:IsShown() then
                return frame
            end
        end
    end

    if CompactRaidFrameContainer and CompactRaidFrameContainer.flowFrames then
        for _, frame in ipairs(CompactRaidFrameContainer.flowFrames) do
            if frame and (frame.unit == "player" or frame.displayedUnit == "player") and frame:IsShown() then
                return frame
            end
        end
    end

    for i = 1, 5 do
        local frame = _G["CompactPartyFrameMember" .. i] or _G["CompactRaidFrame" .. i]
        if frame and (frame.unit == "player" or frame.displayedUnit == "player") and frame:IsShown() then
            return frame
        end
    end

    if CompactPartyFrame and CompactPartyFrame:IsShown() then
        return CompactPartyFrame
    end
end

local function GetPartyFrameUnit(index)
    local frame = GetPartyFrame(index)
    local unit = GetFrameUnit(frame)
    if unit and UnitExists(unit) and not UnitIsUnit(unit, "player") then
        return unit
    end

    unit = "party" .. index
    if UnitExists(unit) then
        return unit
    end
end

local function GetFriendlyTargetUnits()
    local units = {}
    local seen = {}

    AddFriendlyUnit(units, seen, "player")

    for i = 1, 5 do
        AddFriendlyUnit(units, seen, GetPartyFrameUnit(i))
    end

    return units
end

-- Get class file for a unit
local function GetUnitClassFile(unit)
    local _, classFile = UnitClass(unit)
    if classFile and type(classFile) == "string" then
        return classFile
    end
    return nil
end

local function GetComparableClass(unit)
    if UnitClassBase then
        local classFile = UnitClassBase(unit)
        if classFile then
            return classFile
        end
    end

    return GetUnitClassFile(unit)
end

local function GetSafeUnitName(unit)
    local name = UnitName(unit)
    if not name then return "" end
    if issecretvalue and issecretvalue(name) then return "" end
    return name
end

local function UnitsMatch(unit1, unit2)
    if not unit1 or not unit2 or not UnitExists(unit1) or not UnitExists(unit2) then
        return false
    end

    if UnitIsUnit(unit1, unit2) then
        return true
    end

    local class1 = GetComparableClass(unit1)
    local class2 = GetComparableClass(unit2)
    if not class1 or class1 ~= class2 then
        return false
    end

    local race1 = select(2, UnitRace(unit1)) or UnitRace(unit1)
    local race2 = select(2, UnitRace(unit2)) or UnitRace(unit2)
    if not race1 or race1 ~= race2 then
        return false
    end

    local sex1 = UnitSex(unit1)
    local sex2 = UnitSex(unit2)
    if sex1 and sex2 and sex1 ~= sex2 then
        return false
    end

    local honor1 = UnitHonorLevel(unit1)
    local honor2 = UnitHonorLevel(unit2)
    if honor1 == nil or honor2 == nil then
        return false
    end

    return honor1 == honor2
end

-- Get theme offset for target of targets
local function GetThemeOffset()
    local themeName = AC.DB and AC.DB.profile and AC.DB.profile.arenaFrames and AC.DB.profile.arenaFrames.general and AC.DB.profile.arenaFrames.general.theme
    if not themeName then return {offsetX = -5, offsetY = -2} end
    
    local AFT = AC.ArenaFrameThemes
    if not AFT or not AFT.themes or not AFT.themes[themeName] then
        return {offsetX = -5, offsetY = -2}
    end
    
    local theme = AFT.themes[themeName]
    if theme.targetOfTargets then
        return theme.targetOfTargets
    end
    
    return {offsetX = -5, offsetY = -2}
end

-- ============================================================================
-- ICON/TEXT CREATION
-- ============================================================================

-- Update icon border color based on class and setting
local function UpdateIconBorderColor(icon, classFile, settingsTable)
    if not icon or not icon.borderEdges then return end
    
    local useClassColor
    if settingsTable then
        useClassColor = settingsTable.classColoredBorders == true
    else
        local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
        if not db then return end
        
        local settings = db.alliesOnArena
        if not settings then return end
        
        useClassColor = settings.classColoredBorders == true
    end
    
    local r, g, b, a
    if useClassColor and classFile and RAID_CLASS_COLORS[classFile] then
        local classColor = RAID_CLASS_COLORS[classFile]
        r, g, b, a = classColor.r, classColor.g, classColor.b, 1
    else
        r, g, b, a = 0, 0, 0, 1 -- Black default
    end
    
    -- Update all 4 edge colors
    for _, edge in ipairs(icon.borderEdges) do
        edge:SetColorTexture(r, g, b, a)
    end
end

-- Apply class icon texture with warrior offset compensation
local function ApplyClassIconTexture(icon, classFile, settingsTable)
    if not icon or not icon.texture then return end
    
    icon.texture:SetTexture(CLASS_ICONS[classFile])
    icon.classFile = classFile -- Store for border coloring
    
    -- Warrior icon needs slight offset due to its design
    if classFile == "WARRIOR" then
        icon.texture:ClearAllPoints()
        icon.texture:SetAllPoints()
        icon.texture:SetTexCoord(0, 0.92, 0.08, 1)
    else
        icon.texture:ClearAllPoints()
        icon.texture:SetAllPoints()
        icon.texture:SetTexCoord(0, 1, 0, 1)
    end
    
    -- Update border color based on setting
    UpdateIconBorderColor(icon, classFile, settingsTable)
end

-- Create a single target icon
local function CreateTargetIcon(parent, index)
    local icon = CreateFrame("Frame", nil, parent)
    icon:SetSize(ICON_SIZE, ICON_SIZE)
    icon:Hide()
    
    -- Class icon texture
    local texture = icon:CreateTexture(nil, "ARTWORK")
    texture:SetAllPoints()
    icon.texture = texture
    icon.classFile = nil -- Will be set when texture is applied
    
    -- Pixel-perfect border (same system as arena frame class icons)
    local borderHolder = CreateFrame("Frame", nil, icon)
    borderHolder:SetFrameLevel(icon:GetFrameLevel() + 1)
    borderHolder:SetAllPoints(texture)
    
    -- Create 4 edge textures for pixel border
    local edges = {}
    for i = 1, 4 do
        local tex = borderHolder:CreateTexture(nil, "BORDER", nil, 7)
        tex:SetColorTexture(0, 0, 0, 1) -- Black border
        if tex.SetSnapToPixelGrid then tex:SetSnapToPixelGrid(true) end
        if tex.SetTexelSnappingBias then tex:SetTexelSnappingBias(0) end
        edges[i] = tex
    end
    
    -- Top edge
    edges[1]:ClearAllPoints()
    edges[1]:SetPoint("TOPLEFT", texture, "TOPLEFT", 0, 0)
    edges[1]:SetPoint("TOPRIGHT", texture, "TOPRIGHT", 0, 0)
    edges[1]:SetHeight(1)
    
    -- Right edge
    edges[2]:ClearAllPoints()
    edges[2]:SetPoint("TOPRIGHT", texture, "TOPRIGHT", 0, 0)
    edges[2]:SetPoint("BOTTOMRIGHT", texture, "BOTTOMRIGHT", 0, 0)
    edges[2]:SetWidth(1)
    
    -- Bottom edge
    edges[3]:ClearAllPoints()
    edges[3]:SetPoint("BOTTOMLEFT", texture, "BOTTOMLEFT", 0, 0)
    edges[3]:SetPoint("BOTTOMRIGHT", texture, "BOTTOMRIGHT", 0, 0)
    edges[3]:SetHeight(1)
    
    -- Left edge
    edges[4]:ClearAllPoints()
    edges[4]:SetPoint("TOPLEFT", texture, "TOPLEFT", 0, 0)
    edges[4]:SetPoint("BOTTOMLEFT", texture, "BOTTOMLEFT", 0, 0)
    edges[4]:SetWidth(1)
    
    icon.borderHolder = borderHolder
    icon.borderEdges = edges
    
    return icon
end

-- Create a single target text
local function CreateTargetText(parent)
    local text = parent:CreateFontString(nil, "OVERLAY")
    text:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", 10, "OUTLINE")
    text:SetTextColor(1, 1, 1, 1)
    text:SetJustifyH("RIGHT")
    text:SetJustifyV("TOP")
    text:Hide()
    return text
end

-- Create "+X more" indicator
local function CreateMoreIndicator(parent)
    local text = parent:CreateFontString(nil, "OVERLAY")
    text:SetFont(AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf", 9, "OUTLINE")
    text:SetTextColor(1, 0.5, 0, 1) -- Orange color
    text:SetJustifyH("RIGHT")
    text:SetJustifyV("TOP")
    text:SetText("")
    text:Hide()
    return text
end

-- ============================================================================
-- ARENA FRAME ATTACHMENT (Friendly Targets on Arena)
-- ============================================================================

function TOT:AttachToArenaFrame(frame, arenaIndex)
    if not frame then return end
    if arenaIcons[arenaIndex] and arenaIcons[arenaIndex].container then
        arenaIcons[arenaIndex].frame = frame
        arenaIcons[arenaIndex].container:SetParent(frame)
        arenaIcons[arenaIndex].container:SetFrameStrata("HIGH")
        arenaIcons[arenaIndex].container:SetFrameLevel(frame:GetFrameLevel() + 10)
        frame.TOTContainer = arenaIcons[arenaIndex].container
        self:PositionArenaContainer(arenaIndex)
        return
    end
    
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.alliesOnArena
    if not settings then return end
    
    -- Create container for icons/text
    local container = CreateFrame("Frame", nil, frame)
    container:SetFrameStrata("HIGH") -- Sit above trinkets and other arena elements
    container:SetFrameLevel(frame:GetFrameLevel() + 10)
    container:Hide()
    frame.TOTContainer = container
    
    -- Create up to 4 icons
    local icons = {}
    for i = 1, MAX_ICONS do
        local icon = CreateTargetIcon(container, i)
        icons[i] = icon
    end
    
    -- Create text for name display
    local text = CreateTargetText(container)
    
    -- Create "+X more" indicator
    local moreText = CreateMoreIndicator(container)
    
    arenaIcons[arenaIndex] = {
        container = container,
        icons = icons,
        text = text,
        moreText = moreText,
        frame = frame
    }
    
    -- Position container based on theme offsets
    self:PositionArenaContainer(arenaIndex)
end

function TOT:PositionArenaContainer(arenaIndex)
    local data = arenaIcons[arenaIndex]
    if not data or not data.frame then return end

    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end

    local settings = db.alliesOnArena
    if not settings then return end

    local themeOffset = GetThemeOffset()
    local offsetX = (settings.offsetX or 0) + (themeOffset.offsetX or -5)
    local offsetY = (settings.offsetY or 0) + (themeOffset.offsetY or -2)

    -- Simple relative positioning (no absolute screen coords)
    data.container:ClearAllPoints()
    data.container:SetPoint("TOPRIGHT", data.frame, "TOPRIGHT", offsetX, offsetY)
    data.container:SetSize(80, 20)

    -- Position icons
    self:PositionArenaIcons(arenaIndex)
end

function TOT:PositionArenaIcons(arenaIndex)
    local data = arenaIcons[arenaIndex]
    if not data then return end
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.alliesOnArena
    if not settings then return end
    
    -- Update icon positions
    local scale = (settings.iconScale or 100) / 100
    local spacing = settings.spacing or 2
    local scaledSize = ICON_SIZE * scale
    
    for i = 1, MAX_ICONS do
        local icon = data.icons[i]
        if icon then
            icon:SetSize(scaledSize, scaledSize)
            icon:ClearAllPoints()
            -- Stack horizontally leftward from right edge
            icon:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", -((i - 1) * (scaledSize + spacing)), 0)
        end
    end
    
    -- Position text
    if data.text then
        data.text:ClearAllPoints()
        data.text:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", 0, 0)
    end
    
    -- Position more indicator
    if data.moreText then
        data.moreText:ClearAllPoints()
        data.moreText:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", -((MAX_ICONS) * (scaledSize + spacing)), 0)
    end
end

function TOT:EnsureArenaAttached()
    local frames = GetArenaFrames()
    if not frames then return end

    for i, frame in ipairs(frames) do
        if frame then
            self:AttachToArenaFrame(frame, frame.id or i)
        end
    end
end

function TOT:EnsurePartyAttached()
    for i = 1, 4 do
        local partyFrame = GetPartyFrame(i)
        if partyFrame and (not partyIcons[i] or not partyIcons[i].container or partyIcons[i].frame ~= partyFrame) then
            self:AttachToPartyFrame("party" .. i)
        end
    end
end

function TOT:EnsureAttached()
    self:EnsureArenaAttached()
    self:EnsurePartyAttached()
end

-- ============================================================================
-- PARTY FRAME ATTACHMENT (Arena Targets on Party)
-- ============================================================================

function TOT:AttachToPartyFrame(partyUnit)
    if not partyUnit then return end
    
    local partyIndex = partyUnit:match("party(%d)")
    if not partyIndex then return end
    partyIndex = tonumber(partyIndex)
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.attackersOnParty
    if not settings then return end
    
    local partyFrame = GetPartyFrame(partyIndex)
    if not partyFrame then return end
    
    if partyIcons[partyIndex] and partyIcons[partyIndex].container then
        local data = partyIcons[partyIndex]
        data.frame = partyFrame
        data.container:SetParent(partyFrame)
        data.container:SetFrameStrata("HIGH")
        data.container:SetFrameLevel(partyFrame:GetFrameLevel() + 30)
        data.container:SetAlpha(1)
        data.container:SetIgnoreParentAlpha(true)
        self:PositionPartyContainer(partyIndex)
        return
    end
    
    local container = CreateFrame("Frame", nil, partyFrame)
    container:SetFrameStrata("HIGH")
    container:SetFrameLevel(partyFrame:GetFrameLevel() + 30)
    container:SetAlpha(1)
    container:SetIgnoreParentAlpha(true)
    container:Hide()
    partyIcons[partyIndex] = {
        container = container,
        icons = {},
        text = nil,
        moreText = nil,
        frame = partyFrame
    }
    
    for i = 1, MAX_ICONS do
        local icon = CreateTargetIcon(container, i)
        partyIcons[partyIndex].icons[i] = icon
    end
    
    local text = CreateTargetText(container)
    partyIcons[partyIndex].text = text
    
    local moreText = CreateMoreIndicator(container)
    partyIcons[partyIndex].moreText = moreText
    
    self:PositionPartyContainer(partyIndex)
end

function TOT:PositionPartyContainer(partyIndex)
    local data = partyIcons[partyIndex]
    if not data or not data.frame or not data.container then return end
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.attackersOnParty
    if not settings then return end
    
    local partyFrame = data.frame
    local healthBar = partyFrame.healthBar or partyFrame.healthbar or partyFrame.HealthBar
    data.container:ClearAllPoints()
    if healthBar then
        data.container:SetPoint("TOPRIGHT", healthBar, "TOPRIGHT", settings.offsetX or 0, settings.offsetY or 0)
    else
        data.container:SetPoint("TOPRIGHT", partyFrame, "TOPRIGHT", settings.offsetX or 0, settings.offsetY or 0)
    end
    
    local scale = (settings.iconScale or 100) / 100
    local spacing = settings.spacing or 2
    local scaledSize = ICON_SIZE * scale
    data.container:SetSize((scaledSize * MAX_ICONS) + (spacing * (MAX_ICONS - 1)), scaledSize)
    
    for i = 1, MAX_ICONS do
        local icon = data.icons[i]
        if icon then
            icon:SetSize(scaledSize, scaledSize)
            icon:ClearAllPoints()
            icon:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", -((i - 1) * (scaledSize + spacing)), 0)
        end
    end
    
    if data.text then
        data.text:ClearAllPoints()
        data.text:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", 0, 0)
    end
    
    if data.moreText then
        data.moreText:ClearAllPoints()
        data.moreText:SetPoint("TOPRIGHT", data.container, "TOPRIGHT", -((MAX_ICONS) * (scaledSize + spacing)), 0)
    end

    if data.testMarker then
        data.testMarker:SetAllPoints(data.container)
    end
end

function TOT:AttachToSoloRaidFrame()
    local partyFrame = GetSoloRaidFrame()
    if not partyFrame then return end

    if partyIcons[0] and partyIcons[0].container then
        local data = partyIcons[0]
        data.frame = partyFrame
        data.container:SetParent(partyFrame)
        data.container:SetFrameStrata("HIGH")
        data.container:SetFrameLevel(partyFrame:GetFrameLevel() + 30)
        data.container:SetAlpha(1)
        data.container:SetIgnoreParentAlpha(true)
        self:PositionPartyContainer(0)
        return data
    end

    local container = CreateFrame("Frame", nil, partyFrame)
    container:SetFrameStrata("HIGH")
    container:SetFrameLevel(partyFrame:GetFrameLevel() + 30)
    container:SetAlpha(1)
    container:SetIgnoreParentAlpha(true)
    container:Hide()

    partyIcons[0] = {
        container = container,
        icons = {},
        text = nil,
        moreText = nil,
        testMarker = nil,
        frame = partyFrame
    }

    for i = 1, MAX_ICONS do
        partyIcons[0].icons[i] = CreateTargetIcon(container, i)
    end

    partyIcons[0].text = CreateTargetText(container)
    partyIcons[0].moreText = CreateMoreIndicator(container)
    partyIcons[0].testMarker = container:CreateTexture(nil, "BACKGROUND")
    partyIcons[0].testMarker:SetColorTexture(1, 0.35, 0, 0.35)
    partyIcons[0].testMarker:Hide()
    self:PositionPartyContainer(0)

    return partyIcons[0]
end

function TOT:ShowPartyFrameTest()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end

    local settings = db.attackersOnParty
    if not settings or not settings.enabled then return end

    self._partyTestModeActive = true

    C_Timer.After(0.05, function()
        if not self._partyTestModeActive then return end

        local data = self:AttachToSoloRaidFrame()
        if not data then
            if DEFAULT_CHAT_FRAME then
                DEFAULT_CHAT_FRAME:AddMessage("|cffFFAA00ArenaCore:|r No visible Solo Raid Frame found. Enable Solo Raid Frame first, then click TEST again.")
            end
            self._partyTestModeActive = false
            return
        end

        local targetingThis = {
            { classFile = "DEATHKNIGHT", name = "Grippyboi" },
            { classFile = "MAGE", name = "Polyenjoyer" },
            { classFile = "HUNTER", name = "Traplord" },
        }

        self:PositionPartyContainer(0)
        if data.testMarker then
            data.testMarker:Show()
        end
        data.container:Show()

        if settings.showText then
            data.text:SetText(targetingThis[1].name)
            data.text:Show()
            for i = 1, MAX_ICONS do
                data.icons[i]:Hide()
            end
            if #targetingThis > 1 then
                data.moreText:SetText("+" .. (#targetingThis - 1))
                data.moreText:Show()
            else
                data.moreText:Hide()
            end
        else
            data.text:Hide()
            local shownCount = math.min(#targetingThis, MAX_ICONS)
            for i = 1, MAX_ICONS do
                local icon = data.icons[i]
                if i <= shownCount then
                    local info = targetingThis[i]
                    if icon and info.classFile and CLASS_ICONS[info.classFile] then
                        ApplyClassIconTexture(icon, info.classFile, db.attackersOnParty)
                        icon:Show()
                    else
                        icon:Hide()
                    end
                else
                    icon:Hide()
                end
            end

            if #targetingThis > MAX_ICONS then
                data.moreText:SetText("+" .. (#targetingThis - MAX_ICONS))
                data.moreText:Show()
            else
                data.moreText:Hide()
            end
        end
    end)
end

function TOT:HidePartyFrameTest()
    self._partyTestModeActive = false

    local data = partyIcons[0]
    if data and data.container then
        if data.testMarker then
            data.testMarker:Hide()
        end
        data.container:Hide()
    end
end

function TOT:TogglePartyFrameTest()
    if self._partyTestModeActive then
        self:HidePartyFrameTest()
    else
        self:ShowPartyFrameTest()
    end
end

function TOT:IsPartyFrameTestActive()
    return self._partyTestModeActive == true
end

-- ============================================================================
-- TARGET UPDATE LOGIC
-- ============================================================================

function TOT:UpdateArenaTargets()
    if self._testModeActive then return end
    if not IsInArena() then return end
    self:EnsureArenaAttached()

    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    local settings = db.alliesOnArena
    if not settings or not settings.enabled then
        for _, data in pairs(arenaIcons) do
            if data.container then data.container:Hide() end
        end
        return
    end

    local partyTargets = {}

    local partyUnits = GetFriendlyTargetUnits()
    for _, partyUnit in ipairs(partyUnits) do
        if UnitExists(partyUnit) then
            local targetUnit = (partyUnit == "player") and "target" or (partyUnit .. "target")
            for i = 1, GetArenaOpponentCount() do
                local arenaUnit = "arena" .. i
                if UnitsMatch(targetUnit, arenaUnit) then
                    partyTargets[arenaUnit] = partyTargets[arenaUnit] or {}
                    table.insert(partyTargets[arenaUnit], {
                        classFile = GetUnitClassFile(partyUnit),
                        name = GetSafeUnitName(partyUnit),
                    })
                    break
                end
            end
        end
    end

    local frames = GetArenaFrames()
    if not frames then return end

    for i, frame in ipairs(frames) do
        if frame and frame.unit then
            local arenaUnit = frame.unit
            local frameID = frame.id or i
            local data = arenaIcons[frameID]
            if data then
                local targeting = partyTargets[arenaUnit] or {}

                if #targeting == 0 then
                    data.container:Hide()
                else
                    local showText = settings.showText == true
                    local showIcons = settings.showIcons ~= false and not showText

                    if not showText and not showIcons then
                        data.container:Hide()
                    else
                        data.container:Show()
                        self:PositionArenaIcons(frameID)

                        if showText then
                            local displayName = targeting[1] and targeting[1].name or ""
                            data.text:SetText(displayName)
                            data.text:SetShown(displayName ~= "")
                            for j = 1, MAX_ICONS do
                                data.icons[j]:Hide()
                            end
                            if #targeting > 1 then
                                data.moreText:SetText("+" .. (#targeting - 1))
                                data.moreText:Show()
                            else
                                data.moreText:Hide()
                            end
                        else
                            data.text:Hide()

                            local shownCount = math.min(#targeting, MAX_ICONS)
                            for j = 1, MAX_ICONS do
                                local icon = data.icons[j]
                                if j <= shownCount then
                                    local info = targeting[j]
                                    if info.classFile and CLASS_ICONS[info.classFile] then
                                        ApplyClassIconTexture(icon, info.classFile)
                                        UpdateIconBorderColor(icon, info.classFile)
                                        icon:Show()
                                    else
                                        icon:Hide()
                                    end
                                else
                                    icon:Hide()
                                end
                            end

                            if #targeting > MAX_ICONS then
                                data.moreText:SetText("+" .. (#targeting - MAX_ICONS))
                                data.moreText:Show()
                            else
                                data.moreText:Hide()
                            end
                        end
                    end
                end
            end
        end
    end
end

function TOT:UpdatePartyTargets()
    -- Don't update if not in arena
    if not IsInArena() then return end
    self:EnsurePartyAttached()
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.attackersOnParty
    if not settings or not settings.enabled then
        -- Hide all party containers
        for i = 1, 4 do
            if partyIcons[i] and partyIcons[i].container then
                partyIcons[i].container:Hide()
            end
        end
        return
    end
    
    -- Get dynamic arena opponent count
    local numOpponents = GetArenaOpponentCount()
    
    -- Check each arena opponent's target
    local arenaTargets = {}
    
    for i = 1, numOpponents do
        local arenaUnit = "arena" .. i
        if UnitExists(arenaUnit) then
            local targetUnit = arenaUnit .. "target"
            if UnitExists(targetUnit) then
                for j = 1, 4 do
                    local partyUnit = "party" .. j
                    if UnitsMatch(targetUnit, partyUnit) then
                        table.insert(arenaTargets, {
                            arenaIndex = i,
                            partyIndex = j,
                            classFile = GetUnitClassFile(arenaUnit),
                            name = GetSafeUnitName(arenaUnit)
                        })
                        break
                    end
                end
            end
        end
    end
    
    -- Update each party frame
    for partyIdx = 1, 4 do
        local data = partyIcons[partyIdx]
        if not data then
            -- Skip this party frame
        else
            -- Find all arena opponents targeting this party member
            local targetingThis = {}
            for _, at in ipairs(arenaTargets) do
                if at.partyIndex == partyIdx then
                    table.insert(targetingThis, at)
                end
            end

            if #targetingThis == 0 then
                data.container:Hide()
            else
                local showText = settings.showText == true
                local showIcons = settings.showIcons ~= false and not showText

                if not showText and not showIcons then
                    data.container:Hide()
                else
                    data.container:Show()

                    if showText then
                        local displayName = targetingThis[1] and targetingThis[1].name or ""
                        data.text:SetText(displayName)
                        data.text:SetShown(displayName ~= "")
                        for j = 1, MAX_ICONS do
                            data.icons[j]:Hide()
                        end
                        if #targetingThis > 1 then
                            data.moreText:SetText("+" .. (#targetingThis - 1))
                            data.moreText:Show()
                        else
                            data.moreText:Hide()
                        end
                    else
                        data.text:Hide()

                        local shownCount = math.min(#targetingThis, MAX_ICONS)
                        for j = 1, MAX_ICONS do
                            if j <= shownCount then
                                local at = targetingThis[j]
                                local icon = data.icons[j]
                                if icon and at.classFile and CLASS_ICONS[at.classFile] then
                                    ApplyClassIconTexture(icon, at.classFile, db.attackersOnParty)
                                    icon:Show()
                                else
                                    icon:Hide()
                                end
                            else
                                data.icons[j]:Hide()
                            end
                        end

                        if #targetingThis > MAX_ICONS then
                            data.moreText:SetText("+" .. (#targetingThis - MAX_ICONS))
                            data.moreText:Show()
                        else
                            data.moreText:Hide()
                        end
                    end
                end
            end
        end
    end
 end

-- ============================================================================
-- EVENT HANDLING
-- ============================================================================

local function Refresh()
    TOT:EnsureAttached()
    TOT:UpdateArenaTargets()
    TOT:UpdatePartyTargets()
end

local function OnEvent(self, event, ...)
    if event == "UNIT_TARGET" then
        local unit = ...
        if unit and (unit:match("^party%d") or unit:match("^arena%d")) then
            Refresh()
        end
    elseif event == "PLAYER_TARGET_CHANGED" then
        -- Your own target changed
        Refresh()
    elseif event == "GROUP_ROSTER_UPDATE" then
        -- Solo Shuffle round transitions
        C_Timer.After(0.2, Refresh)
    elseif event == "ARENA_OPPONENT_UPDATE" then
        -- Arena opponent became visible/hidden
        C_Timer.After(0.1, Refresh)
    elseif event == "PLAYER_ENTERING_WORLD" then
        C_Timer.After(0.5, Refresh)
    end
end

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function TOT:Initialize()
    if eventFrame then return end -- Already initialized

    eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("UNIT_TARGET")
    eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
    eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
    eventFrame:RegisterEvent("ARENA_OPPONENT_UPDATE")
    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:SetScript("OnEvent", OnEvent)

    -- Attach to arena frames when they're available
    C_Timer.After(1, function()
        local frames = AC.FrameManager and AC.FrameManager:GetFrames()
        if frames then
            for i, frame in ipairs(frames) do
                if frame then
                    TOT:AttachToArenaFrame(frame, i)
                end
            end
        end

        TOT:EnsurePartyAttached()

        -- Initial update if in arena
        if IsInArena() then
            Refresh()
        end
    end)
end

function TOT:Refresh()
    if self._partyTestModeActive then
        self:ShowPartyFrameTest()
        return
    end

    if self._testModeActive then
        self:ShowTestIcons()
        return
    end

    -- Re-position existing arena containers (don't re-attach)
    for i = 1, 5 do
        self:PositionArenaContainer(i)
    end

    -- Re-position existing party containers
    for i = 1, 4 do
        self:PositionPartyContainer(i)
    end

    -- Update border colors on all existing icons
    for i = 1, 5 do
        if arenaIcons[i] and arenaIcons[i].icons then
            for _, icon in ipairs(arenaIcons[i].icons) do
                if icon.classFile then
                    UpdateIconBorderColor(icon, icon.classFile)
                end
            end
        end
    end

    for i = 1, 4 do
        if partyIcons[i] and partyIcons[i].icons then
            for _, icon in ipairs(partyIcons[i].icons) do
                if icon.classFile then
                    UpdateIconBorderColor(icon, icon.classFile)
                end
            end
        end
    end

    -- Update displays
    Refresh()
end

-- ============================================================================
-- TEST MODE
-- ============================================================================

TOT._testModeActive = false

function TOT:ShowTestIcons()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.targetOfTargets
    if not db then return end
    
    local settings = db.alliesOnArena
    if not settings or not settings.enabled then return end
    
    -- Ensure containers are attached
    local frames = AC.FrameManager and AC.FrameManager:GetFrames()
    if frames then
        for i, frame in ipairs(frames) do
            if frame and not arenaIcons[i] then
                self:AttachToArenaFrame(frame, i)
            end
        end
    end
    
    self._testModeActive = true
    
    -- Fake party targeting data for test mode preview
    local testTargets = {
        [1] = {"WARRIOR", "PALADIN"},
        [2] = {"PRIEST"},
        [3] = {},
    }
    
    -- Fake target names for test mode when showText is enabled
    local testTargetNames = {
        [1] = "Ponyboix",
        [2] = "Seasonisdead",
        [3] = "Stealthyboi",
    }
    
    for arenaIdx = 1, 3 do
        local data = arenaIcons[arenaIdx]
        if not data then
            -- Skip this arena frame
        else
            local targetingThis = testTargets[arenaIdx]
            if #targetingThis == 0 then
                data.container:Hide()
            else
                self:PositionArenaContainer(arenaIdx)
                self:PositionArenaIcons(arenaIdx)
                data.container:Show()
                
                if settings.showText then
                    -- Show text with fake target name for testing
                    local targetName = testTargetNames[arenaIdx] or "Unknown"
                    data.text:SetText(targetName)
                    data.text:Show()
                    
                    -- Hide icons
                    for i = 1, MAX_ICONS do
                        data.icons[i]:Hide()
                    end
                    data.moreText:Hide()
                else
                    -- Show icons
                    data.text:Hide()
                    
                    local shownCount = math.min(#targetingThis, MAX_ICONS)
                    for i = 1, MAX_ICONS do
                        if i <= shownCount then
                            local classFile = targetingThis[i]
                            local icon = data.icons[i]
                            if icon and classFile and CLASS_ICONS[classFile] then
                                ApplyClassIconTexture(icon, classFile)
                                UpdateIconBorderColor(icon, classFile)
                                icon:Show()
                            else
                                icon:Hide()
                            end
                        else
                            data.icons[i]:Hide()
                        end
                    end
                    
                    -- Show "+X more" if needed
                    if #targetingThis > MAX_ICONS then
                        data.moreText:SetText("+" .. (#targetingThis - MAX_ICONS))
                        data.moreText:Show()
                    else
                        data.moreText:Hide()
                    end
                end
            end
        end
    end
end

function TOT:HideTestIcons()
    self._testModeActive = false
    self:HidePartyFrameTest()
    
    for i = 1, 5 do
        local data = arenaIcons[i]
        if data and data.container then
            data.container:Hide()
        end
    end
end

-- ============================================================================
-- PUBLIC API
-- ============================================================================

AC.TargetOfTargets = TOT
