local ADDON_NAME, ns = ...

-- Always unify namespace (prevents nil Mixins / split-table bugs)
ns = _G[ADDON_NAME] or ns
_G[ADDON_NAME] = ns

ns.mixins = ns.mixins or {}
local Mixins = ns.mixins

local U = ns.util

-- =========================================================
-- pom_mixins.lua
-- =========================================================

-- ------------------------------------------------------------
-- Safe font apply (prevents "Font not set" + bad paths)
-- ------------------------------------------------------------
local function SafeSetFont(fs, path, size, flags)
    if not (fs and fs.SetFont) then return false end
    size  = tonumber(size) or 12
    flags = flags or ""

    local ok = pcall(fs.SetFont, fs, path, size, flags)
    if ok then return true end

    ok = pcall(fs.SetFont, fs, (_G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"), size, flags)
    return ok and true or false
end

-- ------------------------------------------------------------
-- Secret number detection (12.0 / Midnight)
-- ------------------------------------------------------------
local function IsSecretNumber(v)
    if type(v) ~= "number" then return false end
    local f = _G.issecretvalue
    if type(f) ~= "function" then return false end
    local ok, r = pcall(f, v)
    return ok and r and true or false
end

-- ------------------------------------------------------------
-- Rare detection (classification)
-- ------------------------------------------------------------
local function IsRareUnit(unit)
    if not unit or not UnitExists(unit) then return false, nil end
    local cls = U.PlainClassification(unit)
    if cls == "rare" or cls == "rareelite" then
        return true, cls
    end
    return false, cls
end

-- ------------------------------------------------------------
-- DB helpers (READ-ONLY)
-- IMPORTANT:
--   Do NOT write defaults here. InitDB() in pom.lua owns all defaults+migrations.
-- ------------------------------------------------------------
local function GetBaseColorsDB(db)
    if db and type(db.baseColors) == "table" then
        return db.baseColors
    end
    if db and type(db.colors) == "table" then
        return db.colors
    end
    return nil
end

local function GetThreatDB(db)
    if db and type(db.threat) == "table" then
        return db.threat
    end
    return nil
end

local function GetKeyWithLegacy(tbl, modernKey, legacyKey)
    if type(tbl) ~= "table" then return nil end
    if tbl[modernKey] ~= nil then return tbl[modernKey] end
    return tbl[legacyKey]
end

local function GetSkinID(db, styleKey)
    local skins = db and db.skins
    if type(skins) ~= "table" then
        return 23
    end

    if styleKey == "boss" then
        return skins.boss or skins.highlevel or 23
    elseif styleKey == "eliteCaster" then
        return skins.eliteCaster or skins.caster or 23
    elseif styleKey == "eliteMelee" then
        return skins.eliteMelee or skins.melee or 23
    end

    return skins[styleKey] or 23
end

local function GetBaseColorForKey(bdb, colorKey)
    if type(bdb) ~= "table" then
        return { r = 0.8, g = 0.8, b = 0.8 }
    end

    if colorKey == "boss" then
        return bdb.boss or bdb.highlevel or { r = 0.8, g = 0.0, b = 1.0 }
    elseif colorKey == "eliteCaster" then
        return bdb.eliteCaster or bdb.caster or { r = 1.0, g = 0.4, b = 0.8 }
    elseif colorKey == "eliteMelee" then
        return bdb.eliteMelee or bdb.melee or { r = 0.0, g = 0.6, b = 1.0 }
    end

    return bdb[colorKey] or { r = 0.8, g = 0.8, b = 0.8 }
end

-- ------------------------------------------------------------
-- Classification / resolver helpers
-- ------------------------------------------------------------
-- type() alone is not enough: a secret number still answers "number", and it
-- is the arithmetic and the comparisons downstream that raise. U.PlainNumber
-- reduces first and answers nil for a secret, which every caller here already
-- handles as "unknown level".
local function GetSafeUnitLevel(unit)
    if not unit or not UnitExists(unit) then return nil end
    return U.PlainNumber(UnitLevel(unit))
end

local function GetSafePlayerLevel()
    return U.PlainNumber(UnitLevel("player"))
end

local function GetEffectiveLevel(unit)
    if not unit or not UnitExists(unit) then return nil end

    if UnitEffectiveLevel then
        local lvl = U.PlainNumber(UnitEffectiveLevel(unit))
        if lvl then
            return lvl
        end
    end

    return GetSafeUnitLevel(unit)
end

local function IsBossLikeUnit(unit)
    if not unit or not UnitExists(unit) then return false end

    local lvl = GetSafeUnitLevel(unit)
    if lvl == -1 then
        return true
    end

    if UnitIsBossMob then
        -- The pcall protects the call; `ok and r` is the part that raises
        -- when r comes back secret.
        if U.PlainBool(pcall(UnitIsBossMob, unit)) then
            return true
        end
    end

    return false
end

local function ResolveEliteBucket(unit)
    if not unit or not UnitExists(unit) then
        return "eliteMelee"
    end

    -- Conservative split until we build better NPC heuristics. nil =
    -- restricted unit, and an unknown class falls to melee like every other
    -- non-paladin.
    local class = U.PlainClass(unit)
    if class == "PALADIN" then
        return "eliteCaster"
    end

    return "eliteMelee"
end

local function IsPlayerTankRole()
    local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned("player")
    return role == "TANK"
end

local function GetPlayerBasePlateColorKey()
    local role = UnitGroupRolesAssigned and UnitGroupRolesAssigned("player")
    if role == "HEALER" then
        return "healer"
    end
    return "dps"
end

local function IsGrayUnitForColor(unit)
    if not unit or not UnitExists(unit) then
        return false
    end

    if UnitIsTapDenied then
        if U.PlainBool(pcall(UnitIsTapDenied, unit)) then
            return true
        end
    end

    return false
end

local function ResolveStyleKey(unit)
    if not unit or not UnitExists(unit) then
        return "normal"
    end

    local lvl = GetSafeUnitLevel(unit)
    local pl  = GetSafePlayerLevel()
    local cls = U.PlainClassification(unit)

    if IsBossLikeUnit(unit) then
        return "boss"
    end

    if lvl and pl and lvl >= pl + 2 then
        return "boss"
    end

    if lvl and pl and lvl == pl + 1 then
        return "plusOne"
    end

    if cls == "elite" then
        return ResolveEliteBucket(unit)
    end

    return "normal"
end

local function ResolveColorKey(unit)
    if not unit or not UnitExists(unit) then
        return "normal"
    end

    local db = PlateOMaticDB or {}

    -- Gray/tapped must always win.
    if IsGrayUnitForColor(unit) then
        return "gray"
    end

    -- Restore role-based fallback for non-tanks (or tanks if explicitly enabled).
    local useRoleBaseColors = not not GetKeyWithLegacy(db, "useRoleBaseColors", "alwaysShowRoleBaseColors")
    local useRoleBaseColorsForTanks = not not GetKeyWithLegacy(db, "useRoleBaseColorsForTanks", "alwaysShowRoleBaseColorsForTanks")

    if useRoleBaseColors then
        local isTank = IsPlayerTankRole()
        if (not isTank) or useRoleBaseColorsForTanks then
            return GetPlayerBasePlateColorKey()
        end
    end

    local lvl = GetEffectiveLevel(unit)
    local pl  = GetSafePlayerLevel()
    local cls = U.PlainClassification(unit)

    if IsBossLikeUnit(unit) then
        return "boss"
    end

    if lvl and pl and lvl >= pl + 2 then
        return "boss"
    end

    if lvl and pl and lvl == pl + 1 then
        return "plusOne"
    end

    if cls == "elite" then
        return ResolveEliteBucket(unit)
    end

    return "normal"
end

local function SafeSetText(fs, text)
    if not (fs and fs.SetText) then return end
    pcall(fs.SetText, fs, text or "")
end

local function EnsurePrimaryFonts(self)
    local hb = self and self.healthBar
    if not hb then return end

    local db = PlateOMaticDB or {}

    if hb.pomName then
        SafeSetFont(hb.pomName, db.fontPath, db.fontSize, db.fontOutline)
    end

    if hb.pomHealth then
        SafeSetFont(hb.pomHealth, db.fontPath, db.fontSize, db.fontOutline)
    end
end

-- ------------------------------------------------------------
-- Constructor
-- ------------------------------------------------------------
function Mixins:New(frame, skins)
    local o = {}
    for k, v in pairs(self) do
        if k ~= "New" then o[k] = v end
    end
    o.frame = frame
    o.skins = skins or ns.SKINS
    o.healthBar = frame.healthBar or (frame.HealthBarsContainer and frame.HealthBarsContainer.healthBar)
    if not o.healthBar then return o end
    o:Init()
    return o
end

-- ------------------------------------------------------------
-- Init (one-time per nameplate)
-- ------------------------------------------------------------
function Mixins:Init()
    local hb = self.healthBar
    if not hb then return end

    if not hb.pomBG then
        local bg = hb:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints(hb)
        bg:SetTexture("Interface\\Buttons\\WHITE8x8")
        bg:SetVertexColor(0.25, 0.25, 0.25, 0.85)
        hb.pomBG = bg
    end

    if not hb.pomBorders then
        hb.pomBorders = U.MakeBorder(hb, "pomBorders")
    end

    if not hb.pomTextFrame then
        local tf = CreateFrame("Frame", nil, hb)
        tf:SetAllPoints(hb)
        tf:SetFrameLevel(hb:GetFrameLevel() + 20)
        hb.pomTextFrame = tf

        local fsName = tf:CreateFontString(nil, "OVERLAY")
        fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
        fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
        fsName:SetJustifyH("LEFT")
        fsName:SetWordWrap(false)
        hb.pomName = fsName

        local fsHealth = tf:CreateFontString(nil, "OVERLAY")
        fsHealth:SetPoint("RIGHT", tf, "RIGHT", -4, 0)
        fsHealth:SetJustifyH("RIGHT")
        hb.pomHealth = fsHealth
    end

    if not hb.pomThreatText then
        local ft = hb.pomTextFrame:CreateFontString(nil, "OVERLAY")
        ft:SetPoint("TOP", hb, "BOTTOM", 0, -2)
        hb.pomThreatText = ft
    end

    if not hb.pomRareText then
        local rt = hb.pomTextFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        rt:SetPoint("BOTTOM", hb, "TOP", 0, 2)
        rt:SetJustifyH("CENTER")
        rt:SetTextColor(0.8, 0.6, 1.0)
        rt:SetText("RARE")
        rt:Hide()
        hb.pomRareText = rt
    end

    if not hb.pomQuestIcon then
        local qi = hb:CreateTexture(nil, "OVERLAY")
        qi:SetTexture("Interface\\TargetingFrame\\PortraitQuestBadge")
        qi:SetSize(20, 20)
        qi:SetPoint("RIGHT", hb, "LEFT", -5, 5)
        qi:Hide()
        hb.pomQuestIcon = qi

        local qt = hb.pomTextFrame:CreateFontString(nil, "OVERLAY")
        qt:SetPoint("TOP", qi, "BOTTOM", 0, 2)
        qt:SetTextColor(1, 0.82, 0)
        hb.pomQuestText = qt
    end

    if not hb.pomArrows then
        hb.pomArrows = {}

        local arrowL = self.frame:CreateTexture(nil, "OVERLAY")
        arrowL:SetTexture("Interface\\Buttons\\UI-MicroStream-Yellow")
        arrowL:SetSize(24, 24)
        arrowL:Hide()
        hb.pomArrows.left = arrowL

        local arrowR = self.frame:CreateTexture(nil, "OVERLAY")
        arrowR:SetTexture("Interface\\Buttons\\UI-MicroStream-Yellow")
        arrowR:SetSize(24, 24)
        arrowR:SetTexCoord(1, 0, 0, 1)
        arrowR:Hide()
        hb.pomArrows.right = arrowR
    end

    if not hb.pomWarningGlow then
        local glow = hb:CreateTexture(nil, "OVERLAY", nil, 6)
        glow:SetAllPoints(hb)
        glow:SetTexture("Interface\\Buttons\\WHITE8x8")
        glow:SetVertexColor(1, 1, 0, 0.6)
        glow:SetBlendMode("ADD")
        glow:Hide()
        hb.pomWarningGlow = glow

        local ag = glow:CreateAnimationGroup()
        ag:SetLooping("BOUNCE")
        local a1 = ag:CreateAnimation("Alpha")
        a1:SetFromAlpha(0)
        a1:SetToAlpha(1)
        a1:SetDuration(0.4)
        a1:SetSmoothing("IN_OUT")
        hb.pomWarningAnim = ag
    end

    if not hb.pomTargetBorder then
        hb.pomTargetBorder = U.MakeBorder(hb, "pomTargetBorder")
        for i = 1, 4 do hb.pomTargetBorder[i]:Hide() end
    end
end

-- ------------------------------------------------------------
-- Visuals (fonts / name anchor)
-- ------------------------------------------------------------
function Mixins:UpdateVisuals()
    local hb = self.healthBar
    if not hb then return end
    local db = PlateOMaticDB or {}

    local tf = hb.pomTextFrame
    local fsName = hb.pomName
    if tf and fsName then
        fsName:ClearAllPoints()
        local pos = db.nameAnchor or "LEFT"

        if pos == "TOP" then
            fsName:SetPoint("BOTTOM", tf, "TOP", 0, 2)
            fsName:SetJustifyH("CENTER")
            fsName:SetPoint("LEFT", tf, "LEFT", -50, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", 50, 0)
        elseif pos == "CENTER" then
            fsName:SetPoint("CENTER", tf, "CENTER", 0, 1)
            fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
            fsName:SetJustifyH("CENTER")
        else
            fsName:SetPoint("LEFT", tf, "LEFT", 4, 0)
            fsName:SetPoint("RIGHT", tf, "RIGHT", -40, 0)
            fsName:SetJustifyH("LEFT")
        end
    end

    if hb.pomName then
        SafeSetFont(hb.pomName, db.fontPath, db.fontSize, db.fontOutline)
        hb.pomName:SetTextColor(1, 1, 1)
        hb.pomName:Show()
    end

    if hb.pomHealth then
        SafeSetFont(hb.pomHealth, db.fontPath, db.fontSize, db.fontOutline)
        hb.pomHealth:SetTextColor(1, 1, 1)
        hb.pomHealth:Show()
    end

    if hb.pomThreatText then
        SafeSetFont(hb.pomThreatText, db.fontPath, (tonumber(db.fontSize) or 10) - 1, db.fontOutline)
    end

    if hb.pomRareText then
        SafeSetFont(hb.pomRareText, db.fontPath, (tonumber(db.fontSize) or 10) - 2, db.fontOutline)
    end

    if hb.pomQuestText then
        SafeSetFont(hb.pomQuestText, db.fontPath, (tonumber(db.fontSize) or 10) - 2, db.fontOutline)
    end
end

-- ------------------------------------------------------------
-- Heights (ONLY calls heights module; safe to run always)
-- ------------------------------------------------------------
function Mixins:UpdateHeight()
    local hb = self.healthBar
    if not hb then return end

    if ns and ns.heights and ns.heights.ApplyForUnit then
        ns.heights:ApplyForUnit(hb, self.frame.unit)
    end
end

-- ------------------------------------------------------------
-- Quest logic (tooltip scan)
-- ------------------------------------------------------------
function Mixins:UpdateQuest()
    local hb = self.healthBar
    if not hb or not hb.pomQuestIcon then return end

    local db = PlateOMaticDB or {}
    local showIcon = db.showQuestIcon
    local showProg = db.showQuestProg

    if not showIcon then
        hb.pomQuestIcon:Hide()
        if hb.pomQuestText then hb.pomQuestText:Hide() end
        return
    end

    local unit = self.frame.unit
    local isQuest = false
    local progressText = ""

    if C_TooltipInfo and C_TooltipInfo.GetUnit then
        local data = C_TooltipInfo.GetUnit(unit)
        if data then
            for _, line in ipairs(data.lines) do
                if line.type == 17 or line.type == 18 then
                    isQuest = true
                end
                if showProg and line.leftText and type(line.leftText) == "string" then
                    local ok, current, total = pcall(string.match, line.leftText, "(%d+)/(%d+)")
                    if ok and current and total then
                        progressText = current .. "/" .. total
                    else
                        local ok2, perc = pcall(string.match, line.leftText, "(%d+%%)")
                        if ok2 and perc then progressText = perc end
                    end
                end
            end
        end
    end

    if isQuest then
        hb.pomQuestIcon:Show()
        if showProg and progressText ~= "" then
            SafeSetText(hb.pomQuestText, progressText)
            hb.pomQuestText:Show()
        else
            hb.pomQuestText:Hide()
        end
    else
        hb.pomQuestIcon:Hide()
        if hb.pomQuestText then
            hb.pomQuestText:Hide()
        end
    end
end

-- ------------------------------------------------------------
-- Name + HP text
-- ------------------------------------------------------------
function Mixins:UpdateName()
    local hb = self.healthBar
    if not hb or not hb.pomName then return end

    EnsurePrimaryFonts(self)

    -- `UnitName(...) or ""` is a truthiness test, and on a restricted unit the
    -- name comes back secret -- so the `or` raised, not the read. SetText
    -- takes a secret string fine; it is only our fallback that has to go.
    -- type() is a call, which a secret may be passed to; == and `or` are not.
    local name = UnitName(self.frame.unit)
    if type(name) ~= "string" then name = "" end

    SafeSetText(hb.pomName, name)
end

function Mixins:UpdateHealth()
    local hb = self.healthBar
    if not hb or not hb.pomHealth then return end
    local unit = self.frame.unit

    EnsurePrimaryFonts(self)

    if not U.IsSafeUnit(unit) then
        SafeSetText(hb.pomHealth, "")
        return
    end

    if UnitIsDeadOrGhost(unit) then
        SafeSetText(hb.pomHealth, "0%")
        return
    end

    local percentValue
    if UnitHealthPercent then
        local scaling = (CurveConstants and CurveConstants.ScaleTo100) or 1
        percentValue = UnitHealthPercent(unit, true, scaling)
    else
        local maxH = UnitHealthMax(unit) or 1
        local curH = UnitHealth(unit, true) or 0
        percentValue = (curH / maxH) * 100
    end

    percentValue = tonumber(percentValue) or 0
    SafeSetText(hb.pomHealth, string.format("%d%%", percentValue))
end

-- =========================================================
-- UpdateState (skins + base colors + threat + target visuals)
-- =========================================================
function Mixins:UpdateState()
    local hb = self.healthBar
    if not hb then return end

    local db = PlateOMaticDB or {}
    local unit = self.frame.unit

    -- RARE label
    if hb.pomRareText then
        local isRare, cls = IsRareUnit(unit)
        if isRare then
            SafeSetText(hb.pomRareText, (cls == "rareelite") and "RARE+" or "RARE")
            hb.pomRareText:Show()
        else
            hb.pomRareText:Hide()
        end
    end

    -- Scale + target visuals
    local currentScale = db.globalScale or 1.0
    local isTarget = U.PlainIsUnit("target", unit)

    if isTarget then
        currentScale = currentScale * (db.targetScale or 1.15)

        if hb.pomArrows then
            if db.showTargetArrow then
                local arrL, arrR = hb.pomArrows.left, hb.pomArrows.right
                arrL:ClearAllPoints()
                arrR:ClearAllPoints()

                if db.arrowPointingOut then
                    arrL:SetPoint("RIGHT", hb, "LEFT", -4, 0); arrL:SetRotation(1.57)
                    arrR:SetPoint("LEFT", hb, "RIGHT", 4, 0);  arrR:SetRotation(-1.57)
                else
                    arrL:SetPoint("RIGHT", hb, "LEFT", -4, 0); arrL:SetRotation(-1.57)
                    arrR:SetPoint("LEFT", hb, "RIGHT", 4, 0);  arrR:SetRotation(1.57)
                end

                arrL:Show()
                arrR:Show()
            else
                hb.pomArrows.left:Hide()
                hb.pomArrows.right:Hide()
            end
        end
    else
        if hb.pomArrows then
            hb.pomArrows.left:Hide()
            hb.pomArrows.right:Hide()
        end
    end

    self.frame:SetScale(currentScale)

    -- Split style/color resolution
    local styleKey = ResolveStyleKey(unit)
    local colorKey = ResolveColorKey(unit)

    -- Keep resolved keys around for future debugging
    self.frame.POM_StyleKey = styleKey
    self.frame.POM_ColorKey = colorKey

    -- Skin texture
    local skins = (type(self.skins) == "table") and self.skins or ((type(ns.SKINS) == "table") and ns.SKINS or nil)
    local skinID = GetSkinID(db, styleKey)
    local skin = (skins and (skins[skinID] or skins[23])) or nil

    if skin and skin.texture then
        hb:SetStatusBarTexture(skin.texture)
    else
        hb:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
    end

    -- Base color
    local bdb = GetBaseColorsDB(db)
    local cBase = GetBaseColorForKey(bdb, colorKey)
    local finalR, finalG, finalB = cBase.r or 0.8, cBase.g or 0.8, cBase.b or 0.8

    local isTankRole = IsPlayerTankRole()
    local useRoleBaseColors = not not GetKeyWithLegacy(db, "useRoleBaseColors", "alwaysShowRoleBaseColors")
    local useRoleBaseColorsForTanks = not not GetKeyWithLegacy(db, "useRoleBaseColorsForTanks", "alwaysShowRoleBaseColorsForTanks")
    local roleBaseLocked = false

    if useRoleBaseColors and ((not isTankRole) or useRoleBaseColorsForTanks) then
        if colorKey == "healer" or colorKey == "dps" then
            roleBaseLocked = true
        end
    end

    if skin and skin.ignoreColor then
        finalR, finalG, finalB = 1, 1, 1
    end

    -- Neutral lock (reaction==4 and not in combat)
    local neutralLock = false
    if not (skin and skin.ignoreColor) and bdb and bdb.neutral then
        -- Three separate secret traps in the original two lines: `or 0` on a
        -- secret reaction, `== 4` on it, and `not UnitIsPlayer(unit)`. All of
        -- them reduce to nil on a restricted unit, and nil means we cannot
        -- claim the unit is neutral -- so the lock simply does not engage.
        local reaction = U.PlainNumber(UnitReaction(unit, "player"))
        local isPlayer = U.PlainBool(true, UnitIsPlayer(unit))

        if reaction == 4 and isPlayer == false then
            local nc = bdb.neutral
            finalR = nc.r or finalR
            finalG = nc.g or finalG
            finalB = nc.b or finalB

            local inCombat = U.PlainBool(true, UnitAffectingCombat(unit))
            neutralLock = (inCombat == false)
        end
    end

    -- PvP: if active AND disableThreatInPvP => never run threat here.
    local pvpNoThreat = false
    if ns and ns.pvp and ns.pvp.IsActive and ns.pvp:IsActive() then
        local pdb = db.pvp
        if pdb and pdb.disableThreatInPvP then
            pvpNoThreat = true
        end
    end

    -- Solo override: keep ONLY base colors
    local isSolo = (not IsInGroup()) and (not IsInRaid())
    if db.useBaseColorsSolo and isSolo and not (skin and skin.ignoreColor) and not neutralLock then
        hb:SetStatusBarColor(finalR, finalG, finalB)

        if hb.pomThreatText then
            SafeSetText(hb.pomThreatText, "")
            hb.pomThreatText:Hide()
        end

        if hb.pomWarningGlow and hb.pomWarningAnim and self.frame.pomIsFlashing then
            self.frame.pomIsFlashing = false
            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
            hb.pomWarningGlow:Hide()
            hb.pomWarningAnim:Stop()
        end

    elseif pvpNoThreat then
        hb:SetStatusBarColor(finalR, finalG, finalB)

        if hb.pomThreatText then
            SafeSetText(hb.pomThreatText, "")
            hb.pomThreatText:Hide()
        end

        if hb.pomWarningGlow and hb.pomWarningAnim and self.frame.pomIsFlashing then
            self.frame.pomIsFlashing = false
            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
            hb.pomWarningGlow:Hide()
            hb.pomWarningAnim:Stop()
        end

    else
        -- Threat override
        local tdb = GetThreatDB(db) or {}

        local tr, tg, tb, shouldFlash, threatPct = nil, nil, nil, false, 0
        if not neutralLock and tdb.enabled and ns and ns.threat and ns.threat.GetState then
            tr, tg, tb, shouldFlash, threatPct = ns.threat:GetState(unit)
        end

        if not neutralLock and tr and not (skin and skin.ignoreColor) then
            finalR, finalG, finalB = tr, tg, tb
        elseif roleBaseLocked and not neutralLock and not (skin and skin.ignoreColor) then
            local forcedBase = GetBaseColorForKey(bdb, GetPlayerBasePlateColorKey())
            finalR = forcedBase.r or finalR
            finalG = forcedBase.g or finalG
            finalB = forcedBase.b or finalB
        end

        hb:SetStatusBarColor(finalR, finalG, finalB)

        -- Threat % text (SECRET-SAFE)
        if hb.pomThreatText and (not neutralLock) and tdb.showThreatPerc then
            local minPct = tonumber(tdb.pctTextMin) or 1

            local pctIsSecret = IsSecretNumber(threatPct)
            local pct = (not pctIsSecret) and (tonumber(threatPct) or 0) or 0

            if (not pctIsSecret) and pct >= minPct then
                SafeSetText(hb.pomThreatText, string.format("%.0f%%", pct))
                hb.pomThreatText:Show()

                if pct >= 100 then
                    hb.pomThreatText:SetTextColor(1, 0.2, 0.2)
                elseif pct >= 70 then
                    hb.pomThreatText:SetTextColor(1, 1, 0)
                else
                    hb.pomThreatText:SetTextColor(0.6, 0.6, 0.6)
                end
            else
                SafeSetText(hb.pomThreatText, "")
                hb.pomThreatText:Hide()
            end
        elseif hb.pomThreatText then
            SafeSetText(hb.pomThreatText, "")
            hb.pomThreatText:Hide()
        end

        if hb.pomWarningGlow and hb.pomWarningAnim then
            if neutralLock then
                if self.frame.pomIsFlashing then
                    self.frame.pomIsFlashing = false
                    if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                    hb.pomWarningGlow:Hide()
                    hb.pomWarningAnim:Stop()
                end
            else
                local wantFlash = (tdb.flashAggro and shouldFlash) and true or false

                if wantFlash then
                    if not self.frame.pomIsFlashing then
                        self.frame.pomIsFlashing = true
                        hb.pomWarningGlow:Show()
                        hb.pomWarningAnim:Play()

                        local dur = tonumber(tdb.flashDuration) or 0
                        if dur > 0 then
                            if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                            self.frame.pomFlashTimer = C_Timer.NewTimer(dur, function()
                                if self.frame then
                                    self.frame.pomIsFlashing = false
                                    if hb and hb.pomWarningGlow then
                                        hb.pomWarningGlow:Hide()
                                        hb.pomWarningAnim:Stop()
                                    end
                                end
                            end)
                        end
                    end
                else
                    if self.frame.pomIsFlashing then
                        self.frame.pomIsFlashing = false
                        if self.frame.pomFlashTimer then self.frame.pomFlashTimer:Cancel() end
                        hb.pomWarningGlow:Hide()
                        hb.pomWarningAnim:Stop()
                    end
                end
            end
        end
    end

    -- Borders
    if hb.pomBorders then
        U.LayoutBorder(hb.pomBorders, hb, 2)
        for i = 1, 4 do
            hb.pomBorders[i]:SetColorTexture(0, 0, 0, 1)
        end
    end

    -- Target border
    if hb.pomTargetBorder then
        if isTarget then
            local c = db.targetBorderColor or { r = 1, g = 0.82, b = 0, a = 1 }
            local r, g, b, a = c.r or 1, c.g or 1, c.b or 1, c.a or 1
            for i = 1, 4 do
                hb.pomTargetBorder[i]:SetColorTexture(r, g, b, a)
            end
            U.LayoutBorder(hb.pomTargetBorder, hb, 3)
            for i = 1, 4 do
                hb.pomTargetBorder[i]:Show()
            end
        else
            for i = 1, 4 do
                hb.pomTargetBorder[i]:Hide()
            end
        end
    end
end