local addon, ns = ...
local oUF = ns.oUF or oUF

local cfg
local db = ns.db
local func = ns.func
local unit = ns.unit

local floor, sin, pi = floor, math.sin, math.pi
local format = string.format

-- strip taint (works for non-secret numbers like max health)
local function clean(v)
  if v == nil then return 0 end
  local ok, r = pcall(format, "%.10g", v)
  if ok then return tonumber(r) or 0 end
  return 0
end

---------------------------------------------
-- FUNCTIONS
---------------------------------------------

local initUnitParameters = function(self)
  self:SetFrameStrata("LOW")
  self:SetFrameLevel(1)
  self:SetSize(self.cfg.size, self.cfg.size)
  self:SetScale(self.cfg.scale)
  self:SetPoint(self.cfg.pos.a1, self.cfg.pos.af, self.cfg.pos.a2, self.cfg.pos.x, self.cfg.pos.y)
  self:RegisterForClicks("AnyDown")
  
  -- Custom Tooltip Handler for Target
  self:SetScript("OnEnter", function(frame)
    if UnitExists("targettarget") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("targettarget")
      GameTooltip:Show()
    end
  end)
  
  self:SetScript("OnLeave", function(frame)
    GameTooltip:Hide()
  end)

  -- Kein applyDragFunctionality auf self: Parent ist nur oUF-Container; Drag liegt auf rOrbsToTHealthOrb.
end

-- Update color of spark/galaxies/bubbles/fill (same as player orb)
local function updateStatusBarColor(bar, r, g, b)
  local orb = bar._orb
  if not orb then return end
  if orb.spark then orb.spark:SetVertexColor(r, g, b) end
  if orb.fillTex then orb.fillTex:SetVertexColor(r, g, b) end
  if orb.galaxies then
    for _, galaxy in pairs(orb.galaxies) do galaxy:SetVertexColor(r, g, b) end
  end
  if orb.bubbles then
    for _, bubble in pairs(orb.bubbles) do bubble:SetVertexColor(r, g, b) end
  end
end

-- PostUpdate for health bar
-- SECRET-SAFE: All text rendering uses C-side functions.
-- Threshold checks use StatusBar fill height ratio (pixel-based).
local function postUpdate(bar, unitId, cur, max)
  local orb = bar._orb
  local self = orb:GetParent()

  -- SECRET-SAFE: cur/max are secret values in WoW 12.0+.
  -- NEVER do Lua arithmetic or comparisons on them.
  -- For show/hide logic use only boolean unit state functions.

  local isDead = UnitIsDead("targettarget")
  local exists = UnitExists("targettarget")

  -- Orb fill color: class color for player targets (if enabled), else config color
  local orbcfg = db.char.TOT_HEALTH or db.char.HEALTH
  local r, g, b
  if rOrbsDB and rOrbsDB["totClassColor"] and UnitIsPlayer("targettarget") and not isDead then
    local _, class = UnitClass("targettarget")
    local cc = class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[class]
    r = cc and cc.r or orbcfg.filling.color.r
    g = cc and cc.g or orbcfg.filling.color.g
    b = cc and cc.b or orbcfg.filling.color.b
  else
    r, g, b = orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b
  end
  updateStatusBarColor(bar, r, g, b)

  -- low hp glow + skull
  if isDead then
    orb.lowHP:Show()
    orb.skull:Show()
  else
    ns.UpdateLowHPGlow(orb, "targettarget")
    orb.skull:Hide()
  end

  -- hide/show values: only hide when dead/not exists (never compare secret numeric values)
  local valcfg = (db.char.TOT_HEALTH or db.char.HEALTH).value
  local userTextHide = rOrbsDB and rOrbsDB["textShow_TOT_HEALTH"] == false
  local hoverMode = rOrbsDB and rOrbsDB["textHover_TOT_HEALTH"] == true
  if userTextHide then
    orb.values:Hide()
  elseif hoverMode and not orb.isHovered then
    orb.values:Hide()
  elseif valcfg.hideOnEmpty and (isDead or not exists) then
    orb.values:Hide()
  elseif not hoverMode and not userTextHide and not orb.values:IsShown() then
    orb.values:Show()
  end

  -- Update text via C-side functions only
  local textMode = rOrbsDB and rOrbsDB["textMode_TOT_HEALTH"] or "BOTH"
  local noSign   = rOrbsDB and rOrbsDB["hidePercentSign_TOT_HEALTH"] == true
  if textMode == "PERCENT" then
    ns.RenderPercent(orb.values.top, "targettarget", true, noSign)
    orb.values.bottom:SetText("")
  elseif textMode == "VALUE" then
    ns.RenderAbbreviated(orb.values.top, "targettarget", true)
    orb.values.bottom:SetText("")
  else -- BOTH
    ns.RenderPercent(orb.values.top, "targettarget", true, noSign)
    ns.RenderAbbreviated(orb.values.bottom, "targettarget", true)
  end

  if isDead then
    bar:SetValue(0)
  end

  -- Update target name text
  if orb.nameFrame and orb.nameFrame.text then
    if exists then
      orb.nameFrame.text:SetText(UnitName("targettarget") or "")
      -- Class color for player targets (name only, not orb fill)
      if rOrbsDB and rOrbsDB["totNameClassColor"] and UnitIsPlayer("targettarget") then
        local _, class = UnitClass("targettarget")
        local cc = class and RAID_CLASS_COLORS and RAID_CLASS_COLORS[class]
        if cc then
          orb.nameFrame.text:SetTextColor(cc.r, cc.g, cc.b)
        else
          orb.nameFrame.text:SetTextColor(1, 1, 1)
        end
      elseif not (rOrbsDB and rOrbsDB["totNameColor"]) then
        orb.nameFrame.text:SetTextColor(1, 1, 1)
      end
    else
      orb.nameFrame.text:SetText("")
    end
  end
end

-- Update orb spark – SECRET-SAFE via pcall
local function updateOrb(bar, value)
  local orb = bar._orb
  if not orb or not orb.spark then return end

  if not UnitExists("targettarget") or UnitIsDead("targettarget") then
    if orb.spark:IsShown() then orb.spark:Hide() end
    return
  end

  local ok, hide = pcall(function()
    local min, max = bar:GetMinMaxValues()
    if max <= 0 then return true end
    local ratio = value / max
    return ratio >= 0.97 or ratio <= 0.04
  end)

  if not ok or hide then
    if orb.spark:IsShown() then orb.spark:Hide() end
  else
    if not orb.spark:IsShown() then orb.spark:Show() end
  end
end

-- Create rotating galaxy/bubble texture
local function createGalaxy(parent, x, y, size, duration, texture, sublevel, degree)
  local t = parent:CreateTexture(nil, "ARTWORK", nil, sublevel)
  t:SetSize(size, size)
  t:SetPoint("CENTER", x, y)
  t:SetTexture("Interface\\AddOns\\rOrbs\\media\\"..texture)
  t:SetBlendMode("ADD")
  t.ag = t:CreateAnimationGroup()
  t.ag.anim = t.ag:CreateAnimation("Rotation")
  t.ag.anim:SetDegrees(degree)
  t.ag.anim:SetDuration(duration)
  t.ag:Play()
  t.ag:SetLooping("REPEAT")
  return t
end

---------------------------------------------
-- CREATE ORB
---------------------------------------------

local function createOrb(self)
  local orbcfg = db.char.TOT_HEALTH or db.char.HEALTH
  local name = "rOrbsToTHealthOrb"
  local size = self.cfg.size

  -- BASE FRAME
  local orb = CreateFrame("Button", name, self, "SecureUnitButtonTemplate")
  orb.self = self
  orb.type = "HEALTH"
  orb.size = size
  orb.unit = "targettarget"
  orb:SetSize(size, size)
  
  -- Setze Startposition aus config
  orb:SetPoint(self.cfg.pos.a1, self.cfg.pos.af, self.cfg.pos.a2, self.cfg.pos.x, self.cfg.pos.y)
  orb:RegisterForClicks("AnyDown")
  
  -- Set unit attribute for targettarget
  orb:SetAttribute("unit", "targettarget")
  orb:SetAttribute("*type1", "target")
  orb:SetAttribute("*type2", "togglemenu")
  
  orb.isHovered = false  -- track hover state for text-on-hover feature

  -- Custom Tooltip + hover text show
  orb:SetScript("OnEnter", function(frame)
    frame.isHovered = true
    if frame.values and rOrbsDB and rOrbsDB["textHover_TOT_HEALTH"] then
      frame.values:Show()
    end
    if UnitExists("targettarget") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("targettarget")
      GameTooltip:Show()
    end
  end)

  orb:SetScript("OnLeave", function(frame)
    frame.isHovered = false
    if frame.values and rOrbsDB and rOrbsDB["textHover_TOT_HEALTH"] then
      frame.values:Hide()
    end
    GameTooltip:Hide()
  end)
  
  -- Apply drag functionality
  func.applyDragFunctionality(orb, "orb")
  
  -- Load saved position and scale AFTER applyDragFunctionality
  C_Timer.After(0, function()
    local frameName = orb:GetName()
    
    -- Load saved position
    if frameName and rOrbsDB and rOrbsDB.framePositions and rOrbsDB.framePositions[frameName] then
      local pos = rOrbsDB.framePositions[frameName]
      orb:ClearAllPoints()
      local relFrame = _G[pos.relativeTo] or UIParent
      orb:SetPoint(pos.point, relFrame, pos.relativePoint, pos.x, pos.y)
    end
    
    -- Load saved scale
    if frameName and rOrbsDB and rOrbsDB.frameScales and rOrbsDB.frameScales[frameName] then
      orb:SetScale(rOrbsDB.frameScales[frameName])
    end
  end)

  -- DEBUFF GLOW
  local glow = orb:CreateTexture("$parentGlow", "BACKGROUND", nil, -7)
  glow:SetPoint("CENTER")
  glow:SetSize(size + 5, size + 5)
  glow:SetBlendMode("BLEND")
  glow:SetVertexColor(0, 1, 1, 0)
  glow:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_debuff_glow")
  orb.glow = glow
  self.DebuffHighlight = glow
  self.DebuffHighlightAlpha = 1
  self.DebuffHighlightFilter = false

  -- BACKGROUND (desaturated for neutral empty-state, like player orbs)
  local bg = orb:CreateTexture("$parentBG", "BACKGROUND", nil, -6)
  bg:SetAllPoints()
  bg:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_back2")
  bg:SetDesaturated(true)
  orb.bg = bg

  -- VERTICAL STATUSBAR (like player orb - fillTex visible with db color)
  local fill = CreateFrame("StatusBar", "$parentFill", orb)
  fill:SetAllPoints()
  fill:SetFrameLevel(orb:GetFrameLevel() + 1)
  fill:SetOrientation("VERTICAL")
  fill:SetStatusBarTexture(orbcfg.filling.texture)
  fill:SetStatusBarColor(orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b)
  
  fill:SetMinMaxValues(0, 1)
  fill:SetValue(0)
  fill:EnableMouse(false)
  fill._orb = orb
  orb.fill = fill

  local fillTex = fill:GetStatusBarTexture()
  fillTex:SetDrawLayer("BACKGROUND", 0)
  orb.fillTex = fillTex
  
  fill:SetScript("OnValueChanged", updateOrb)

  -- CLIP FRAME
  local clipFrame = CreateFrame("Frame", "$parentClip", orb)
  clipFrame:SetFrameLevel(orb:GetFrameLevel() + 2)
  clipFrame:SetPoint("BOTTOMLEFT", fill, "BOTTOMLEFT")
  clipFrame:SetPoint("BOTTOMRIGHT", fill, "BOTTOMRIGHT")
  clipFrame:SetPoint("TOP", fillTex, "TOP", 0, 0)
  clipFrame:SetClipsChildren(true)
  clipFrame:EnableMouse(false)
  orb.clipFrame = clipFrame

  local holder = CreateFrame("Frame", nil, clipFrame)
  holder:SetSize(size, size)
  holder:SetPoint("BOTTOM", orb, "BOTTOM")
  holder:EnableMouse(false)

  -- GALAXIES (inside holder, clipped by clipFrame) - wie Player Orb mit size-basierten Größen
  orb.galaxies = {}
  table.insert(orb.galaxies, createGalaxy(holder, 0, 0, size, 120, "galaxy2", 1, 360))
  table.insert(orb.galaxies, createGalaxy(holder, 0, -2, size-20, 90, "galaxy", 2, 360))
  table.insert(orb.galaxies, createGalaxy(holder, 0, -4, size-5, 60, "galaxy4", 3, 360))
  
  -- Set colors and alpha from db config (like player orb)
  for _, galaxy in pairs(orb.galaxies) do
    galaxy:SetVertexColor(orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b)
    galaxy:SetAlpha(orbcfg.galaxies.alpha or 0.4)
    if galaxy.ag then galaxy.ag:Play() end
  end

  -- BUBBLES (inside holder, clipped by clipFrame) - exakt wie Player Orb
  orb.bubbles = {}
  table.insert(orb.bubbles, createGalaxy(holder, 0, 0, size-12, 10, "orb_rotation_bubbles1", 4, -360))
  table.insert(orb.bubbles, createGalaxy(holder, 0, 0, size, 20, "orb_rotation_bubbles2", 5, 360))
  table.insert(orb.bubbles, createGalaxy(holder, 0, 0, size-12, 15, "orb_rotation_bubbles1", 6, 360))
  table.insert(orb.bubbles, createGalaxy(holder, 0, 0, size, 20, "orb_rotation_bubbles2", 5, -360))
  
  -- Set colors and alpha from db config (like player orb)
  for _, bubble in pairs(orb.bubbles) do
    bubble:SetVertexColor(orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b)
    bubble:SetAlpha(orbcfg.bubbles.alpha or 1)
  end

  -- SPARK (maximal Sublevel 7!)
  -- OVERLAY FRAME (above clip - für Gloss, Skull, LowHP, Values)
  -- Must be a separate frame with higher frameLevel so it renders
  -- above the fill StatusBar (frameLevel+1) and clipFrame (frameLevel+2).
  local overlay = CreateFrame("Frame", "$parentOverlay", orb)
  overlay:SetAllPoints(orb)
  overlay:SetFrameLevel(orb:GetFrameLevel() + 10)
  overlay:EnableMouse(false)
  orb.overlay = overlay

  -- SPARK – direct child of clipFrame → SetClipsChildren clips it at the water-line.
  local spark = clipFrame:CreateTexture(nil, "ARTWORK", nil, 7)
  spark:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_spark")
  spark:SetVertexColor(orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b)
  spark:SetSize(size * 0.8, 32 * size / 256)
  spark:SetPoint("TOP", clipFrame, "TOP", 0, (32 * size / 256) / 2)
  spark:SetAlpha(orbcfg.spark and orbcfg.spark.alpha or 1)
  spark:SetBlendMode("ADD")
  spark:Hide()
  orb.spark = spark

  -- LOW HP GLOW
  local lowHP = overlay:CreateTexture(nil, "BACKGROUND", nil, 2)
  lowHP:SetPoint("CENTER")
  lowHP:SetSize(size - 15, size - 15)  -- Wie beim Player Orb
  lowHP:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_lowhp_glow")
  lowHP:SetVertexColor(1, 0, 0, 1)  -- Red like player orb
  lowHP:SetBlendMode("ADD")
  lowHP:Hide()
  orb.lowHP = lowHP

  -- SKULL
  local skull = overlay:CreateTexture(nil, "BACKGROUND", nil, 1)
  skull:SetPoint("CENTER")
  skull:SetSize(size - 40, size - 40)  -- Wie beim Player Orb
  skull:SetTexture("Interface\\AddOns\\rOrbs\\media\\d2_skull")
  skull:SetBlendMode("ADD")  -- Wie beim Player Orb
  skull:SetAlpha(0.6)  -- Wie beim Player Orb
  skull:Hide()
  orb.skull = skull

  -- GLOSS - MUSS GANZ VORNE SEIN!
  -- BACKGROUND Layer with highest sublevel, on overlay frame (like player orb)
  local glossChoice = rOrbsDB and rOrbsDB["glossTex_TOT_HEALTH"] or "new"
  local glossTex = (glossChoice == "classic")
      and "Interface\\AddOns\\rOrbs\\media\\orb_gloss"
      or  "Interface\\AddOns\\rOrbs\\media\\orb_gloss_new"
  local highlight = overlay:CreateTexture("$parentHighlight", "BACKGROUND", nil, 3)
  highlight:SetAllPoints()
  highlight:SetTexture(glossTex)
  orb.highlight = highlight

  -- DECORATIVE RING (nelson_neto_round) – outer wreath/border around the orb
  -- The texture has a transparent center hole of ~80.8% of its radius.
  -- Scale factor 1.05 makes the inner hole edge align just inside the orb
  -- edge (97% of orb radius), giving the framed look from the reference screenshot.
  -- Visibility controlled by rOrbsDB.totRingShow (default: true).
  local ringSize = math.floor(size * 1.05)
  local ring = overlay:CreateTexture("$parentRing", "ARTWORK", nil, 1)
  ring:SetSize(ringSize, ringSize)
  ring:SetPoint("CENTER", orb, "CENTER", 0, 1)
  ring:SetTexture("Interface\\AddOns\\rOrbs\\media\\nelson_neto_round")
  ring:SetBlendMode("BLEND")
  if rOrbsDB and rOrbsDB.totRingShow == false then ring:Hide() end
  orb.ring = ring

  -- TARGET NAME TEXT (above or below orb)
  local nameFrame = CreateFrame("Frame", "$parentNameFrame", overlay)
  nameFrame:SetSize(200, 30)
  nameFrame:SetFrameLevel(overlay:GetFrameLevel() + 2)
  nameFrame:EnableMouse(false)

  local namePos  = rOrbsDB and rOrbsDB["totNamePos"]  or "TOP"
  local nameOffY = rOrbsDB and rOrbsDB["totNameOffY"] or -8
  if namePos == "BOTTOM" then
    nameFrame:SetPoint("TOP", orb, "BOTTOM", 0, nameOffY)
  else
    nameFrame:SetPoint("BOTTOM", orb, "TOP", 0, nameOffY)
  end

  local nameFontName = rOrbsDB and rOrbsDB["font_TOT_HEALTH"] or "Enchanted Land"
  local nameFontSize = rOrbsDB and rOrbsDB["totNameFontSize"] or 22
  local nameText = func.createFontString(nameFrame, nameFontName, nameFontSize, "OUTLINE")
  nameText:SetPoint("CENTER", 0, 0)
  nameText:SetTextColor(1, 1, 1)
  nameText:SetShadowOffset(2, -2)
  nameText:SetShadowColor(0, 0, 0, 0.8)
  nameFrame.text = nameText
  orb.nameFrame = nameFrame

  -- Apply saved name color if set
  if rOrbsDB and rOrbsDB["totNameColor"] then
    local nc = rOrbsDB["totNameColor"]
    nameText:SetTextColor(nc.r, nc.g, nc.b)
  end

  -- Hide if setting is off
  if rOrbsDB and rOrbsDB["totNameShow"] == false then
    nameFrame:Hide()
  end

  -- TEXT VALUES
  local values = CreateFrame("Frame", "$parentValues", overlay)
  values:SetAllPoints(orb)
  values:SetFrameLevel(overlay:GetFrameLevel() + 1)
  values:EnableMouse(false)
  values:SetAlpha(rOrbsDB and rOrbsDB["textAlpha_TOT_HEALTH"] or 0.5)
  orb.values = values

  -- Use saved font/size if available
  local savedFontName = rOrbsDB and rOrbsDB["font_TOT_HEALTH"]
  local fontRef = savedFontName or ns.cfg.fonts.textFont
  local topSize = (rOrbsDB and rOrbsDB["fontSize_TOT_HEALTH"]) or 28
  local botSize = math.max(8, topSize - 12)

  values.top = func.createFontString(values, fontRef, topSize, "THINOUTLINE")
  values.top:SetPoint("CENTER", 0, 10)
  values.top:SetShadowOffset(1, -1)

  values.bottom = func.createFontString(values, fontRef, botSize, "THINOUTLINE")
  values.bottom:SetPoint("CENTER", 0, -10)
  values.bottom:SetShadowOffset(1, -1)

  if orb.dragframe and orb.nameFrame then
    orb.dragframe:SetFrameLevel(orb.nameFrame:GetFrameLevel() + 2)
  end

  return orb
end

---------------------------------------------
-- STYLE FUNCTION
---------------------------------------------

local function createStyle(self)
  self.cfg = cfg.units.targettarget
  if not self.cfg.show then return end
  initUnitParameters(self)
  
  -- Create health orb and store it
  self.rOrbsToTHealthOrb = createOrb(self)
  ns.ToTHealthOrb = self.rOrbsToTHealthOrb  -- expose to GUI
  
  -- Health element (oUF)
  self.Health = self.rOrbsToTHealthOrb.fill
  self.Health.frequentUpdates = self.cfg.health.frequentUpdates
  self.Health.PostUpdate = postUpdate
  self.Health.colorClass = false
  self.Health.colorReaction = false
  self.Health.colorHealth = false

  -- -------------------------------------------------------
  -- ABSORB OVERLAYS – oUF HealthPrediction (Target-of-Target Health Orb)
  -- Damage Absorb : fills UPWARD  from health level (cyan/blue)
  -- Heal   Absorb : fills DOWNWARD from health level (dark red)
  -- -------------------------------------------------------
  if not rOrbs:CheckClientVersion(WOW_PROJECT_CLASSIC)
      and not (self.cfg.absorb and self.cfg.absorb.show == false) then
    local _orb     = self.rOrbsToTHealthOrb
    local _fill    = _orb.fill
    local _fillTex = _orb.fillTex
    local _orbcfg  = db.char.TOT_HEALTH or db.char.HEALTH
    local _size    = self.cfg.size

    -- DAMAGE ABSORB – SetAllPoints: Overlay auf dem fill-Frame selbst
    local damageAbsorb = CreateFrame("StatusBar", nil, _fill)
    damageAbsorb:SetAllPoints(_fill)
    damageAbsorb:SetOrientation("VERTICAL")
    damageAbsorb:SetStatusBarTexture(_orbcfg.filling.texture)
    damageAbsorb:SetStatusBarColor(0, 0.78, 1, 0.7)
    damageAbsorb:SetFrameLevel(_fill:GetFrameLevel() + 2)
    damageAbsorb:SetMinMaxValues(0, 1)
    damageAbsorb:SetValue(0)
    damageAbsorb:EnableMouse(false)
    damageAbsorb:Hide()
    _orb.damageAbsorb = damageAbsorb

    -- HEAL ABSORB – SetAllPoints + ReverseFill
    local healAbsorb = CreateFrame("StatusBar", nil, _fill)
    healAbsorb:SetAllPoints(_fill)
    healAbsorb:SetOrientation("VERTICAL")
    healAbsorb:SetReverseFill(true)
    healAbsorb:SetStatusBarTexture(_orbcfg.filling.texture)
    healAbsorb:SetStatusBarColor(0.65, 0, 0.1, 0.8)
    healAbsorb:SetFrameLevel(_fill:GetFrameLevel() + 2)
    healAbsorb:SetMinMaxValues(0, 1)
    healAbsorb:SetValue(0)
    healAbsorb:EnableMouse(false)
    healAbsorb:Hide()
    _orb.healAbsorb = healAbsorb

    local totAbsorbEventFrame = CreateFrame("Frame")
    totAbsorbEventFrame:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED",     "targettarget")
    totAbsorbEventFrame:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", "targettarget")
    totAbsorbEventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
    totAbsorbEventFrame:SetScript("OnEvent", function(_, event)
      if event == "PLAYER_TARGET_CHANGED" then
        damageAbsorb:Hide(); healAbsorb:Hide()
      elseif event == "UNIT_ABSORB_AMOUNT_CHANGED" then
        if UnitExists("targettarget") and not UnitIsDeadOrGhost("targettarget") then
          damageAbsorb:Show()
        end
      elseif event == "UNIT_HEAL_ABSORB_AMOUNT_CHANGED" then
        if UnitExists("targettarget") and not UnitIsDeadOrGhost("targettarget") then
          healAbsorb:Show()
        end
      end
    end)

    damageAbsorb:Hide()
    healAbsorb:Hide()

    self.HealthPrediction = {
      damageAbsorb = damageAbsorb,
      healAbsorb   = healAbsorb,
      PostUpdate = function(element, unit)
        local da = element.damageAbsorb
        local ha = element.healAbsorb
        local maxHP = UnitHealthMax(unit)
        da:SetMinMaxValues(0, maxHP)
        da:SetValue(UnitGetTotalAbsorbs(unit))
        ha:SetMinMaxValues(0, maxHP)
        ha:SetValue(UnitGetTotalHealAbsorbs(unit))
        if UnitIsDeadOrGhost(unit) then
          da:Hide(); ha:Hide()
        end
      end,
    }
  end

  -- Hook to keep animation colors in sync (use target-specific config)
  hooksecurefunc(self.Health, "SetStatusBarColor", function(bar)
    local orbcfg = db.char.TOT_HEALTH or db.char.HEALTH
    local color = orbcfg.filling.color
    updateStatusBarColor(bar, color.r, color.g, color.b)
  end)
  
  -- Fill is transparent, animations provide color
  self.Health.colorSmooth = false
  self.Health.colorTapping = false
  self.Health.colorDisconnected = false
end

---------------------------------------------
-- INIT
---------------------------------------------

local function Init()
  cfg = ns.cfg
  if cfg.units.targettarget and cfg.units.targettarget.show then
    -- Check if target orb is enabled in settings
    if rOrbsDB and rOrbsDB.totOrbEnabled == false then return end
    db = ns.db
    ns:RegisterInit(function()
      local styleName = "rOrbs: ToT Health"
      
      -- Unregister if exists
      local f = _G["rOrbsToTHealthFrame"]
      if f then
        UnregisterUnitWatch(f)
        f:UnregisterAllEvents()
        for _, child in ipairs({f:GetChildren()}) do
          child:UnregisterAllEvents()
          child:Hide()
        end
        f:Hide()
        f:SetParent(nil)
        _G["rOrbsToTHealthFrame"] = nil
      end
      
      oUF:RegisterStyle(styleName, createStyle)
      oUF:SetActiveStyle(styleName)
      local frame = oUF:Spawn("targettarget", "rOrbsToTHealthFrame")
      frame:UpdateAllElements("ForceUpdate")

      -- We never call frame:Hide() or frame:Show() from event handlers or
      -- timers because rOrbsToTHealthFrame is a secure frame and WoW blocks
      -- Hide/Show on secure frames from addon code during combat lockdown.
      -- Instead we use an alpha-only approach:
      --   * frame is always :Show()n (WoW renders it)
      --   * visibility is controlled exclusively via frame:SetAlpha(0 or 1)
      UnregisterUnitWatch(frame)
      frame:SetAlpha(0)
      frame:Show()   -- keep shown always; alpha=0 makes it invisible
      frame:UpdateAllElements("ForceUpdate")

      local _fading      = false
      local _fadeElapsed = 0
      local _fadeDir     = 0   -- 1 = in, -1 = out
      local _fadeDur     = 0
      local _fadeStartA  = 0
      local _hasToT      = false

      local animator = CreateFrame("Frame", nil, UIParent)
      animator:Hide()
      animator:SetScript("OnUpdate", function(self, dt)
        _fadeElapsed = _fadeElapsed + dt
        local pct = math.min(_fadeElapsed / _fadeDur, 1)
        if _fadeDir == 1 then
          frame:SetAlpha(_fadeStartA + (1 - _fadeStartA) * pct)
          if pct >= 1 then self:Hide() ; _fading = false end
        else
          frame:SetAlpha(_fadeStartA * (1 - pct))
          if pct >= 1 then
            self:Hide()
            _fading = false
            frame:SetAlpha(0)
            -- NO frame:Hide() here -- we never hide the secure frame
          end
        end
      end)

      local function StartFade(dir, dur)
        if _fading then animator:Hide() ; _fading = false end
        _fadeDir     = dir
        _fadeDur     = dur
        _fadeElapsed = 0
        _fadeStartA  = frame:GetAlpha()
        _fading      = true
        animator:Show()
      end

      -- Poller: runs every frame from a safe non-protected OnUpdate context.
      -- UnitExists() and SetAlpha() are always safe to call from addon code.
      -- This replaces the old event handler that called Show()/RegisterUnitWatch()
      -- from a potentially combat-locked execution path.
      local poller = CreateFrame("Frame", nil, UIParent)
      poller:SetScript("OnUpdate", function(self, dt)
        local hasToT = UnitExists("targettarget") and true or false
        if hasToT == _hasToT then return end
        _hasToT = hasToT
        if hasToT then
          StartFade(1, 0.20)
        else
          StartFade(-1, 0.20)
        end
      end)

      -- Set initial alpha based on current targettarget state
      _hasToT = UnitExists("targettarget") and true or false
      frame:SetAlpha(_hasToT and 1 or 0)

      C_Timer.After(0.5, function()
        if frame and frame.UpdateAllElements then
          frame:UpdateAllElements("ForceUpdate")
        end
      end)
    end)
  end
end

ns:RegisterInit(Init)
