ZO_ATTRIBUTE_BAR_POWER_SHIELD_NO_HEALING_LEVEL = 1000
ZO_ATTRIBUTE_BAR_POWER_SHIELD_LEVEL = 2000
ZO_ATTRIBUTE_BAR_POWER_SHIELD_TRAUMA_LEVEL = 3000
ZO_ATTRIBUTE_BAR_POWER_SHIELD_TRAUMA_GLOSS_LEVEL = 3001
ZO_ATTRIBUTE_BAR_POWER_SHIELD_FAKE_HEALTH_LEVEL = 4000
ZO_ATTRIBUTE_BAR_POWER_SHIELD_FAKE_HEALTH_GLOSS_LEVEL = 4001
ZO_ATTRIBUTE_BAR_POWER_SHIELD_FAKE_NO_HEALING_OUTER_LEVEL = 5000
ZO_ATTRIBUTE_BAR_POWER_SHIELD_FAKE_NO_HEALING_INNER_LEVEL = 5001

local FULL_ALPHA_VALUE = 1
local FADED_ALPHA_VALUE = 0.3

local RELEVANT_VISUAL_TYPES =
{
    ATTRIBUTE_VISUAL_POWER_SHIELDING,
    ATTRIBUTE_VISUAL_TRAUMA,
    ATTRIBUTE_VISUAL_NO_HEALING,
}

ZO_UnitVisualizer_PowerShieldModule = ZO_UnitAttributeVisualizerModuleBase:Subclass()

function ZO_UnitVisualizer_PowerShieldModule:New(...)
    return ZO_UnitAttributeVisualizerModuleBase.New(self, ...)
end

function ZO_UnitVisualizer_PowerShieldModule:Initialize(layoutData)
    self.layoutData = layoutData
end

function ZO_UnitVisualizer_PowerShieldModule:CreateInfoTable(control, oldInfo, stat, attribute, power)
    if control then
        local info = oldInfo or { visualInfo = {} }

        for _, visualType in ipairs(RELEVANT_VISUAL_TYPES) do
            if not info.visualInfo[visualType] then
                info.visualInfo[visualType] = {}
            end
            local visualInfo = info.visualInfo[visualType]

            visualInfo.value, visualInfo.maxValue = self:GetInitialValueAndMarkMostRecent(visualType, stat, attribute, power)
            if visualInfo.lastValue == nil then
                visualInfo.lastValue = 0
            end
        end

        return info
    end
    return nil
end

function ZO_UnitVisualizer_PowerShieldModule:OnAdded(healthBarControl, magickaBarControl, staminaBarControl)
    self.attributeBarControls =
    {
        [ATTRIBUTE_HEALTH] = healthBarControl,
    }

    if IsPlayerActivated() then
        self:InitializeBarValues()
    end

    local function OnSizeChanged(resizing, bar, size)
        if bar == healthBarControl then
            local info = self.attributeInfo and self.attributeInfo[ATTRIBUTE_HEALTH]
            if info then
                info.isResizing = resizing
            end
        end
    end

    local STARTING_RESIZE = true
    local STOPPING_RESIZE = false
    self:GetOwner():RegisterCallback("AttributeBarSizeChangingStart", function(...) OnSizeChanged(STARTING_RESIZE, ...) end)
    self:GetOwner():RegisterCallback("AttributeBarSizeChangingStopped", function(...) OnSizeChanged(STOPPING_RESIZE, ...) end)

    EVENT_MANAGER:RegisterForEvent("ZO_UnitVisualizer_PowerShieldModule" .. self:GetModuleId(), EVENT_PLAYER_ACTIVATED, function() self:InitializeBarValues() end)
    EVENT_MANAGER:RegisterForUpdate("ZO_UnitVisualizer_PowerShieldModule" .. self:GetModuleId(), 0, function() self:OnUpdate() end)
end

function ZO_UnitVisualizer_PowerShieldModule:InitializeBarValues()
    local healthBarControl = self.attributeBarControls[ATTRIBUTE_HEALTH]

    local oldBarInfo = self.attributeInfo
    self.attributeInfo =
    {
        [ATTRIBUTE_HEALTH] = self:CreateInfoTable(healthBarControl, oldBarInfo and oldBarInfo[ATTRIBUTE_HEALTH], STAT_MITIGATION, ATTRIBUTE_HEALTH, COMBAT_MECHANIC_FLAGS_HEALTH),
    }

    for attribute, bar in pairs(self.attributeBarControls) do
        local barInfo = self.attributeInfo[attribute]
        for visualType, _ in pairs(barInfo.visualInfo) do
            self:OnValueChanged(bar, barInfo, visualType)
        end
    end
end

function ZO_UnitVisualizer_PowerShieldModule:OnUnitChanged()
    self:InitializeBarValues()
end

function ZO_UnitVisualizer_PowerShieldModule:OnUpdate()
    if self.attributeInfo then
        for attribute, info in pairs(self.attributeInfo) do
            if info.isResizing then
                self:UpdateValue(self.attributeBarControls[attribute], info)
            end
        end
    end
end

function ZO_UnitVisualizer_PowerShieldModule:IsUnitVisualRelevant(visualType, stat, attribute, powerType)
    if self.attributeInfo == nil or self.attributeInfo[attribute] == nil then
        return false
    end

    for _, currentVisualType in ipairs(RELEVANT_VISUAL_TYPES) do
        if visualType == currentVisualType then
            return true
        end
    end

    return false
end

function ZO_UnitVisualizer_PowerShieldModule:OnUnitAttributeVisualAdded(visualType, stat, attribute, powerType, value, maxValue)
    local barInfo = self.attributeInfo[attribute]
    local info = barInfo.visualInfo[visualType]
    local barControl = self.attributeBarControls[attribute]
    info.value = info.value + value
    info.maxValue = info.maxValue + maxValue
    self:OnValueChanged(barControl, barInfo, visualType)
    self:DoAlphaUpdate(IsUnitInGroupSupportRange(self:GetUnitTag()))
end

function ZO_UnitVisualizer_PowerShieldModule:OnUnitAttributeVisualUpdated(visualType, stat, attribute, powerType, oldValue, newValue, oldMaxValue, newMaxValue)
    local barInfo = self.attributeInfo[attribute]
    local info = barInfo.visualInfo[visualType]
    info.value = info.value + (newValue - oldValue)
    info.maxValue = info.maxValue + (newMaxValue - oldMaxValue)
    self:OnValueChanged(self.attributeBarControls[attribute], barInfo, visualType)
end

function ZO_UnitVisualizer_PowerShieldModule:OnUnitAttributeVisualRemoved(visualType, stat, attribute, powerType, value, maxValue)
    local barInfo = self.attributeInfo[attribute]
    local info = barInfo.visualInfo[visualType]
    local barControl = self.attributeBarControls[attribute]
    info.value = info.value - value
    info.maxValue = info.maxValue - maxValue
    self:OnValueChanged(barControl, barInfo, visualType)
end

local function ApplyPlatformStyleToShield(left, right, leftOverlay, rightOverlay)
    ApplyTemplateToControl(left, ZO_GetPlatformTemplate(leftOverlay))
    if rightOverlay then
        ApplyTemplateToControl(right, ZO_GetPlatformTemplate(rightOverlay))
    end
end

local LEFT_BAR, RIGHT_BAR = 1, 2
local SHIELD_COLOR_GRADIENT = { ZO_ColorDef:New(.5, .5, 1, .3), ZO_ColorDef:New(.25, .25, .5, .5) }
local TRAUMA_COLOR_GRADIENT = { ZO_ColorDef:New("ab1c6473"), ZO_ColorDef:New("ab76bcc3") }
local NO_HEALING_FILL_COLOR_GRADIENT = { ZO_ColorDef:New("1a0909"), ZO_ColorDef:New("1a0909") }
local NO_HEALING_FILL_GROUP_FRAME_COLOR_GRADIENT = { ZO_ColorDef:New("501212"), ZO_ColorDef:New("501212") }
local NO_HEALING_BORDER_COLOR_GRADIENT = { ZO_ColorDef:New("da3030"), ZO_ColorDef:New("722323") }

function ZO_UnitVisualizer_PowerShieldModule:ShowOverlay(attributeBar, info)
    if not info.overlayControls then
        local leftStatusBar, rightStatusBar = unpack(attributeBar.barControls)

        local shieldLeftOverlay = CreateControlFromVirtual("$(parent)PowerShieldLeftOverlay", attributeBar, self.layoutData.barLeftOverlayTemplate)
        local shieldRightOverlay = (rightStatusBar and self.layoutData.barRightOverlayTemplate) and CreateControlFromVirtual("$(parent)PowerShieldRightOverlay", attributeBar, self.layoutData.barRightOverlayTemplate)

        info.overlayControls = { shieldLeftOverlay, shieldRightOverlay }

        local noHealingFillGradient
        if self.layoutData.noHealingGradientOverride then
            noHealingFillGradient = self.layoutData.noHealingGradientOverride
        elseif rightStatusBar then
            noHealingFillGradient = NO_HEALING_FILL_COLOR_GRADIENT
        else
            noHealingFillGradient = NO_HEALING_FILL_GROUP_FRAME_COLOR_GRADIENT
        end

        local fakeHealthGradient = self.layoutData.fakeHealthGradientOverride or ZO_POWER_BAR_GRADIENT_COLORS[COMBAT_MECHANIC_FLAGS_HEALTH]

        for _, overlay in ipairs(info.overlayControls) do
            ZO_StatusBar_SetGradientColor(overlay, SHIELD_COLOR_GRADIENT)
            ZO_StatusBar_SetGradientColor(overlay.traumaBar, TRAUMA_COLOR_GRADIENT)
            ZO_StatusBar_SetGradientColor(overlay.fakeHealthBar, fakeHealthGradient)
            ZO_StatusBar_SetGradientColor(overlay.noHealingInner, noHealingFillGradient)
            ZO_StatusBar_SetGradientColor(overlay.fakeNoHealingInner, noHealingFillGradient)
            if overlay.noHealingOuter and overlay.fakeNoHealingOuter then
                ZO_StatusBar_SetGradientColor(overlay.noHealingOuter, NO_HEALING_BORDER_COLOR_GRADIENT)
                ZO_StatusBar_SetGradientColor(overlay.fakeNoHealingOuter, NO_HEALING_BORDER_COLOR_GRADIENT)
            end
            overlay:SetValue(1)
        end

        leftStatusBar:SetHandler("OnMinMaxValueChanged", function(_, min, max)
            info.attributeMax = max
            self:OnStatusBarValueChanged(attributeBar, info)
        end, "PowerShield")

        leftStatusBar:SetHandler("OnValueChanged", function(_, value)
            info.attributeValue = value
            self:OnStatusBarValueChanged(attributeBar, info)
        end, "PowerShield")

        info.attributeMax = select(2, leftStatusBar:GetMinMax())
        info.attributeValue = leftStatusBar:GetValue()
    end

    ApplyPlatformStyleToShield(info.overlayControls[LEFT_BAR], info.overlayControls[RIGHT_BAR], self.layoutData.barLeftOverlayTemplate, self.layoutData.barRightOverlayTemplate)

    self:GetOwner():NotifyTakingControlOf(attributeBar)
    self:GetOwner():NotifyEndingControlOf(attributeBar)
end

function ZO_UnitVisualizer_PowerShieldModule:ShouldHideBar(barInfo)
    for _, visualInfo in pairs(barInfo.visualInfo) do
        if visualInfo.value > 0 then
            return false
        end
    end
    return true
end

function ZO_UnitVisualizer_PowerShieldModule:ApplyValueToBar(attributeBar, barInfo, leftControl, rightControl, value)
    local percentOfBarRequested = zo_clamp(value / barInfo.attributeMax, 0, 1.0)
    -- arbitrary hardcoded threshold to avoid "too-small" values
    if percentOfBarRequested <= .01 then
        leftControl:SetHidden(true)
        if rightControl then 
            rightControl:SetHidden(true)
        end
        return
    else
        leftControl:SetHidden(false)
        if rightControl then
            rightControl:SetHidden(false)
        end
    end

    local leftAttributeBar, rightAttributeBar = unpack(attributeBar.barControls)
    local halfWidth = leftAttributeBar:GetWidth()
    local leftOffsetX = halfWidth * (1 - percentOfBarRequested)

    if rightControl and rightAttributeBar then
        leftControl:ClearAnchors()
        leftControl:SetAnchor(LEFT, leftAttributeBar, LEFT, leftOffsetX, 0)
        leftControl:SetAnchor(RIGHT, leftAttributeBar, RIGHT)

        rightControl:ClearAnchors()
        rightControl:SetAnchor(RIGHT, rightAttributeBar, RIGHT, -leftOffsetX, 0)
        rightControl:SetAnchor(LEFT, rightAttributeBar, LEFT)
    else
        -- In the case that we only have a single bar, that bar grows left-to-right.
        leftControl:ClearAnchors()
        leftControl:SetAnchor(RIGHT, leftAttributeBar, RIGHT, -leftOffsetX, 0)
        leftControl:SetAnchor(LEFT, leftAttributeBar, RIGHT, -halfWidth, 0)
    end
end

function ZO_UnitVisualizer_PowerShieldModule:OnStatusBarValueChanged(attributeBar, barInfo)
    local shieldInfo, traumaInfo, noHealingInfo = barInfo.visualInfo[ATTRIBUTE_VISUAL_POWER_SHIELDING], barInfo.visualInfo[ATTRIBUTE_VISUAL_TRAUMA], barInfo.visualInfo[ATTRIBUTE_VISUAL_NO_HEALING]
    local leftOverlay, rightOverlay = unpack(barInfo.overlayControls)
    if not self:ShouldHideBar(barInfo) then
        -- This math just establishes the relationships between each bar: the clamping and scaling to turn these into actual control positions happens in ApplyValueToBar().
        -- Each bar is drawn on top of the last one in the sequence, so the actual amount of each bar the player will see will always be distance between the last bar and the next.

        -- These are the source values: we work a half-scale because we apply one half of the value's magnitude on each side of the total bar.
        -- We don't do this for health because the parent attribute bar provides us with half-values.
        -- The anti-healing status is binary; if its value is positive, the overlay is on, otherwise it's off.
        local health = barInfo.attributeValue
        local shield = shieldInfo.value
        local trauma = traumaInfo.value
        local noHealing = noHealingInfo.value

        -- In the case where we're a brand new visualizer on a unit with an already extant visualized effect, it's possible for us to not have a max health value. In that case, we'll try to grab it from the bar.
        if attributeBar.barControls[ATTRIBUTE_HEALTH].max and barInfo.attributeMax ~= attributeBar.barControls[ATTRIBUTE_HEALTH].max then
            barInfo.attributeMax = attributeBar.barControls[ATTRIBUTE_HEALTH].max
        end

        if rightOverlay then
            shield = shield * .5
            trauma = trauma * .5
        end

        -- Shields add to your original health bar, so they grow out of that value.
        -- When that amount extends beyond your max health we need shrink your fakehealth to compensate, which we carry over as shieldOverflow
        local shieldBarSize = health + shield
        self:ApplyValueToBar(attributeBar, barInfo, leftOverlay, rightOverlay, shieldBarSize)
        local shieldOverflow = zo_max(0, shieldBarSize - barInfo.attributeMax)

        -- Trauma starts at your current health value, minus any shield overflow.
        -- This means that you should perceive the size of this bar as being your "health", it just needs to be overhealed before you can benefit from extra heal.
        local traumaBarSize = health - shieldOverflow
        self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.traumaBar, rightOverlay and rightOverlay.traumaBar, traumaBarSize)

        -- Then the fakehealth starts at the step 2 interpretation of health minus any trauma experienced.
        -- Sometimes trauma and shield overflow will be 0, in which case this value is the same as your actual health, otherwise it shrinks to fit each effect.
        local fakeHealthSize = traumaBarSize - trauma
        self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.fakeHealthBar, rightOverlay and rightOverlay.fakeHealthBar, fakeHealthSize)

        -- The anti-healing overlay always matches the current health value if it's on.
        local noHealingSize = noHealing > 0 and health or 0
        self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.noHealingInner, rightOverlay and rightOverlay.noHealingInner, noHealingSize)
        if leftOverlay.noHealingOuter then
            self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.noHealingOuter, rightOverlay and rightOverlay.noHealingOuter, noHealingSize)
        end

        local fakeNoHealingSize = noHealing > 0 and fakeHealthSize or 0
        self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.fakeNoHealingInner, rightOverlay and rightOverlay.fakeNoHealingInner, fakeNoHealingSize)
        if leftOverlay.fakeNoHealingOuter then
            self:ApplyValueToBar(attributeBar, barInfo, leftOverlay.fakeNoHealingOuter, rightOverlay and rightOverlay.fakeNoHealingOuter, fakeNoHealingSize)
        end
    else
        leftOverlay:SetHidden(true)
        if rightOverlay then
            rightOverlay:SetHidden(true)
        end
    end
end

function ZO_UnitVisualizer_PowerShieldModule:UpdateValue(attributeBar, info)
    if info.overlayControls then
        self:OnStatusBarValueChanged(attributeBar, info)
    end
end

local STATE_GAINED_SOUND_FOR_VISUAL_TYPE =
{
    [ATTRIBUTE_VISUAL_POWER_SHIELDING] = STAT_STATE_SHIELD_GAINED,
    [ATTRIBUTE_VISUAL_TRAUMA] = STAT_STATE_TRAUMA_GAINED,
    --TODO AntiHealing: Add sound for anti-healing?
}

local STATE_LOST_SOUND_FOR_VISUAL_TYPE =
{
    [ATTRIBUTE_VISUAL_POWER_SHIELDING] = STAT_STATE_SHIELD_LOST,
    [ATTRIBUTE_VISUAL_TRAUMA] = STAT_STATE_TRAUMA_LOST,
    --TODO AntiHealing: Add sound for anti-healing?
}

function ZO_UnitVisualizer_PowerShieldModule:OnValueChanged(attributeBar, barInfo, visualType)
    local visualInfo = barInfo.visualInfo[visualType]
    local value = visualInfo.value
    local lastValue = visualInfo.lastValue
    visualInfo.lastValue = value

    if value > 0 and lastValue <= 0 then
        self:ShowOverlay(attributeBar, barInfo)
        self.owner:PlaySoundFromStat(STAT_MITIGATION, STATE_GAINED_SOUND_FOR_VISUAL_TYPE[visualType])
        TriggerTutorial(TUTORIAL_TRIGGER_COMBAT_STATUS_EFFECT)
    elseif value <= 0 and lastValue > 0 then
        self.owner:PlaySoundFromStat(STAT_MITIGATION, STATE_LOST_SOUND_FOR_VISUAL_TYPE[visualType])
    end

    self:UpdateValue(attributeBar, barInfo)
end

function ZO_UnitVisualizer_PowerShieldModule:ApplyPlatformStyle()
    if IsPlayerActivated() then
        for attribute, bar in pairs(self.attributeBarControls) do
            local barInfo = self.attributeInfo and self.attributeInfo[attribute]
            if barInfo then
                if barInfo.overlayControls then
                    ApplyPlatformStyleToShield(barInfo.overlayControls[LEFT_BAR], barInfo.overlayControls[RIGHT_BAR], self.layoutData.barLeftOverlayTemplate, self.layoutData.barRightOverlayTemplate)
                end
                if barInfo.visualInfo then
                    for visualType in pairs(barInfo.visualInfo) do
                        self:OnValueChanged(bar, barInfo, visualType)
                    end
                end
            end
        end
    end
end

function ZO_UnitVisualizer_PowerShieldModule:DoAlphaUpdate(isNearby)
    if IsPlayerActivated() then
        for attribute, bar in ipairs(self.attributeBarControls) do
            local barInfo = self.attributeInfo and self.attributeInfo[attribute]
            if barInfo and barInfo.overlayControls then
                for _, overlay in pairs(barInfo.overlayControls) do
                    local alpha = isNearby and FULL_ALPHA_VALUE or FADED_ALPHA_VALUE
                    overlay:SetAlpha(alpha)
                end
            end
        end
    end
end