-------------------------
-- AiB Co-Tank | Mixin
-- Visuelle Update-Funktionen für den CoTank-Frame
-------------------------

local ns = AiB.CoTank.ns
local K  = ns.K

local AiB_CoTank_Mixin = {}

-- ── Settings-Getter ───────────────────────────────────────────────────────
function AiB_CoTank_Mixin:MaxShownPrivateAuras()
    return self.maxPrivateAuras or ns.SettingNumber("maxPrivateAuras", K.PRIVATE_AURAS_DEFAULT_COUNT)
end
function AiB_CoTank_Mixin:MaxShownDebuffs()
    return self.maxDebuffs or ns.SettingNumber("maxDebuffs", K.DEBUFF_DEFAULT_COUNT)
end
function AiB_CoTank_Mixin:MaxShownDefensives()
    return self.maxDefensives or ns.SettingNumber("maxDefensives", K.BIG_DEFENSIVES_DEFAULT_COUNT)
end
function AiB_CoTank_Mixin:ShowPrivateAuras()  return self:MaxShownPrivateAuras() > 0 end
function AiB_CoTank_Mixin:ShowDebuffs()       return self:MaxShownDebuffs()      > 0 end
function AiB_CoTank_Mixin:ShowDefensives()    return self:MaxShownDefensives()   > 0 end

-- ── Tot-Anzeige ───────────────────────────────────────────────────────────
function AiB_CoTank_Mixin:CreateDeadOverlay()
    self.deadOverlay = self:CreateTexture(nil, "OVERLAY")
    self.deadOverlay:SetAllPoints()
    self.deadOverlay:SetColorTexture(0.06, 0.06, 0.10, 0.85)
    self.deadOverlay:Hide()
    self.deadText = self:CreateFontString(nil, "OVERLAY")
    self.deadText:SetFont("Fonts\\FRIZQT__.TTF", 16, "OUTLINE")
    self.deadText:SetPoint("CENTER", 0, 4)
    self.deadText:SetText("|cffcc3333DEAD|r")
    self.deadText:Hide()
end

function AiB_CoTank_Mixin:UpdateDeadState()
    if ns.isTestMode then
        if self.deadOverlay then self.deadOverlay:Hide() end
        if self.deadText    then self.deadText:Hide()    end
        return
    end
    local unit   = self:GetAttribute("unit")
    local isDead = unit and UnitExists(unit) and UnitIsDeadOrGhost(unit)
    if self.deadOverlay then self.deadOverlay:SetShown(isDead) end
    if self.deadText    then self.deadText:SetShown(isDead)    end
end

-- ── Clear-Helfer ──────────────────────────────────────────────────────────
function AiB_CoTank_Mixin:ClearDebuffs()
    if not self.debuffs then return end
    for i = 1, #self.debuffs do
        self.debuffs[i]:SetAlpha(0)
        self.debuffs[i].icon:SetTexture(nil)
        self.debuffs[i].count:SetText("")
        self.debuffs[i].cd:SetCooldown(0, 0)
    end
end

function AiB_CoTank_Mixin:ClearBigDefensives()
    if not self.bigDefensives then return end
    for i = 1, #self.bigDefensives do
        self.bigDefensives[i]:SetAlpha(0)
        self.bigDefensives[i].icon:SetTexture(nil)
        self.bigDefensives[i].count:SetText("")
        self.bigDefensives[i].cd:SetCooldown(0, 0)
    end
end

-- ── Aura-Anzeige ──────────────────────────────────────────────────────────
function AiB_CoTank_Mixin:UpdateBigDefensives()
    local unit      = (ns.isTestMode and ns.FindCoTank()) or self:GetAttribute("unit")
    local isEditing = ns.IsInEditMode()
    if not self:ShowDefensives() or not unit or not UnitExists(unit) or isEditing then return end
    self:ClearBigDefensives()
    for auraIdx = 1, self:MaxShownDefensives() do
        local aura = C_UnitAuras.GetAuraDataByIndex(unit, auraIdx, K.BIG_DEFENSIVE_AURA_FILTER)
        if not aura then break end
        local f = self.bigDefensives[auraIdx]
        f.auraInstanceID = aura.auraInstanceID
        f.icon:SetTexture(aura.icon)
        local apps = tonumber(tostring(aura.applications)) or 0
        if apps > 1 and not f.stackHidden then
            f.count:SetText(apps)
            f.count:SetAlpha(1)
        else
            f.count:SetText("")
            f.count:SetAlpha(0)
        end
        f.expirationTime = aura.expirationTime or 0
        if f.cd.SetCooldownFromExpirationTime then
            local ok = pcall(f.cd.SetCooldownFromExpirationTime, f.cd, aura.expirationTime, aura.duration)
            if not ok then f.cd:SetCooldown(0, 0) end
        else
            f.cd:SetCooldown(0, 0)
        end
        f:SetAlpha(1)
    end
end

function AiB_CoTank_Mixin:ShowMockDebuffs()
    local mocks = K.MOCK_PREVIEW_DEBUFF_ICONS
    local max   = math.min(self:MaxShownDebuffs(), #mocks)
    local now   = GetTime()
    for i = 1, #self.debuffs do
        local f = self.debuffs[i]
        if i <= max then
            f.auraInstanceID = nil
            f.icon:SetTexture(mocks[i])
            local stacks = (i == 2 or i == 4) and 3 or (i == 3) and 5 or 1
            if stacks > 1 and not f.stackHidden then
                f.count:SetText(stacks)
                f.count:SetAlpha(1)
            else
                f.count:SetText("")
                f.count:SetAlpha(0)
            end
            local duration = 8 + (i * 2)
            local elapsed  = (i * 1.7) % duration
            f.cd:SetCooldown(now - elapsed, duration)
            f.expirationTime = now + (duration - elapsed)
            f:SetAlpha(1)
        else
            f:SetAlpha(0)
            f.expirationTime = 0
        end
    end
end

function AiB_CoTank_Mixin:UpdateDebuffs()
    if not self:ShowDebuffs() or ns.IsInEditMode() then return end
    if ns.isTestMode then
        self:ShowMockDebuffs()
        return
    end
    local unit = self:GetAttribute("unit")
    if not unit or not UnitExists(unit) then return end
    self:ClearDebuffs()
    local idx = 1
    for auraIdx = 1, 40 do
        if idx > self:MaxShownDebuffs() then break end
        local aura = C_UnitAuras.GetAuraDataByIndex(unit, auraIdx, K.DEBUFF_AURA_FILTER)
        if not aura then break end
        local f = self.debuffs[idx]
        f.auraInstanceID = aura.auraInstanceID
        f.icon:SetTexture(aura.icon)
        local apps = tonumber(tostring(aura.applications)) or 0
        if apps > 1 and not f.stackHidden then
            f.count:SetText(apps)
            f.count:SetAlpha(1)
        else
            f.count:SetText("")
            f.count:SetAlpha(0)
        end
        f.expirationTime = aura.expirationTime or 0
        -- expirationTime kann ein "Secret Value" sein (nicht nil, aber restricted)
        -- pcall faengt den Fehler sicher ab
        if f.cd.SetCooldownFromExpirationTime then
            local ok = pcall(f.cd.SetCooldownFromExpirationTime, f.cd, aura.expirationTime, aura.duration)
            if not ok then f.cd:SetCooldown(0, 0) end
        else
            f.cd:SetCooldown(0, 0)
        end
        f:SetAlpha(1)
        idx = idx + 1
    end
    ns.Alert:Check(unit)
end

-- ── Health / Power Bars ───────────────────────────────────────────────────
function AiB_CoTank_Mixin:UpdateHealthBarColor()
    if ns.isTestMode then
        -- Paladin-Pink als Mock-Klassenfarbe
        self.health:SetStatusBarColor(0.96 * K.HEALTH_BAR_INTENSITY, 0.55 * K.HEALTH_BAR_INTENSITY, 0.73 * K.HEALTH_BAR_INTENSITY)
        return
    end
    local unit  = self:GetAttribute("unit")
    local color
    if unit then
        local _, class = UnitClass(unit)
        if class then color = RAID_CLASS_COLORS[class] end
    end
    if color then
        self.health:SetStatusBarColor(
            color.r * K.HEALTH_BAR_INTENSITY,
            color.g * K.HEALTH_BAR_INTENSITY,
            color.b * K.HEALTH_BAR_INTENSITY)
    else
        self.health:SetStatusBarColor(0.5, 0.5, 0.5)
    end
end

function AiB_CoTank_Mixin:UpdateHealthText()
    if ns.isTestMode then
        self:SetNameText(K.MOCK_NAMES[4])
        self.hpText:SetText("65,432 | 65%")
        return
    end
    local unit = self:GetAttribute("unit")
    if not unit or not UnitExists(unit) then self.hpText:SetText(""); return end
    self:SetNameText(UnitName(unit))

    if self.hpText.HasSecretValues and type(self.hpText.HasSecretValues) == "function"
       and self.hpText:HasSecretValues() then
        self.hpText:SetToDefaults()
    end

    local unitHealth
    if AbbreviateLargeNumbers and type(AbbreviateLargeNumbers) == "function" then
        unitHealth = AbbreviateLargeNumbers(UnitHealth(unit))
    else
        unitHealth = UnitHealth(unit)
    end
    local ok, pct
    if CurveConstants and CurveConstants.ScaleTo100 then
        ok, pct = pcall(UnitHealthPercent, unit, false, CurveConstants.ScaleTo100)
    else
        ok, pct = pcall(UnitHealthPercent, unit, false, true)
    end
    if ok and pct ~= nil and unitHealth ~= nil then
        self.hpText:SetFormattedText("%s | %.0f%%", unitHealth, pct)
    elseif unitHealth then
        self.hpText:SetText(unitHealth)
    else
        self.hpText:SetText("??")
    end
end

function AiB_CoTank_Mixin:UpdateHealthBar()
    if ns.isTestMode then
        self.health:SetMinMaxValues(0, 100)
        self.health:SetValue(65)
        self:UpdateHealthText()
        return
    end
    local unit = self:GetAttribute("unit")
    if not unit or not UnitExists(unit) then return end
    self.health:SetMinMaxValues(0, UnitHealthMax(unit))
    self.health:SetValue(UnitHealth(unit))
    self:UpdateHealthText()
end

function AiB_CoTank_Mixin:UpdatePower()
    if ns.isTestMode then
        self.power:SetMinMaxValues(0, 100)
        self.power:SetValue(45)
        self.power:SetStatusBarColor(0.0, 0.52, 1.0)
        return
    end
    local unit = self:GetAttribute("unit")
    if not unit or not UnitExists(unit) then return end
    local _, powerToken = UnitPowerType(unit)
    self.power:SetMinMaxValues(0, UnitPowerMax(unit))
    self.power:SetValue(UnitPower(unit))
    local color = PowerBarColor[powerToken] or PowerBarColor["MANA"]
    self.power:SetStatusBarColor(color.r, color.g, color.b)
end

-- ── Layout-Helfer ─────────────────────────────────────────────────────────
function AiB_CoTank_Mixin:UpdateIconList(frameList, maxCount)
    if InCombatLockdown() or not frameList then return end
    for i, f in ipairs(frameList) do f:SetShown(i <= maxCount) end
end

function AiB_CoTank_Mixin:UpdateAndRefreshAuraLayout(newVal)
    local changed = self:MaxShownPrivateAuras() ~= newVal
    self.maxPrivateAuras = newVal; self:RefreshAuraLayout(); return changed
end
function AiB_CoTank_Mixin:UpdateAndRefreshDebuffLayout(newVal)
    local changed = self:MaxShownDebuffs() ~= newVal
    self.maxDebuffs = newVal; self:RefreshDebuffLayout(); return changed
end
function AiB_CoTank_Mixin:UpdateAndRefreshDefensiveLayout(newVal)
    local changed = self:MaxShownDefensives() ~= newVal
    self.maxDefensives = newVal; self:RefreshDefensiveLayout(); return changed
end

function AiB_CoTank_Mixin:RefreshAuraLayout()      self:UpdateIconList(self.anchorFrames,  self:MaxShownPrivateAuras()) end
function AiB_CoTank_Mixin:RefreshDebuffLayout()    self:UpdateIconList(self.debuffs,       self:MaxShownDebuffs())      end
function AiB_CoTank_Mixin:RefreshDefensiveLayout() self:UpdateIconList(self.bigDefensives, self:MaxShownDefensives())   end

-- ── Icon-Einstellungen ────────────────────────────────────────────────────
function ns.ApplyIconSettings(f)
    local db = ns.DB()
    local size = ns.SettingNumber("stackCountSize", K.STACK_COUNT_DEFAULT_SIZE)
    f.stackHidden = (size <= 0)
    f.count:SetFont("Fonts\\FRIZQT__.TTF", math.max(size, 1), "OUTLINE")
    local posId = db.stackCountPos or "BR_IN"
    local pos
    for _, p in ipairs(K.STACK_COUNT_POSITIONS) do
        if p.id == posId then pos = p; break end
    end
    pos = pos or K.STACK_COUNT_POSITIONS[1]
    f.count:ClearAllPoints()
    f.count:SetPoint(pos.point, f, pos.rel or pos.point, pos.px, pos.py)
    local scaleInt = ns.SettingNumber("cdFontScale", K.CD_FONT_SCALE_DEFAULT)
    local scale    = scaleInt / 10.0
    local cdAbove  = db.cdAbove or false
    if f.cd.SetFontScale then f.cd:SetFontScale(math.max(scale, 0.05)) end
    f.cd:SetHideCountdownNumbers(cdAbove or scale <= 0)
    if f.timerText then
        f.timerText:SetShown(cdAbove)
        local timerSize = ns.SettingNumber("timerTextSize", K.TIMER_TEXT_DEFAULT_SIZE)
        f.timerText:SetFont("Fonts\\FRIZQT__.TTF", math.max(timerSize, 1), "OUTLINE")
    end
end

function ns.RefreshAllIconSettings()
    local frame = AiB.CoTank.frame
    if not frame then return end
    if frame.debuffs then
        for _, f in ipairs(frame.debuffs) do ns.ApplyIconSettings(f) end
    end
    if frame.bigDefensives then
        for _, f in ipairs(frame.bigDefensives) do ns.ApplyIconSettings(f) end
    end
end

function ns.RefreshDebuffSpacing()
    local frame = AiB.CoTank.frame
    if not frame or not frame.debuffs then return end
    local spacing = ns.SettingNumber("debuffSpacing", K.DEBUFF_SPACING)
    for i, f in ipairs(frame.debuffs) do
        f:ClearAllPoints()
        if i == 1 then
            f:SetPoint("TOPRIGHT", frame, "BOTTOMRIGHT", 0, 0)
        else
            f:SetPoint("RIGHT", frame.debuffs[i - 1], "LEFT", -spacing, 0)
        end
    end
end

local function ApplyPrivateAuraGrow(frames, spacing)
    local frame = AiB.CoTank.frame
    if not frame then return end
    local dir = ns.DB().privateAuraGrowDir or "LEFT"
    for i, container in ipairs(frames) do
        container:ClearAllPoints()
        if i == 1 then
            if dir == "RIGHT" then
                container:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 0)
            elseif dir == "DOWN" then
                container:SetPoint("TOPRIGHT", frame, "BOTTOMRIGHT", 0, 0)
            elseif dir == "UP" then
                container:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, 0)
            else -- LEFT (Standard)
                container:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, 0)
            end
        else
            local prev = frames[i - 1]
            if dir == "RIGHT" then
                container:SetPoint("LEFT", prev, "RIGHT", spacing, 0)
            elseif dir == "DOWN" then
                container:SetPoint("TOP", prev, "BOTTOM", 0, -spacing)
            elseif dir == "UP" then
                container:SetPoint("BOTTOM", prev, "TOP", 0, spacing)
            else -- LEFT
                container:SetPoint("RIGHT", prev, "LEFT", -spacing, 0)
            end
        end
    end
end

function ns.RefreshPrivateAuraSpacing()
    local frame = AiB.CoTank.frame
    if not frame or not frame.anchorFrames then return end
    local spacing = ns.SettingNumber("privateAuraSpacing", K.DEBUFF_SPACING)
    ApplyPrivateAuraGrow(frame.anchorFrames, spacing)
end

function ns.RefreshPrivateAuraGrow()
    local frame = AiB.CoTank.frame
    if not frame or InCombatLockdown() then return end
    frame:CleanupPrivateAnchors()
    ns.RefreshPrivateAuraSpacing()
    frame:AttachPrivateAnchors()
end

function ns.RefreshPrivateAuraSize()
    local frame = AiB.CoTank.frame
    if not frame or not frame.anchorFrames then return end
    if InCombatLockdown() then return end
    local size = ns.SettingNumber("privateAuraSize", K.PRIVATE_AURA_SIZE_DEFAULT)
    for _, container in ipairs(frame.anchorFrames) do
        container:SetSize(size, size)
    end
    frame:CleanupPrivateAnchors()
    frame:AttachPrivateAnchors()
end

-- Timer-Updater für "Above Icon"-Modus
local timerUpdater = CreateFrame("Frame")
timerUpdater:SetScript("OnUpdate", function()
    if not ns.DB().cdAbove then return end
    local frame = AiB.CoTank.frame
    if not frame then return end
    local now = GetTime()
    local function updateList(list)
        if not list then return end
        for _, f in ipairs(list) do
            if f.timerText and f:GetAlpha() > 0 and (f.expirationTime or 0) > 0 then
                local rem = f.expirationTime - now
                if rem > 0 then
                    if rem >= 60 then
                        f.timerText:SetFormattedText("%d:%02d", math.floor(rem / 60), rem % 60)
                    elseif rem >= 10 then
                        f.timerText:SetFormattedText("%d", math.floor(rem))
                    else
                        f.timerText:SetFormattedText("%.1f", rem)
                    end
                    f.timerText:Show()
                else
                    f.timerText:Hide()
                end
            elseif f.timerText then
                f.timerText:Hide()
            end
        end
    end
    updateList(frame.debuffs)
    updateList(frame.bigDefensives)
end)

-- ── Icon-Frame-Fabrik ─────────────────────────────────────────────────────
local function CreateIconFrame(iconList, iconSize, point, parent, relativePoint, ofsx, ofsy)
    local f = CreateFrame("Button", nil, parent, "BackdropTemplate")
    f:SetFrameStrata("MEDIUM")
    f:SetMouseClickEnabled(true)
    f:SetSize(iconSize, iconSize)
    f:SetBackdrop({edgeFile = K.BACKDROP_TEXTURE, edgeSize = 1})
    f:SetBackdropBorderColor(0, 0, 0, 0.7)
    f.border = f

    f.icon = f:CreateTexture(nil, "ARTWORK")
    f.icon:SetPoint("TOPLEFT",     f, "TOPLEFT",     1, -1)
    f.icon:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1)
    f.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    f.cd = CreateFrame("Cooldown", nil, f, "CooldownFrameTemplate")
    f.cd:SetAllPoints(f.icon)
    f.cd:SetReverse(true)

    f.count = f:CreateFontString(nil, "OVERLAY")
    f.count:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
    f.count:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 2, 1)
    f.count:SetTextColor(1, 1, 1)

    f.timerText = f:CreateFontString(nil, "OVERLAY")
    f.timerText:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
    f.timerText:SetPoint("BOTTOM", f, "TOP", 0, 2)
    f.timerText:SetTextColor(1, 0.85, 0.4)
    f.timerText:Hide()

    f:EnableMouse(true)
    f:SetScript("OnLeave", function()
        GameTooltip:Hide()
        if GameTooltip.ClearLines then GameTooltip:ClearLines() end
    end)
    f:SetScript("OnEnter", function(btn)
        local unit = AiB.CoTank.frame and AiB.CoTank.frame:GetAttribute("unit")
        if unit and btn.auraInstanceID then
            GameTooltip:SetOwner(btn, "ANCHOR_RIGHT")
            GameTooltip:SetUnitAura(unit, btn.auraInstanceID)
            GameTooltip:Show()
        end
    end)

    local idx = #iconList + 1
    if idx == 1 then
        f:SetPoint(point, parent, relativePoint, ofsx, ofsy)
    else
        f:SetPoint("RIGHT", iconList[idx - 1], "LEFT", -K.DEBUFF_SPACING, 0)
    end
    f:SetAlpha(0)
    iconList[idx] = f
    ns.ApplyIconSettings(f)
end

function AiB_CoTank_Mixin:CreateDebuffContainer()
    self.debuffs = {}
    for i = 1, K.DEBUFF_MAX_COUNT do
        CreateIconFrame(self.debuffs, K.DEBUFF_ICON_SIZE, "TOPRIGHT", self, "BOTTOMRIGHT", 0, 0)
    end
    self:RefreshDebuffLayout()
end

function AiB_CoTank_Mixin:CreateDefensiveContainer()
    if InCombatLockdown() or self.bigDefensives then return end
    self.bigDefensives = {}
    local iconSize = K.DEFAULT_HEIGHT - K.POWER_BAR_HEIGHT - 16
    for i = 1, K.BIG_DEFENSIVES_MAX_COUNT do
        CreateIconFrame(self.bigDefensives, iconSize, "RIGHT", self.health, "RIGHT", -K.DEBUFF_SPACING, -2)
    end
    self:RefreshDefensiveLayout()
end

function AiB_CoTank_Mixin:CreatePrivateAnchorContainers()
    if InCombatLockdown() or self.anchorFrames then return end
    self.anchorFrames = {}
    local scaleFactor = ns.CalculateScaleFactor()
    local size = ns.SettingNumber("privateAuraSize", K.PRIVATE_AURA_SIZE_DEFAULT)
    for i = 1, K.PRIVATE_AURAS_MAX_COUNT do
        local container = CreateFrame("Frame", nil, self, "BackdropTemplate")
        container:SetSize(size, size)
        -- Kein Backdrop: Container bleibt immer alpha=1, Engine rendert Aura selbst
        -- Border nur via AttachPrivateAnchors per borderScale
        local anchor = CreateFrame("Frame", nil, container)
        anchor:SetAllPoints(container)
        self.anchorFrames[i] = container
        self.anchorFrames[i]:SetAlpha(1)
    end
    local spacing = ns.SettingNumber("privateAuraSpacing", K.DEBUFF_SPACING)
    ApplyPrivateAuraGrow(self.anchorFrames, spacing)
    self:RefreshAuraLayout()
end

-- ── Private Aura Anchors ──────────────────────────────────────────────────
function AiB_CoTank_Mixin:ShowMockPrivateAuras()
    if not self.anchorFrames then return end
    local mocks = K.MOCK_PREVIEW_DEBUFF_ICONS
    local max   = math.min(self:MaxShownPrivateAuras(), #mocks)
    for i, container in ipairs(self.anchorFrames) do
        if i <= max then
            if not container.mockIcon then
                local tex = container:CreateTexture(nil, "OVERLAY")
                tex:SetPoint("TOPLEFT",     container, "TOPLEFT",      1, -1)
                tex:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", -1,  1)
                tex:SetTexCoord(0.08, 0.92, 0.08, 0.92)
                container.mockIcon = tex
            end
            container.mockIcon:SetTexture(mocks[i])
            container.mockIcon:SetAlpha(0.5)
            container:SetBackdropBorderColor(0, 0, 0, 0.7)
            if not container.mockStack then
                local fs = container:CreateFontString(nil, "OVERLAY")
                fs:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
                fs:SetPoint("BOTTOMRIGHT", container, "BOTTOMRIGHT", 1, 1)
                container.mockStack = fs
            end
            local stacks = ({3, 1, 5, 2})[((i - 1) % 4) + 1]
            container.mockStack:SetText(stacks > 1 and tostring(stacks) or "")
            container.mockStack:Show()
        elseif container.mockIcon then
            container.mockIcon:SetAlpha(0)
            container:SetBackdropBorderColor(0, 0, 0, 0)
            if container.mockStack then container.mockStack:Hide() end
        end
    end
end

function AiB_CoTank_Mixin:HideMockPrivateAuras()
    if not self.anchorFrames then return end
    for _, container in ipairs(self.anchorFrames) do
        if container.mockIcon then container.mockIcon:SetAlpha(0) end
        container:SetBackdropBorderColor(0, 0, 0, 0)
    end
end

function AiB_CoTank_Mixin:AttachPrivateAnchors()
    self:CleanupPrivateAnchors()
    self:RefreshAuraLayout()
    if not self:IsVisible() or not self:ShowPrivateAuras() then return end
    if ns.isTestMode then
        self:ShowMockPrivateAuras()
        return
    end
    local unit = self:GetAttribute("unit")
    if not unit then return end
    self.privateAnchorIDs   = self.privateAnchorIDs or {}
    self.privateScaleFrames = self.privateScaleFrames or {}
    local size        = ns.SettingNumber("privateAuraSize",        K.PRIVATE_AURA_SIZE_DEFAULT)
    local borderVal   = ns.SettingNumber("privateAuraBorderScale", K.PRIVATE_AURA_BORDER_SCALE_DEFAULT)
    local borderScale = borderVal <= 0 and -100 or borderVal
    local stackScale  = ns.SettingNumber("privateAuraStackScale",  K.PRIVATE_AURA_STACK_SCALE_DEFAULT)
    local upscale     = stackScale > 1
    for i = 1, #self.anchorFrames do
        local container = self.anchorFrames[i]
        if not self.privateScaleFrames[i] then
            local sf = CreateFrame("Frame", nil, container)
            sf:SetSize(0.001, 0.001)
            sf:SetPoint("CENTER", container, "CENTER", 0, 0)
            self.privateScaleFrames[i] = sf
        end
        self.privateScaleFrames[i]:SetScale(upscale and stackScale or 1)
        local iconW = upscale and (size / stackScale) or size
        local id = C_UnitAuras.AddPrivateAuraAnchor({
            unitToken            = unit,
            auraIndex            = i,
            parent               = self.privateScaleFrames[i],
            isContainer          = false,
            showCountdownFrame   = true,
            showCountdownNumbers = true,
            iconInfo = {
                iconWidth   = iconW,
                iconHeight  = iconW,
                borderScale = borderScale,
                iconAnchor  = {
                    point         = "CENTER",
                    relativeTo    = self.privateScaleFrames[i],
                    relativePoint = "CENTER",
                    offsetX = 0, offsetY = 0,
                },
            },
        })
        if id then
            table.insert(self.privateAnchorIDs, id)
            -- Container bleibt alpha=0, die Engine rendert das Aura-Icon selbst
        end
    end
end

function AiB_CoTank_Mixin:CleanupPrivateAnchors()
    -- RemovePrivateAuraAnchor darf nicht in Combat aufgerufen werden
    if InCombatLockdown() then return end
    if self.privateAnchorIDs then
        for _, id in ipairs(self.privateAnchorIDs) do
            pcall(C_UnitAuras.RemovePrivateAuraAnchor, id)
        end
        self.privateAnchorIDs = {}
    end
    -- Container bleiben alpha=1 (Engine entscheidet ob Aura gerendert wird)
    if self.privateStackAnchorIDs then
        for _, id in ipairs(self.privateStackAnchorIDs) do
            pcall(C_UnitAuras.RemovePrivateAuraAnchor, id)
        end
        self.privateStackAnchorIDs = {}
    end
    self:HideMockPrivateAuras()
end

-- ── Event-Handler ─────────────────────────────────────────────────────────
function AiB_CoTank_Mixin:OnAttributeChanged(name, value)
    if name ~= "unit" then return end
    self:UnregisterAllEvents()
    local watchUnit = ns.isTestMode and "player" or value
    if watchUnit then
        self:RegisterUnitEvent("UNIT_HEALTH",       watchUnit)
        self:RegisterUnitEvent("UNIT_MAXHEALTH",    watchUnit)
        self:RegisterUnitEvent("UNIT_POWER_UPDATE", watchUnit)
        self:RegisterUnitEvent("UNIT_MAXPOWER",     watchUnit)
        self:RegisterUnitEvent("UNIT_DISPLAYPOWER", watchUnit)
        self:RegisterUnitEvent("UNIT_AURA",         watchUnit)
        if ns.isTestMode and value and value ~= watchUnit then
            self:RegisterUnitEvent("UNIT_AURA", value)
        end
        self:AttachPrivateAnchors()
        self:UpdateVisuals()
    else
        if not ns.isTestMode then self:SetNameText("NO CO-TANK") end
        self.health:SetValue(0)
        self.power:SetValue(0)
        for i = 1, #self.debuffs do self.debuffs[i]:SetAlpha(0) end
        self:CleanupPrivateAnchors()
    end
end

function AiB_CoTank_Mixin:OnSizeChangedHandler() self:FitNameToWidth() end

function AiB_CoTank_Mixin:SetNameText(nameText)
    self.nameText.rawText = nameText
    self.nameText:SetText(nameText)
    self:FitNameToWidth()
end

function AiB_CoTank_Mixin:FitNameToWidth()
    local maxWidth = self:GetWidth() - 8
    self.nameText:SetText(self.nameText.rawText)
    if self.nameText:GetStringWidth() > maxWidth then
        for i = string.len(self.nameText.rawText), 1, -1 do
            local sub = string.sub(self.nameText.rawText, 1, i) .. "..."
            self.nameText:SetText(sub)
            if self.nameText:GetStringWidth() <= maxWidth then break end
        end
    end
end

function AiB_CoTank_Mixin:UpdateVisuals(event, unused_unit, info)
    if event == "UNIT_AURA" and info then
        if not info.isFullUpdate and not info.addedAuras
           and not info.updatedAuras and not info.removedAuraInstanceIDs then
            return
        end
    end
    local now = GetTime()
    if (self.nextUpdate or 0) > now then return end
    self.nextUpdate = now + K.UPDATE_THROTTLE
    local unit = self:GetAttribute("unit")
    if not unit or not UnitExists(unit) then return end
    self:UpdateHealthBar()
    self:UpdatePower()
    self:UpdateDebuffs()
    self:UpdateBigDefensives()
    self:UpdateDeadState()
    if not ns.isTestMode then
        local name = UnitName(unit)
        if name then self:SetNameText(name) end
    end
end

-- Export
ns.Mixin = AiB_CoTank_Mixin
