local _, private = ...

-- Lua Globals --
-- luacheck: globals ceil

-- Libs --
local oUF = private.oUF
local Base = _G.Aurora.Base
local Color = _G.Aurora.Color

-- RealUI --
local RealUI = private.RealUI
local db, ndb -- luacheck: ignore

local UnitFrames = RealUI:GetModule("UnitFrames")
local CombatFader = RealUI:GetModule("CombatFader")
local round = RealUI.Round

local function GetMiscDB()
    return (db and db.misc) or {}
end

local function GetUnitsDB()
    return (db and db.units) or {}
end

local function SafeShow(frame, show)
    if not frame then return end
    if _G.InCombatLockdown() and frame.IsProtected and frame:IsProtected() then
        _G.C_Timer.After(0, function()
            if show then frame:Show() else frame:Hide() end
        end)
    else
        if show then frame:Show() else frame:Hide() end
    end
end

-- Power types where the default state is empty
RealUI.ReversePowers = {
    ["RAGE"] = true,
    ["RUNIC_POWER"] = true,
    ["POWER_TYPE_SUN_POWER"] = true,
    ["LUNAR_POWER"] = true,
    ["INSANITY"] = true,
    ["MAELSTROM"] = true,
    ["FURY"] = true,
    ["PAIN"] = true,
}

-- Attached frames mirror their parent's fill direction: pet hangs off the
-- player frame and targettarget off the target frame, so reversing the
-- parent's health bar must flip them too ("Pet and ToT don't respect
-- health's Reverse Fill Direction"). Their own reverseFill toggle then
-- flips them *relative to* the parent rather than absolutely.
local reverseFillParent = {
    pet = "player",
    targettarget = "target",
}

-- Secret-safe class color lookup: UnitClass can return a secret class token
-- in tainted contexts, and indexing colors.class with a secret errors.
-- C_ClassColor.GetClassColor handles secrets natively (the oUF 14 pattern).
local function GetClassColor(colors, class)
    if not class then return nil end
    if _G.issecretvalue(class) then
        return _G.C_ClassColor.GetClassColor(class)
    end
    return colors.class[class]
end

-- Secret-safe boolean: unit-state queries return secret booleans in tainted
-- execution contexts, and a boolean test on one errors. Substitute a default.
local function SafeBool(value, default)
    if _G.issecretvalue(value) then return default end
    return value
end

-- Secret-safe table lookup: indexing a normal table with a secret key errors
-- the same way a boolean test on a secret does, so unit queries used as color
-- table keys (UnitReaction, UnitSelectionType, UnitClassification) have to be
-- screened before use. Returns nil when the key is absent or secret.
local function SafeIndex(tbl, key)
    if key == nil or _G.issecretvalue(key) then return nil end
    return tbl[key]
end

local function GetReverseFill(unit, info)
    -- Natural fill direction based on bar side:
    -- Player (RIGHT side): natural=true → fill anchored RIGHT, grows right→left
    -- Target (LEFT side): natural=false → fill anchored LEFT, grows left→right
    local natural = false
    if info and info.point then
        natural = info.point == "RIGHT"
    end

    local reverse = natural
    local unitsDB = db and db.units

    -- Inherit the parent frame's toggle first
    local parentDB = unitsDB and reverseFillParent[unit] and unitsDB[reverseFillParent[unit]]
    if parentDB and parentDB.reverseFill then
        reverse = not reverse
    end

    -- Per-unit toggle flips relative to the direction so far
    local unitDB = unitsDB and unitsDB[unit]
    if unitDB and unitDB.reverseFill then
        reverse = not reverse
    end

    return reverse
end
UnitFrames.GetReverseFill = GetReverseFill

local function GetVertices(info, useOther)
    local side = info.point
    if useOther then
        side = side == "RIGHT" and "LEFT" or "RIGHT"
    end

    if side == "RIGHT" then
        return (info.rightVertex % 2) + 1, info.rightVertex
    else
        return info.leftVertex, (info.leftVertex % 2) + 3
    end
end

-- (Re)anchor the health prediction sub-widgets for the given fill direction.
-- Called at creation and again whenever the health bar's reverse fill changes
-- at runtime (per-unit "Reverse Fill Direction" toggle), so the bars track the
-- live direction instead of the one captured at creation time.
local function PositionHealthPredictions(Health, isReverse)
    if not Health.HealingAll then return end
    local fillTexture = Health:GetStatusBarTexture()

    -- Incoming heals: stacked onto the health fill's moving edge
    local HealingAll = Health.HealingAll
    HealingAll:ClearAllPoints()
    HealingAll:SetPoint("TOP", Health)
    HealingAll:SetPoint("BOTTOM", Health)
    if isReverse then
        HealingAll:SetPoint("RIGHT", fillTexture, "LEFT")
    else
        HealingAll:SetPoint("LEFT", fillTexture, "RIGHT")
    end
    HealingAll:SetReverseFill(isReverse)

    -- Damage absorb: overlay pinned to the bar's full-health end, growing
    -- inward over the fill. Absorbs are reported even at max health
    -- (damageAbsorbClampMode = MaximumHealth), so a bar stacked after the
    -- fill edge would spill out past the end of the frame at full health;
    -- the overlay instead shows how much of the bar the shield covers.
    local DamageAbsorb = Health.DamageAbsorb
    DamageAbsorb:ClearAllPoints()
    DamageAbsorb:SetPoint("TOP", Health)
    DamageAbsorb:SetPoint("BOTTOM", Health)
    DamageAbsorb:SetPoint("LEFT", Health)
    DamageAbsorb:SetPoint("RIGHT", Health)
    DamageAbsorb:SetReverseFill(not isReverse)

    -- Heal absorb: eats into the health fill from its moving edge
    local HealAbsorb = Health.HealAbsorb
    HealAbsorb:ClearAllPoints()
    HealAbsorb:SetPoint("TOP", Health)
    HealAbsorb:SetPoint("BOTTOM", Health)
    if isReverse then
        HealAbsorb:SetPoint("LEFT", fillTexture, "LEFT")
    else
        HealAbsorb:SetPoint("RIGHT", fillTexture, "RIGHT")
    end
    HealAbsorb:SetReverseFill(not isReverse)
end

local function CreateHealthBar(parent, info, isAngled, unitToken)
    local Health
    if isAngled then
        local width, height = parent:GetWidth(), parent:GetHeight()
        if db.units[unitToken].healthHeight then
            height = round((height - 3) * db.units[unitToken].healthHeight)
        end

        Health = parent:CreateAngle("StatusBar", nil, parent.overlay)
        Health:SetAngleVertex(info.leftVertex, info.rightVertex)
        Health:SetSize(width, height)
        Health:SetPoint("TOP"..info.point, parent)
        Health:SetReverseFill(GetReverseFill(unitToken, info))

        -- Set initial bg color: red when alternative style is active
        -- Use a separate HealthBG StatusBar at the same frame level, with fill at BORDER layer
        -- (above Health's bg at BACKGROUND, below Health's fill at ARTWORK)
        if GetMiscDB().alternativeBarStyle then
            local HealthBG = parent:CreateAngle("StatusBar", nil, parent.overlay)
            HealthBG:SetAngleVertex(info.leftVertex, info.rightVertex)
            HealthBG:SetSize(width, height)
            HealthBG:SetPoint("TOP"..info.point, parent)
            HealthBG:SetReverseFill(GetReverseFill(unitToken, info))
            HealthBG:SetFrameLevel(Health:GetFrameLevel())
            -- Hide HealthBG's own bg and borders
            HealthBG.bg:SetAlpha(0)
            HealthBG.top:Hide()
            HealthBG.bottom:Hide()
            HealthBG.left:Hide()
            HealthBG.right:Hide()
            -- Put fill at BORDER layer (between BACKGROUND and ARTWORK)
            HealthBG.fill:SetDrawLayer("BORDER")
            HealthBG:SetMinMaxValues(0, 1)
            HealthBG:SetValue(1)
            local unitsDB = GetUnitsDB()
            local hbDB = unitsDB[unitToken] and unitsDB[unitToken].healthBar
            local bgColor = (hbDB and hbDB.background) or {0.78, 0.15, 0.15}
            local bgOpacity = (hbDB and hbDB.backgroundOpacity) or 1.0
            HealthBG:SetStatusBarColor(bgColor[1], bgColor[2], bgColor[3], bgOpacity)
            parent.HealthBG = HealthBG
        end

        Health.PreUpdate = function(self)
            local isReverse = GetReverseFill(unitToken, info)
            self:SetReverseFill(isReverse)
            -- Keep the prediction bars tracking the live fill direction
            if self.predictionsReversed ~= isReverse then
                self.predictionsReversed = isReverse
                PositionHealthPredictions(self, isReverse)
            end
        end

        -- Hook overlay alpha to hide HealthBG/PowerBG when faded (prevents
        -- color bleed-through). Delay activation until after CombatFader's
        -- initial setup completes.
        if GetMiscDB().alternativeBarStyle then
            _G.C_Timer.After(1, function()
                _G.hooksecurefunc(parent.overlay, "SetAlpha", function(overlay, alpha)
                    if not GetMiscDB().alternativeBarStyle then return end
                    -- Hide only when deeply faded (below "hurt" threshold)
                    -- Target Selected = 0.75, so use 0.5 as threshold
                    local show = alpha >= 0.5
                    if parent.HealthBG then parent.HealthBG:SetShown(show) end
                    if parent.PowerBG then parent.PowerBG:SetShown(show) end
                end)
            end)
        end

        -- Health prediction sub-widgets
        local isReverse = GetReverseFill(unitToken, info)

        local HealingAll = parent:CreateAngle("Prediction", nil, Health)
        HealingAll:SetWidth(Health:GetWidth())
        HealingAll:SetStatusBarColor(0.0, 0.659, 0.608, 0.4)
        Health.HealingAll = HealingAll

        local DamageAbsorb = parent:CreateAngle("Prediction", nil, Health)
        DamageAbsorb:SetWidth(Health:GetWidth())
        DamageAbsorb:SetStatusBarColor(0.75, 0.75, 1.0, 0.35)
        Health.DamageAbsorb = DamageAbsorb
        -- Default clamp mode (MissingHealth=0) returns 0 absorb when HP is full.
        -- MaximumHealth clamps to maxHP so absorb shows even at 100% health.
        Health.damageAbsorbClampMode = Enum.UnitDamageAbsorbClampMode.MaximumHealth

        local HealAbsorb = parent:CreateAngle("Prediction", nil, Health)
        HealAbsorb:SetWidth(Health:GetWidth())
        HealAbsorb:SetStatusBarColor(0.6, 0.15, 0.15, 0.5)
        Health.HealAbsorb = HealAbsorb

        Health.predictionsReversed = isReverse
        PositionHealthPredictions(Health, isReverse)
    else
        Health = _G.CreateFrame("StatusBar", nil, parent.overlay)
        Health:SetPoint("TOPLEFT", parent)
        Health:SetPoint("BOTTOMRIGHT", parent, 0, 3)
        Health:SetStatusBarTexture(RealUI.textures.plain)

        Base.SetBackdrop(Health, Color.black, 1)
        Health:SetBackdropOption("offsets", {
            left = -1,
            right = -1,
            top = -1,
            bottom = -1,
        })
    end

    -- Tag health text using composed tag strings
    if info.text then
        Health.text = Health:CreateFontString(nil, "OVERLAY")
        if info.point then
            Health.text:SetPoint("BOTTOM"..info.point, Health, "TOP"..info.point, 2, 2)
        else
            Health.text:SetPoint("CENTER")
        end
        UnitFrames:ApplyStatusTextFont(Health.text)
        local statusText = GetMiscDB().statusText
        parent:Tag(Health.text, UnitFrames.GetHealthTagString(statusText))
    end

    Health.barType = "health"
    local unitDB = GetUnitsDB()[unitToken] or {}
    local hb = unitDB.healthBar or {}
    Health.colorClass = db.overlay.classColor or hb.colorForegroundByClass
    Health.colorTapping = true
    Health.colorDisconnected = true
    Health.colorHealth = not Health.colorClass
    Health.colorReaction = Health.colorClass

    parent.Health = Health

    -- For angled bars with alternative style: override oUF's UpdateColor to apply dark foreground
    if isAngled then
        Health.UpdateColor = function(self, event, unit)
            -- oUF 14: the live unit is the private __unit (tracks vehicle
            -- swaps exactly like the old frame.unit did)
            if not unit or self.__unit ~= unit then return end
            local element = self.Health
            if not element then return end

            if GetMiscDB().alternativeBarStyle then
                -- Alternative style: dark foreground, red bg shows through missing health.
                -- Honor foregroundOpacity and colorForegroundByClass the same way the
                -- HealthBG honors backgroundOpacity and colorBackgroundByClass.
                local unitSettings = GetUnitsDB()[unit] or {}
                local healthBarDB = unitSettings.healthBar or {}
                local fgOpacity = healthBarDB.foregroundOpacity or 1.0
                local classColor
                if healthBarDB.colorForegroundByClass then
                    local _, class = _G.UnitClass(unit)
                    classColor = GetClassColor(self.colors, class)
                end
                if classColor then
                    local r, g, b = classColor:GetRGB()
                    element:SetStatusBarColor(r, g, b, fgOpacity)
                else
                    local c = healthBarDB.foreground or {0.08, 0.08, 0.08}
                    element:SetStatusBarColor(c[1], c[2], c[3], fgOpacity)
                end
            else
                -- Default oUF color logic
                local color
                -- Same secret-value exposure as the other unit-state readers in
                -- this file: guard every test, and screen the reaction index
                -- before it reaches the color table. Resolved once up front —
                -- the original called UnitReaction twice.
                local reactionColor = SafeIndex(self.colors.reaction, _G.UnitReaction(unit, "player"))

                if element.colorDisconnected and not SafeBool(_G.UnitIsConnected(unit), true) then
                    color = self.colors.disconnected
                elseif element.colorTapping and not SafeBool(_G.UnitPlayerControlled(unit), false)
                    and SafeBool(_G.UnitIsTapDenied(unit), false) then
                    color = self.colors.tapped
                elseif element.colorClass and (SafeBool(_G.UnitIsPlayer(unit), false)
                    or SafeBool(_G.UnitInPartyIsAI(unit), false)) then
                    local _, class = _G.UnitClass(unit)
                    color = GetClassColor(self.colors, class)
                elseif element.colorReaction and reactionColor then
                    color = reactionColor
                elseif element.colorHealth then
                    color = self.colors.health
                end
                if color then
                    element:SetStatusBarColor(color:GetRGB())
                end
            end
        end
    end
end


local CreateHealthStatus do
    local classification = {
        rareelite = {r=1, g=0.5, b=0},
        elite = {r=1, g=1, b=0},
        rare = {r=0.75, g=0.75, b=0.75},
    }

    local function UpdatePvP(self, event, unit)
        -- oUF registers this element for three events and TWO of them are
        -- unitless: `HONOR_LEVEL_UPDATE` and `PLAYER_REGEN_ENABLED` are
        -- registered with `unitless = true`, so `Path` forwards no unit and
        -- `UnitIsPVP(nil)` throws "bad argument #1" (x8 on leaving combat,
        -- 2026-08-24). Same two lines oUF's own `Update` opens with, and the
        -- same two `UpdatePvPIcon` below already had — this override was the
        -- one that never got them.
        if unit and unit ~= self.__unit then return end
        unit = unit or self.__unit

        local PvPIndicator = self.PvPIndicator

        -- UnitIsPVP, UnitReaction and UnitIsFriend all return secrets for a
        -- restricted unit; a secret reaction would also poison the color lookup.
        local color
        if SafeBool(_G.UnitIsPVP(unit), false) then
            color = SafeIndex(self.colors.reaction, _G.UnitReaction(unit, "player"))
            if not color then
                local isFriend = SafeBool(_G.UnitIsFriend(unit, "player"), false)
                color = self.colors.reaction[isFriend and 5 or 2]
            end
        end

        if color then
            PvPIndicator:SetBackgroundColor(color[1], color[2], color[3], color[4])
        else
            PvPIndicator:SetBackgroundColor(_G.Aurora.Color.frame:GetRGBA())
        end
    end
    local function UpdateClassification(self, event)
        local color = SafeIndex(classification, _G.UnitClassification(self.__unit))
            or _G.Aurora.Color.frame
        self.Classification:SetBackgroundColor(color.r, color.g, color.b, color.a)
    end

    -- oUF's default PvPIndicator update passes a possibly-secret honor level to
    -- C_PvP.GetHonorRewardInfo (upstream guards UnitIsPVP but missed this one;
    -- logged in docs/oUF-Backlog.md). Our non-angled indicators have no Badge,
    -- so honor data is irrelevant — icon-only override with guarded reads.
    local function UpdatePvPIcon(self, event, unit)
        if unit and unit ~= self.__unit then return end
        unit = unit or self.__unit
        local element = self.PvPIndicator

        local status
        local ffa = _G.UnitIsPVPFreeForAll(unit)
        if not _G.issecretvalue(ffa) and ffa then
            status = "FFA"
        else
            local factionGroup = _G.UnitFactionGroup(unit)
            local isPvP = _G.UnitIsPVP(unit)
            if factionGroup and not _G.issecretvalue(factionGroup) and factionGroup ~= "Neutral"
                and not _G.issecretvalue(isPvP) and isPvP then
                status = factionGroup
            end
        end

        if status then
            element:SetTexture([[Interface\TargetingFrame\UI-PVP-]] .. status)
            element:SetTexCoord(0, 0.65625, 0, 0.65625)
            element:Show()
        else
            element:Hide()
        end
    end

    function CreateHealthStatus(parent, info, isAngled, unitToken)
        local PvPIndicator
        if isAngled then
            local leftVertex, rightVertex = GetVertices(info)
            local width, height = 4, _G.ceil(parent.Health:GetHeight() * 0.65)
            PvPIndicator = parent:CreateAngle("Frame", nil, parent.Health)
            PvPIndicator:SetSize(width, height)
            PvPIndicator:SetPoint("TOP"..info.point, parent.Health, info.point == "RIGHT" and -8 or 8, 0)
            PvPIndicator:SetAngleVertex(leftVertex, rightVertex)

            PvPIndicator.Override = UpdatePvP

            if not (unitToken == "player" or unitToken == "pet") then
                local class = parent:CreateAngle("Frame", nil, parent.Health)
                class:SetSize(width, height)
                class:SetPoint("TOP"..info.point, parent.Health, info.point == "RIGHT" and -16 or 16, 0)
                class:SetAngleVertex(leftVertex, rightVertex)

                class.Update = UpdateClassification
                parent.Classification = class
                parent:RegisterEvent("UNIT_CLASSIFICATION_CHANGED", UpdateClassification)
            end
        else
            PvPIndicator = parent.overlay:CreateTexture(nil, 'ARTWORK', nil, 1)
            PvPIndicator:SetSize(16, 16)
            PvPIndicator:SetPoint('RIGHT', parent.overlay, 'LEFT')
            PvPIndicator.Override = UpdatePvPIcon
        end
        parent.PvPIndicator = PvPIndicator
    end
end


local function CreatePowerBar(parent, info, isAngled, unitToken)
    local Power
    if isAngled then
        local width, height = round(parent:GetWidth() * 0.9), round((parent:GetHeight() - 3) * (1 - db.units[unitToken].healthHeight))
        local xOffset = parent.Health:GetHeight() - height

        Power = parent:CreateAngle("StatusBar", nil, parent.overlay)
        Power:SetSize(width, height)
        Power:SetPoint("BOTTOM"..info.point, parent, info.point == "RIGHT" and -xOffset or xOffset, 0)
        Power:SetAngleVertex(info.leftVertex, info.rightVertex)
        Power:SetReverseFill(GetReverseFill(unitToken, info))

        -- Alternative bar style: power-type colored background revealed as
        -- power drains, mirroring HealthBG (fill at BORDER layer, between
        -- Power's bg at BACKGROUND and its fill at ARTWORK)
        if GetMiscDB().alternativeBarStyle then
            local PowerBG = parent:CreateAngle("StatusBar", nil, parent.overlay)
            PowerBG:SetSize(width, height)
            PowerBG:SetPoint("BOTTOM"..info.point, parent, info.point == "RIGHT" and -xOffset or xOffset, 0)
            PowerBG:SetAngleVertex(info.leftVertex, info.rightVertex)
            PowerBG:SetReverseFill(GetReverseFill(unitToken, info))
            PowerBG:SetFrameLevel(Power:GetFrameLevel())
            PowerBG.bg:SetAlpha(0)
            PowerBG.top:Hide()
            PowerBG.bottom:Hide()
            PowerBG.left:Hide()
            PowerBG.right:Hide()
            PowerBG.fill:SetDrawLayer("BORDER")
            PowerBG:SetMinMaxValues(0, 1)
            PowerBG:SetValue(1)
            parent.PowerBG = PowerBG
        end
    else
        Power = _G.CreateFrame("StatusBar", nil, parent.overlay)
        Power:SetPoint("TOPLEFT", parent.Health, "BOTTOMLEFT", 0, -1)
        Power:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
        Power:SetStatusBarTexture(RealUI.textures.plain)

        Base.SetBackdrop(Power, Color.black, 1)
        Power:SetBackdropOption("offsets", {
            left = -1,
            right = -1,
            top = -1,
            bottom = -1,
        })
    end

    -- Tag power text using composed tag strings
    if info.text then
        Power.text = Power:CreateFontString(nil, "OVERLAY")
        if info.point then
            Power.text:SetPoint("TOP"..info.point, Power, "BOTTOM"..info.point, 2, -3)
        else
            Power.text:SetPoint("CENTER")
        end
        UnitFrames:ApplyStatusTextFont(Power.text)
        local statusText = GetMiscDB().statusText
        local _, powerType = _G.UnitPowerType(unitToken)
        parent:Tag(Power.text, UnitFrames.GetPowerTagString(statusText, powerType))
    end

    Power.barType = "power"
    Power.colorPower = true
    Power.frequentUpdates = true

    parent.Power = Power

    -- Alternative bar style: repaint the power fill dark (reusing the Health
    -- foreground settings — one foreground color overall) and give PowerBG the
    -- power-type color oUF just resolved. PostUpdateColor runs after oUF's own
    -- color logic on every color update, including UNIT_DISPLAYPOWER, so druid
    -- form swaps recolor the background live.
    if isAngled then
        function Power.PostUpdateColor(element, unit, color, r, g, b)
            if not GetMiscDB().alternativeBarStyle then return end

            local healthBarDB = (GetUnitsDB()[unit] or {}).healthBar or {}
            local c = healthBarDB.foreground or {0.08, 0.08, 0.08}
            element:SetStatusBarColor(c[1], c[2], c[3], healthBarDB.foregroundOpacity or 1.0)

            local PowerBG = parent.PowerBG
            if PowerBG then
                if color then
                    r, g, b = color:GetRGB()
                end
                if r then
                    PowerBG:SetStatusBarColor(r, g, b, healthBarDB.backgroundOpacity or 1.0)
                end
            end
        end
    end
end


local CreatePowerStatus do
    local status = {
        afk = {1, 1, 0},
        offline = oUF.colors.disconnected,
        leader = {0, 1, 1},
        combat = {1, 0, 0},
        resting = {0, 1, 0},
    }
    local function UpdateStatus(self, event)
        local unit = self.__unit

        -- Every one of these can hand back a secret boolean when the unit is
        -- restricted and we are running tainted, so route them all through
        -- SafeBool rather than testing the raw return.
        local isConnected = SafeBool(_G.UnitIsConnected(unit), true)
        local isLeader = SafeBool(_G.UnitIsGroupLeader(unit), false)
        local inCombat = SafeBool(_G.UnitAffectingCombat(unit), false)
        local isResting = SafeBool(_G.IsResting(), false)
        local isAFK = SafeBool(_G.UnitIsAFK(unit), false)

        if isAFK then
            self.LeaderIndicator.status = "afk"
        elseif not isConnected then
            self.LeaderIndicator.status = "offline"
        elseif isLeader then
            self.LeaderIndicator.status = "leader"
        else
            self.LeaderIndicator.status = false
        end

        if self.LeaderIndicator.status then
            local color = status[self.LeaderIndicator.status]
            self.LeaderIndicator:SetBackgroundColor(color[1], color[2], color[3], color[4])
            SafeShow(self.LeaderIndicator, true)
        else
            SafeShow(self.LeaderIndicator, false)
        end

        if inCombat then
            self.CombatIndicator.status = "combat"
        elseif isResting then
            self.CombatIndicator.status = "resting"
        else
            self.CombatIndicator.status = false
        end

        if self.LeaderIndicator.status and not self.CombatIndicator.status then
            self.CombatIndicator:SetBackgroundColor(_G.Aurora.Color.frame:GetRGBA())
            SafeShow(self.CombatIndicator, true)
        elseif self.CombatIndicator.status then
            local color = status[self.CombatIndicator.status]
            self.CombatIndicator:SetBackgroundColor(color[1], color[2], color[3], color[4])
            SafeShow(self.CombatIndicator, true)
        else
            SafeShow(self.CombatIndicator, false)
        end
    end

    function CreatePowerStatus(parent, data, unit)
        local point, anchor, relPoint, x, info
        if data.power then
            info, anchor = data.power, parent.Power
        else
            info, anchor = data.health, parent.Health
        end
        if info.point == "LEFT" then
            point, relPoint, x = "TOPLEFT", "TOPRIGHT", -8
        else
            point, relPoint, x = "TOPRIGHT", "TOPLEFT", 8
        end
        local leftVertex, rightVertex = GetVertices(info, not data.isBig)
        local width, height = 4, anchor:GetHeight()

        local CombatRest = parent:CreateAngle("Frame", nil, anchor)
        CombatRest:SetSize(width, height)
        CombatRest:SetPoint(point, anchor, relPoint, x, 0)
        CombatRest:SetAngleVertex(leftVertex, rightVertex)
        CombatRest.Override = UpdateStatus
        parent.CombatIndicator = CombatRest
        if unit == "player" then
            parent.RestingIndicator = CombatRest
        end

        local LeaderAFK = parent:CreateAngle("Frame", nil, anchor)
        LeaderAFK:SetSize(width, height)
        LeaderAFK:SetPoint(point, CombatRest, relPoint, x, 0)
        LeaderAFK:SetAngleVertex(leftVertex, rightVertex)
        LeaderAFK.Override = UpdateStatus
        parent.LeaderIndicator = LeaderAFK
        parent.AwayIndicator = LeaderAFK
    end
end


local CreateEndBox do
    local function UpdateEndBox(self, ...)
        local unit = self.__unit
        local isPlayer = SafeBool(_G.UnitIsPlayer(unit), false)
        local isPlayerControlled = SafeBool(_G.UnitPlayerControlled(unit), false)
        local isTapDenied = SafeBool(_G.UnitIsTapDenied(unit), false)

        local color
        if isPlayer or (isPlayerControlled and not isPlayer) then
            local _, classToken = _G.UnitClass(unit)
            color = GetClassColor(self.colors, classToken)
        elseif not isPlayerControlled and isTapDenied then
            color = self.colors.tapped
        else
            color = SafeIndex(self.colors.reaction, _G.UnitReaction(unit, "player"))
                or SafeIndex(self.colors.selection, _G.UnitSelectionType(unit, true))
        end
        if not color then return end

        -- GetRGB works for both oUF colors and C_ClassColor ColorMixins
        -- (the latter have no [1][2][3] array form)
        local r, g, b
        if color.GetRGB then
            r, g, b = color:GetRGB()
        else
            r, g, b = color[1], color[2], color[3]
        end
        for i = 1, #self.EndBox do
            self.EndBox[i]:SetBackgroundColor(r, g, b, 1)
        end
    end
    function CreateEndBox(parent, data)
        local height = parent.Health:GetHeight()
        local boxHeight = height + (data.isBig and 2 or 0)
        local boxWidth = data.isBig and 6 or 4
        local point, relPoint, x
        if data.health.point == "RIGHT" then
            point, relPoint, x = "TOPLEFT", "TOPRIGHT", -(height - 2)
        else
            point, relPoint, x = "TOPRIGHT", "TOPLEFT", (height - 2)
        end
        parent.EndBox = {
            Update = UpdateEndBox
        }

        local healthBox = parent:CreateAngle("Frame", nil, parent.Health)
        healthBox:SetSize(boxWidth, boxHeight)
        healthBox:SetPoint(point, parent.Health, relPoint, x, 0)
        healthBox:SetAngleVertex(GetVertices(data.health))
        parent.EndBox[1] = healthBox

        if data.isBig then
            height = parent.Power:GetHeight()
            boxHeight = height + 2
            boxWidth = data.isBig and 6 or 4
            if data.power.point == "RIGHT" then
                point, relPoint, x = "BOTTOMLEFT", "BOTTOMRIGHT", -(height - 2)
            else
                point, relPoint, x = "BOTTOMRIGHT", "BOTTOMLEFT", (height - 2)
            end
            local powerBox = parent:CreateAngle("Frame", nil, parent.Power)
            powerBox:SetSize(boxWidth, boxHeight)
            powerBox:SetPoint(point, parent.Power, relPoint, x, 0)
            powerBox:SetAngleVertex(GetVertices(data.power))
            parent.EndBox[2] = powerBox

            -- hide the line between the two boxes
            SafeShow(healthBox.bottom, false)
            SafeShow(powerBox.top, false)
        end
    end
end


-- Init
local function Shared(self, unit)
    -- oUF 14 privatized frame.unit (now __unit). unitToken keeps the raw
    -- spawn token ("boss1") for construction-time db lookups — the same
    -- value oUF 13 exposed as frame.unit (AceDB wildcard defaults key off
    -- it); the normalized `unit` drives unitData lookups as before.
    local unitToken = unit
    unit = unit:match("(%a+)%d*")
    UnitFrames:debug("Shared", self, unitToken, unit)

    -- Blizzard's UnitFrame_OnEnter/UnitFrame_UpdateTooltip read self.unit,
    -- which oUF 14 no longer maintains — refresh a compat copy from __unit
    -- at hover time (oUF itself never touches .unit anymore)
    self:SetScript("OnEnter", function(frame, ...)
        frame.unit = frame.__unit
        return _G.UnitFrame_OnEnter(frame, ...)
    end)
    self:SetScript("OnLeave", _G.UnitFrame_OnLeave)
    self:RegisterForClicks("AnyUp")

    local misc = GetMiscDB()
    if misc.focusclick then
        local ModKey = misc.focuskey or "SHIFT"
        local MouseButton = 1
        local key = ModKey .. "-type" .. (MouseButton or "")
        if(unit == "focus") then
            self:SetAttribute(key, "macro")
            self:SetAttribute("macrotext", "/clearfocus")
        else
            self:SetAttribute(key, "focus")
        end
    end

    -- Create a proxy frame for the CombatFader to avoid taint city.
    self.overlay = _G.CreateFrame("Frame", nil, self)
    self.overlay:SetFrameStrata("BACKGROUND")
    self.overlay:EnableMouse(false)
    CombatFader:RegisterFrameForFade("UnitFrames", self.overlay)

    local unitData = UnitFrames[unit]
    local unitDB = db.units[unit]
    local sizeMod = UnitFrames.layoutSize == 1 and 0.85 or 1

    local width, height = round(unitDB.size.x * sizeMod), round(unitDB.size.y * sizeMod)
    local isAngled = unitData.health and unitData.health.leftVertex and unitData.health.rightVertex

    if isAngled then
        self:SetSize(width, height)
    else
        -- Account for backdrop borders
        self:SetSize(width - 2, height - 2)
    end
    unitData.nameLength = ceil(width / 10)

    if unitData.health then
        CreateHealthBar(self, unitData.health, isAngled, unitToken)
        CreateHealthStatus(self, unitData.health, isAngled, unitToken)
    end

    if unitData.power then
        CreatePowerBar(self, unitData.power, isAngled, unitToken)
    end

    if isAngled then
        CreatePowerStatus(self, unitData, unit)
        CreateEndBox(self, unitData)
    end

    unitData.create(self)

    if isAngled then
        local point = unitData.health.point
        local healthHeightPct = unitDB.healthHeight or 0.6

        function self.ApplySize(frame, newWidth, newHeight)
            local newHealthH = round((newHeight - 3) * healthHeightPct)
            local newPowerH  = round((newHeight - 3) * (1 - healthHeightPct))
            local newPowerW  = round(newWidth * 0.9)
            local newXOffset = newHealthH - newPowerH

            frame:SetSize(newWidth, newHeight)

            frame.Health:SetSize(newWidth, newHealthH)
            if frame.HealthBG then frame.HealthBG:SetSize(newWidth, newHealthH) end

            if frame.Health.HealingAll   then frame.Health.HealingAll:SetWidth(newWidth)   end
            if frame.Health.DamageAbsorb then frame.Health.DamageAbsorb:SetWidth(newWidth) end
            if frame.Health.HealAbsorb   then frame.Health.HealAbsorb:SetWidth(newWidth)   end

            if frame.Power then
                frame.Power:SetSize(newPowerW, newPowerH)
                frame.Power:ClearAllPoints()
                if point == "RIGHT" then
                    frame.Power:SetPoint("BOTTOMRIGHT", frame, -newXOffset, 0)
                else
                    frame.Power:SetPoint("BOTTOMLEFT", frame, newXOffset, 0)
                end

                if frame.PowerBG then
                    frame.PowerBG:SetSize(newPowerW, newPowerH)
                    frame.PowerBG:ClearAllPoints()
                    if point == "RIGHT" then
                        frame.PowerBG:SetPoint("BOTTOMRIGHT", frame, -newXOffset, 0)
                    else
                        frame.PowerBG:SetPoint("BOTTOMLEFT", frame, newXOffset, 0)
                    end
                end
            end

            -- B25: the status indicators are anchored to the Power bar only on
            -- frames that have one (CreatePowerStatus); on the small health-only
            -- frames they hang off Health, and newPowerH is 0 there
            -- (healthHeight == 1), which collapsed them on every ResizeFrames.
            local indicatorH = frame.Power and newPowerH or newHealthH
            if frame.CombatIndicator then frame.CombatIndicator:SetHeight(indicatorH) end
            if frame.LeaderIndicator  then frame.LeaderIndicator:SetHeight(indicatorH)  end
            if frame.PvPIndicator and frame.PvPIndicator.SetAngleVertex then
                frame.PvPIndicator:SetHeight(_G.ceil(newHealthH * 0.65))
            end
            if frame.Classification then
                frame.Classification:SetHeight(_G.ceil(newHealthH * 0.65))
            end

            if frame.AdditionalPower and frame.Power then
                frame.AdditionalPower:ClearAllPoints()
                frame.AdditionalPower:SetPoint("BOTTOMLEFT",  frame.Power, "TOPLEFT",  0, 0)
                frame.AdditionalPower:SetPoint("BOTTOMRIGHT", frame.Power, "TOPRIGHT", -newPowerH, 0)
            end

            if frame.EndBox then
                -- B25: creation geometry (CreateEndBox) is 6 wide / +2 tall only
                -- for the big frames; the small frames use 4 wide at bar height.
                -- ApplySize hardcoded the big variant, so any ResizeFrames call
                -- visibly fattened the small frames' end boxes.
                local healthBox = frame.EndBox[1]
                if healthBox then
                    healthBox:SetSize(unitData.isBig and 6 or 4, newHealthH + (unitData.isBig and 2 or 0))
                    healthBox:ClearAllPoints()
                    if point == "RIGHT" then
                        healthBox:SetPoint("TOPLEFT",  frame.Health, "TOPRIGHT", -(newHealthH - 2), 0)
                    else
                        healthBox:SetPoint("TOPRIGHT", frame.Health, "TOPLEFT",  (newHealthH - 2), 0)
                    end
                end
                local powerBox = frame.EndBox[2]
                if powerBox then
                    powerBox:SetSize(6, newPowerH + 2)
                    powerBox:ClearAllPoints()
                    if point == "RIGHT" then
                        powerBox:SetPoint("BOTTOMLEFT",  frame.Power, "BOTTOMRIGHT", -(newPowerH - 2), 0)
                    else
                        powerBox:SetPoint("BOTTOMRIGHT", frame.Power, "BOTTOMLEFT",  (newPowerH - 2), 0)
                    end
                end
            end
        end
    end

    -- Create CastBars for units that had them before the rewrite
    if (unit == "player" or unit == "target" or unit == "focus") and RealUI:GetModuleEnabled("CastBars") then
        RealUI:GetModule("CastBars"):CreateCastBars(self, unit, unitData)
    end

    -- B24: the smooth-disable exists so a full refresh (unit change, spawn)
    -- snaps to the new unit's values instead of animating from the old ones.
    -- Eventless units (targettarget, focustarget) are updated ONLY by oUF's
    -- 0.5s poll, which also runs UpdateAllElements — with the toggle applied
    -- there too, their bars never smoothed and stepped visibly at 2Hz (the
    -- ToT "blink"). The poll passes the literal event string "OnUpdate";
    -- real refreshes keep the snap.
    function self.PreUpdate(frame, event)
        if isAngled and event ~= "OnUpdate" then
            frame.Health:SetSmooth(false)
            if frame.Power then
                frame.Power:SetSmooth(false)
            end
        end

        if unitData.PreUpdate then
            unitData.PreUpdate(frame, event)
        end
    end

    function self.PostUpdate(frame, event)
        if isAngled then
            if event ~= "OnUpdate" then
                frame.Health:SetSmooth(true)
                if frame.Power then
                    frame.Power:SetSmooth(true)
                end
            end
            frame.EndBox.Update(frame, event)
        end

        if frame.Classification then
            frame.Classification.Update(frame, event)
        end
        if unitData.PostUpdate then
            unitData.PostUpdate(frame, event)
        end
    end

    -- Deferred color kick: the native StatusBar engine for secret values (player
    -- health/power) needs one frame to initialize before colors stick. Force an
    -- oUF update after the first render so bars aren't grey on reload.
    -- Also ensures PostUpdateColor fires for alternative bar style on all units.
    _G.C_Timer.After(0, function()
        if self.Health and self.Health.ForceUpdate then self.Health:ForceUpdate() end
        if self.Power and self.Power.ForceUpdate then self.Power:ForceUpdate() end
    end)
end

function UnitFrames:InitializeLayout()
    db = UnitFrames.db.profile
    ndb = RealUI.db.profile
    oUF:RegisterStyle("RealUI", Shared)
    oUF:Factory(function(...)
        oUF:SetActiveStyle("RealUI")
        for i = 1, #UnitFrames.units do
            UnitFrames.units[i]()
        end
    end)
end
