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

-- Buffs: configurable side (default below orb); debuffs right of orb
local TARGET_BUFF_NUM = 8

local function getTargetBuffSide()
  local s = rOrbsDB and rOrbsDB.targetBuffSide
  if s == "top" or s == "bottom" or s == "left" or s == "right" then return s end
  local offY = tonumber(rOrbsDB and rOrbsDB.targetBuffOffY)
  if offY and offY < 0 then return "top" end
  return "bottom"
end

local function getTargetBuffMetrics()
  local size = math.max(8, math.floor(tonumber(rOrbsDB and rOrbsDB.targetBuffSize) or 22))
  local spacing = math.max(0, math.floor(tonumber(rOrbsDB and rOrbsDB.targetBuffSpacing) or 4))
  local scale = tonumber(rOrbsDB and rOrbsDB.targetBuffScale) or 1.0
  if scale < 0.25 then scale = 0.25 elseif scale > 2.5 then scale = 2.5 end
  local offX = tonumber(rOrbsDB and rOrbsDB.targetBuffOffX) or 0
  local offY = tonumber(rOrbsDB and rOrbsDB.targetBuffOffY)
  if offY == nil then offY = 8 end
  local countSz = tonumber(rOrbsDB and rOrbsDB.targetBuffCountFontSize) or 0
  if countSz <= 0 then countSz = size / 1.8 end
  return size, spacing, scale, offX, offY, countSz
end

local function configureTargetBuffGrowth(buffs)
  if not buffs then return end
  local side = getTargetBuffSide()
  local size, spacing = getTargetBuffMetrics()
  local num = buffs.num or TARGET_BUFF_NUM
  buffs.size = size
  buffs.spacing = spacing
  buffs.num = num

  if side == "left" or side == "right" then
    buffs.initialAnchor = side == "left" and "TOPRIGHT" or "TOPLEFT"
    buffs.growthX = side == "left" and "LEFT" or "RIGHT"
    buffs.growthY = "DOWN"
    buffs.maxCols = 1
    buffs:SetSize(size + spacing + 6, (size + spacing) * num + 6)
  else
    buffs.initialAnchor = "TOPLEFT"
    buffs.growthX = "RIGHT"
    buffs.growthY = "DOWN"
    buffs.maxCols = num
    buffs:SetSize((size + spacing) * num + 6, size + spacing + 6)
  end
end

function ns.ApplyTargetBuffButtonStyle(icons, button)
  if not icons or not button then return end
  local size, _, _, _, _, countSz = getTargetBuffMetrics()
  button:SetSize(size, size)
  if button.Count and button.Count.SetFont then
    local fontName = (rOrbsDB and rOrbsDB["font_TARGET_HEALTH"])
      or (rOrbsDB and rOrbsDB["targetNameFont"])
      or (ns.cfg and ns.cfg.fonts and ns.cfg.fonts.textFont)
      or "Enchanted Land"
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    local fontPath = (LSM and LSM:Fetch("font", fontName)) or "Fonts\\FRIZQT__.TTF"
    button.Count:SetFont(fontPath, countSz, "THINOUTLINE")
  end
end

local function restyleTargetBuffButtons(buffs)
  if not buffs then return end
  local num = buffs.num or TARGET_BUFF_NUM
  for i = 1, num do
    local btn = buffs[i]
    if btn then ns.ApplyTargetBuffButtonStyle(buffs, btn) end
  end
end

local function layoutTargetAuraAnchors(orb)
  if not orb or not orb.targetBuffs or not orb.targetDebuffs then return end
  local buffs, debuffs = orb.targetBuffs, orb.targetDebuffs
  local size, spacing, scale, offX, offY = getTargetBuffMetrics()
  local side = getTargetBuffSide()

  configureTargetBuffGrowth(buffs)
  restyleTargetBuffButtons(buffs)

  debuffs:ClearAllPoints()
  debuffs:SetPoint("LEFT", orb, "RIGHT", 8, 0)

  buffs:ClearAllPoints()
  if side == "bottom" then
    buffs:SetPoint("TOPLEFT", orb, "BOTTOMLEFT", offX, -offY)
  elseif side == "top" then
    buffs:SetPoint("BOTTOMLEFT", orb, "TOPLEFT", offX, offY)
  elseif side == "left" then
    buffs:SetPoint("TOPRIGHT", orb, "TOPLEFT", -offX, -offY)
  else
    buffs:SetPoint("TOPLEFT", orb, "TOPRIGHT", offX, -offY)
  end

  buffs:SetScale(scale)
end

function ns.LayoutTargetAuras()
  layoutTargetAuraAnchors(ns.TargetHealthOrb)
end

function ns.ApplyTargetBuffLayout()
  local orb = ns.TargetHealthOrb
  if not orb or not orb.targetBuffs then return end
  layoutTargetAuraAnchors(orb)
  local buffs = orb.targetBuffs
  if buffs.ForceUpdate then buffs:ForceUpdate() end
  if ns.RefreshTargetAurasVisibility then ns.RefreshTargetAurasVisibility() end
end

local function getTargetAurasMode()
  local m = rOrbsDB and rOrbsDB.targetAurasMode
  if m == "off" or m == "mouseover" or m == "always" then return m end
  return "mouseover"
end

function ns.RefreshTargetAurasVisibility()
  local orb = ns.TargetHealthOrb
  if not orb or not orb.targetBuffs or not orb.targetDebuffs then return end
  local buffs, debuffs = orb.targetBuffs, orb.targetDebuffs
  local mode = getTargetAurasMode()
  if mode == "off" then
    buffs:Hide()
    debuffs:Hide()
    return
  end
  if not UnitExists("target") then
    buffs:Hide()
    debuffs:Hide()
    return
  end
  if mode == "always" then
    layoutTargetAuraAnchors(orb)
    buffs:Show()
    debuffs:Show()
    if buffs.ForceUpdate then buffs:ForceUpdate() end
    if debuffs.ForceUpdate then debuffs:ForceUpdate() end
    return
  end
  -- mouseover
  if (orb.IsMouseOver and orb:IsMouseOver())
      or (buffs.IsMouseOver and buffs:IsMouseOver())
      or (debuffs.IsMouseOver and debuffs:IsMouseOver()) then
    layoutTargetAuraAnchors(orb)
    buffs:Show()
    debuffs:Show()
    if buffs.ForceUpdate then buffs:ForceUpdate() end
    if debuffs.ForceUpdate then debuffs:ForceUpdate() end
  else
    buffs:Hide()
    debuffs:Hide()
  end
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("target") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("target")
      GameTooltip:Show()
    end
  end)
  
  self:SetScript("OnLeave", function(frame)
    GameTooltip:Hide()
  end)

  -- No applyDragFunctionality on self: parent is oUF container shell only.
  -- Move/scale on rOrbsTargetHealthOrb (see createOrb).
end

-- Update color of galaxies/bubbles/fill (spark follows fill when matchFillColor is set).
local function updateStatusBarColor(bar, r, g, b)
  local orb = bar._orb
  if not orb then return end
  bar:SetStatusBarColor(r, g, b)
  ns.ApplyOrbFillVisualColor(orb, "TARGET_HEALTH", r, g, b)
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("target")
  local exists = UnitExists("target")

  -- Orb fill color: class color for player targets (if enabled), else config color
  local orbcfg = db.char.TARGET_HEALTH or db.char.HEALTH
  local r, g, b
  if rOrbsDB and rOrbsDB["targetClassColor"] and UnitIsPlayer("target") and not isDead then
    local _, class = UnitClass("target")
    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:SetVertexColor(1, 0, 0, 0.85)
    orb.lowHP:Show()
    orb.skull:Show()
  else
    ns.UpdateLowHPGlow(orb, "target")
    orb.skull:Hide()
  end

  -- elite/boss heads
  if orb.eliteHead then
    if exists and not isDead then
      local classification = UnitClassification("target")
      local level = UnitLevel("target")
      -- level == -1 means "skull" / way-above-player = boss
      -- worldboss is also a boss
      local isBoss = (level == -1) or (classification == "worldboss")
      local isElite = not isBoss and (classification == "elite" or classification == "rareelite" or classification == "rare")
      if isBoss then
        orb.eliteHead:Hide()
        if orb.bossHead then orb.bossHead:Show() end
      elseif isElite then
        orb.eliteHead:Show()
        if orb.bossHead then orb.bossHead:Hide() end
      else
        orb.eliteHead:Hide()
        if orb.bossHead then orb.bossHead:Hide() end
      end
    else
      orb.eliteHead:Hide()
      if orb.bossHead then orb.bossHead:Hide() end
    end
  end

  -- hide/show values: only hide when dead/not exists (never compare secret numeric values)
  local valcfg = (db.char.TARGET_HEALTH or db.char.HEALTH).value
  local userTextHide = rOrbsDB and rOrbsDB["textShow_TARGET_HEALTH"] == false
  local hoverMode = rOrbsDB and rOrbsDB["textHover_TARGET_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_TARGET_HEALTH"] or "BOTH"
  local noSign   = rOrbsDB and rOrbsDB["hidePercentSign_TARGET_HEALTH"] == true
  if textMode == "PERCENT" then
    ns.RenderPercent(orb.values.top, "target", true, noSign)
    orb.values.bottom:SetText("")
  elseif textMode == "VALUE" then
    ns.RenderAbbreviated(orb.values.top, "target", true)
    orb.values.bottom:SetText("")
  else -- BOTH
    ns.RenderPercent(orb.values.top, "target", true, noSign)
    ns.RenderAbbreviated(orb.values.bottom, "target", 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("target") or "")
      -- Apply class color to name if enabled
      if rOrbsDB and rOrbsDB["targetNameClassColor"] and UnitIsPlayer("target") then
        local _, class = UnitClass("target")
        if class then
          local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[class]
          if cc then
            orb.nameFrame.text:SetTextColor(cc.r, cc.g, cc.b)
          end
        end
      elseif not (rOrbsDB and rOrbsDB["targetNameColor"]) then
        orb.nameFrame.text:SetTextColor(1, 1, 1)
      end
    else
      orb.nameFrame.text:SetText("")
    end
    if ns.UpdateTargetNameClassIcon then ns.UpdateTargetNameClassIcon() end
  end

  if ns.UpdateTargetQuestIcon then ns.UpdateTargetQuestIcon() end

  if ns.RefreshTargetAurasVisibility then ns.RefreshTargetAurasVisibility() end

  if ns.RefreshOrbModelVisibility then ns.RefreshOrbModelVisibility(orb) end
end

-- Split spark at the water-line — always visible while the unit is alive,
-- exactly like oUF_Diablo's SparkTexture (follows the fill edge smoothly).
local function updateOrb(bar, value)
  local orb = bar._orb
  if not orb or not orb.spark then return end

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

  if not orb.spark:IsShown() then orb.spark:Show() end
end

-- Create rotating galaxy/bubble texture
local function createGalaxy(parent, x, y, size, duration, texture, sublevel, degree)
  -- Sublevel must be in [-8, 7] (WoW API hard limit)
  if sublevel < -8 then sublevel = -8 elseif sublevel > 7 then sublevel = 7 end
  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.TARGET_HEALTH or db.char.HEALTH
  local name = "rOrbsTargetHealthOrb"
  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 = "target"
  orb:SetSize(size, size)
  
  -- Initial position from 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 target
  orb:SetAttribute("unit", "target")
  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_TARGET_HEALTH"] then
      frame.values:Show()
    end
    if UnitExists("target") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("target")
      GameTooltip:Show()
    end
  end)

  orb:SetScript("OnLeave", function(frame)
    frame.isHovered = false
    if frame.values and rOrbsDB and rOrbsDB["textHover_TARGET_HEALTH"] then
      frame.values:Hide()
    end
    GameTooltip:Hide()
  end)
  
  -- Apply drag functionality
  func.applyDragFunctionality(orb, "orb")

  -- Restore saved scale/position (sync, before WoW user-placed layout can override)
  local frameName = orb:GetName()
  if frameName and rOrbsDB and rOrbsDB.frameScales and rOrbsDB.frameScales[frameName] then
    orb:SetScale(rOrbsDB.frameScales[frameName])
  end
  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)
    orb:SetUserPlaced(false)
  end

  -- 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)
  ns.ConfigureDiabloFillingStatusBar(fill)
  fill:SetStatusBarTexture(orbcfg.filling.texture)
  fill:SetStatusBarColor(orbcfg.filling.color.r, orbcfg.filling.color.g, orbcfg.filling.color.b)
  
  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)
  ns.SetupDiabloOrbClipFrame(clipFrame, fill, fillTex)
  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)
  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
  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)
  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
  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

  -- OVERLAY FRAME (above clip - for Gloss, Skull, LowHP, Values)
  local overlay = CreateFrame("Frame", "$parentOverlay", orb)
  overlay:SetAllPoints(orb)
  overlay:SetFrameLevel(orb:GetFrameLevel() + 10)
  overlay:EnableMouse(false)
  orb.overlay = overlay

  -- DEBUFF + LOW HP GLOW — 1:1 oUF_Diablo (orb_glow / orb_glow2, SetAllPoints).
  local glow, lowHP = ns.CreateDiabloOrbOverlayGlows(overlay)
  orb.glow = glow
  orb.lowHP = lowHP
  self.DebuffHighlight = glow
  self.DebuffHighlightAlpha = 1
  self.DebuffHighlightFilter = false

  local debuffGlowFrame = CreateFrame("Frame")
  debuffGlowFrame:RegisterUnitEvent("UNIT_AURA", "target")
  debuffGlowFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
  debuffGlowFrame:SetScript("OnEvent", function()
    ns.UpdateDebuffTypeGlow(orb, "target")
  end)
  orb._debuffGlowFrame = debuffGlowFrame
  ns.UpdateDebuffTypeGlow(orb, "target")

  -- SPARK ("split") — 1:1 rModelOrbOverlayMixin:OnLoad.
  local sparkColor = orbcfg.spark and orbcfg.spark.color or { r = 1, g = 1, b = 1 }
  local spark = ns.CreateDiabloOrbSpark(
    overlay,
    orb.fillTex,
    size,
    sparkColor,
    orbcfg.spark and orbcfg.spark.alpha or 1
  )
  spark:Hide()
  orb.spark = spark

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

  -- ELITE HEAD (d3_head_skull) - shown for elite/rare/rareelite targets
  local eliteHead = overlay:CreateTexture(nil, "ARTWORK", nil, 2)
  local headW = math.floor(size * 0.62)
  local headH = math.floor(headW * 0.5)  -- 128x64 = 2:1 aspect ratio
  eliteHead:SetSize(headW, headH)
  eliteHead:SetPoint("CENTER", orb, "CENTER", 0, math.floor(size * 0.45))
  eliteHead:SetTexture("Interface\\AddOns\\rOrbs\\media\\d3_head_skull")
  eliteHead:SetBlendMode("BLEND")
  eliteHead:SetAlpha(0.92)
  eliteHead:Hide()
  orb.eliteHead = eliteHead

  -- BOSS HEAD (d3_head_diablo) - shown for worldboss/boss targets
  local bossHead = overlay:CreateTexture(nil, "ARTWORK", nil, 2)
  bossHead:SetSize(headW, headH)
  bossHead:SetPoint("CENTER", orb, "CENTER", 0, math.floor(size * 0.45))
  bossHead:SetTexture("Interface\\AddOns\\rOrbs\\media\\d3_head_diablo")
  bossHead:SetBlendMode("BLEND")
  bossHead:SetAlpha(0.92)
  bossHead:Hide()
  orb.bossHead = bossHead

  -- GLOSS
  local glossChoice = rOrbsDB and rOrbsDB["glossTex_TARGET_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, -1)
  highlight:SetAllPoints()
  highlight:SetTexture(glossTex)
  orb.highlight = highlight

  -- DECORATIVE RING (nelson_neto_round)
  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.targetRingShow == false then ring:Hide() end
  orb.ring = ring

  if rOrbsDB and rOrbsDB.targetRingRotate then
    ring.ag = ring:CreateAnimationGroup()
    ring.ag.anim = ring.ag:CreateAnimation("Rotation")
    local ringSpeed = rOrbsDB.targetRingSpeed or 30
    local ringDir   = rOrbsDB.targetRingDir or "CW"
    ring.ag.anim:SetDegrees(ringDir == "CCW" and 360 or -360)
    ring.ag.anim:SetDuration(ringSpeed)
    ring.ag:SetLooping("REPEAT")
    ring.ag:Play()
  end

  -- 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["targetNamePos"]  or "TOP"
  local nameOffY = rOrbsDB and rOrbsDB["targetNameOffY"] 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["targetNameFont"])
    or (rOrbsDB and rOrbsDB["font_TARGET_HEALTH"])
    or "Enchanted Land"
  local nameFontSize = rOrbsDB and rOrbsDB["targetNameFontSize"] 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

  local classIcon = nameFrame:CreateTexture(nil, "OVERLAY")
  classIcon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
  classIcon:Hide()
  nameFrame.classIcon = classIcon

  orb.nameFrame = nameFrame

  local questSz = rOrbsDB and rOrbsDB.targetQuestIconSize or 18
  local questIcon = nameFrame:CreateTexture(nil, "OVERLAY")
  questIcon:SetSize(questSz, questSz)
  questIcon:SetPoint("LEFT", nameText, "RIGHT",
    -1 + (rOrbsDB and rOrbsDB.targetQuestIconOffX or 0),
    rOrbsDB and rOrbsDB.targetQuestIconOffY or 0)
  questIcon:SetAtlas("QuestNormal")
  questIcon:SetTexCoord(0, 1, 0, 1)
  questIcon:SetDesaturated(false)
  questIcon:SetVertexColor(1, 1, 1)
  questIcon:Hide()
  nameFrame.questIcon = questIcon

  local glowW = questSz * (20 / 14.5)
  local glowH = questSz * (24 / 14.5)
  local questIconGlow = nameFrame:CreateTexture(nil, "OVERLAY", nil, -1)
  questIconGlow:SetSize(glowW, glowH)
  questIconGlow:SetPoint("CENTER", questIcon, "CENTER", 0, 0.5)
  questIconGlow:SetAtlas("UI-QuestPoiCampaign-OuterGlow")
  questIconGlow:Hide()
  nameFrame.questIconGlow = questIconGlow

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

  -- Hide if setting is off
  if rOrbsDB and rOrbsDB["targetNameShow"] == 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_TARGET_HEALTH"] or 0.5)
  orb.values = values

  -- Use saved font/size if available
  local savedFontName = rOrbsDB and rOrbsDB["font_TARGET_HEALTH"]
  local fontRef = savedFontName or ns.cfg.fonts.textFont
  local topSize = (rOrbsDB and rOrbsDB["fontSize_TARGET_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.targetHealth
  if not self.cfg.show then return end
  initUnitParameters(self)
  
  -- Create health orb and store it
  self.rOrbsTargetHealthOrb = createOrb(self)
  ns.TargetHealthOrb = self.rOrbsTargetHealthOrb  -- expose to GUI
  
  -- Health element (oUF) — hidden proxy; visible fill driven like oUF_Diablo PostUpdate.
  local thOrb = self.rOrbsTargetHealthOrb
  local thFill = thOrb.fill
  ns.ConnectDiabloOrbElement(self, "Health", thFill, thOrb, "HEALTH", postUpdate)

  -- -------------------------------------------------------
  -- ABSORB OVERLAYS - oUF HealthPrediction (Target Health Orb)
  -- -------------------------------------------------------
  if not rOrbs:CheckClientVersion(WOW_PROJECT_CLASSIC)
      and not (self.cfg.absorb and self.cfg.absorb.show == false) then
    local _orb     = self.rOrbsTargetHealthOrb
    local _fill    = _orb.fill
    local _fillTex = _orb.fillTex
    local _orbcfg  = db.char.TARGET_HEALTH or db.char.HEALTH
    local _size    = self.cfg.size

    -- DAMAGE ABSORB
    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
    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 tgtAbsorbEventFrame = CreateFrame("Frame")
    tgtAbsorbEventFrame:RegisterUnitEvent("UNIT_ABSORB_AMOUNT_CHANGED",     "target")
    tgtAbsorbEventFrame:RegisterUnitEvent("UNIT_HEAL_ABSORB_AMOUNT_CHANGED", "target")
    tgtAbsorbEventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
    tgtAbsorbEventFrame:SetScript("OnEvent", function(_, event)
      if event == "PLAYER_TARGET_CHANGED" then
        damageAbsorb:Hide(); healAbsorb:Hide()
      elseif event == "UNIT_ABSORB_AMOUNT_CHANGED" then
        if UnitExists("target") and not UnitIsDeadOrGhost("target") then
          damageAbsorb:Show()
        end
      elseif event == "UNIT_HEAL_ABSORB_AMOUNT_CHANGED" then
        if UnitExists("target") and not UnitIsDeadOrGhost("target") 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

  -- Alle oUF-Farb-Flags deaktivieren.
  self.Health.colorClass        = false
  self.Health.colorReaction     = false
  self.Health.colorHealth       = false
  self.Health.colorSmooth       = false
  self.Health.colorTapping      = false
  self.Health.colorDisconnected = false

  -- -------------------------------------------------------
  -- STATUS ICONS (oUF built-in elements)
  -- -------------------------------------------------------
  local orb     = self.rOrbsTargetHealthOrb
  local overlay = orb and orb.overlay
  local orbSize = self.cfg.size
  local iconOff = math.floor(orbSize * 0.55)

  -- -------------------------------------------------------
  -- BUFFS / DEBUFFS (oUF auras): mouseover on orb or icons
  -- Buffs under name, debuffs right of orb
  -- -------------------------------------------------------
  if overlay then
    local auraSize = math.max(8, math.floor(tonumber(rOrbsDB and rOrbsDB.targetBuffSize) or 22))
    local auraPad = math.max(0, math.floor(tonumber(rOrbsDB and rOrbsDB.targetBuffSpacing) or 4))

    local function makeAuraHost(isDebuff)
      local f = CreateFrame("Frame", nil, overlay)
      f:SetFrameStrata(overlay:GetFrameStrata())
      f:SetFrameLevel(overlay:GetFrameLevel() + 4)
      f.size = auraSize
      f.num = isDebuff and 10 or TARGET_BUFF_NUM
      if isDebuff then
        f._rOrbsTargetDebuffHost = true
        f.maxCols = 2
        f:SetSize((auraSize + auraPad) * f.maxCols, (auraSize + auraPad) * 6)
      else
        f._rOrbsTargetBuffHost = true
        f.maxCols = f.num
        f:SetSize((auraSize + auraPad) * f.num, auraSize + auraPad + 6)
      end
      f.initialAnchor = "TOPLEFT"
      f.growthX = "RIGHT"
      f.growthY = "DOWN"
      f.spacing = auraPad
      if isDebuff then
        f.showDebuffType = true
      end
      f.onlyShowPlayer = false
      f.PostCreateButton = function(icons, button)
        func.createAuraIcon(icons, button)
        if icons._rOrbsTargetBuffHost and ns.ApplyTargetBuffButtonStyle then
          ns.ApplyTargetBuffButtonStyle(icons, button)
        end
        if button.RegisterForClicks then
          button:RegisterForClicks()
        end
        if button.SetMouseClickEnabled then
          pcall(function() button:SetMouseClickEnabled(false) end)
        end
      end
      f:EnableMouse(false)
      f:Hide()
      return f
    end

    local buffs = makeAuraHost(false)
    local debuffs = makeAuraHost(true)
    self.Buffs = buffs
    self.Debuffs = debuffs
    orb.targetBuffs = buffs
    orb.targetDebuffs = debuffs

    layoutTargetAuraAnchors(orb)

    local function deferAuraHover()
      if getTargetAurasMode() ~= "mouseover" then return end
      C_Timer.After(0, ns.RefreshTargetAurasVisibility)
    end

    local oldEnter = orb:GetScript("OnEnter")
    local oldLeave = orb:GetScript("OnLeave")
    orb:SetScript("OnEnter", function(o)
      if oldEnter then oldEnter(o) end
      ns.RefreshTargetAurasVisibility()
    end)
    orb:SetScript("OnLeave", function(o)
      if oldLeave then oldLeave(o) end
      deferAuraHover()
    end)

    buffs:SetScript("OnEnter", ns.RefreshTargetAurasVisibility)
    buffs:SetScript("OnLeave", deferAuraHover)
    debuffs:SetScript("OnEnter", ns.RefreshTargetAurasVisibility)
    debuffs:SetScript("OnLeave", deferAuraHover)

    C_Timer.After(0, ns.RefreshTargetAurasVisibility)
  end

  -- COMBAT INDICATOR (crossed swords)
  C_Timer.After(0, function()
    local targetOrb = ns.TargetHealthOrb
    if not targetOrb then return end
    local orbSize = ns.cfg and ns.cfg.units and ns.cfg.units.targetHealth and ns.cfg.units.targetHealth.size or 160
    local iconOff = math.floor(orbSize * 0.55)

    local combatFrame = CreateFrame("Frame", "rOrbsTargetCombatFrame", UIParent)
    combatFrame:SetFrameStrata("MEDIUM")
    combatFrame:SetFrameLevel(60)
    combatFrame:EnableMouse(false)
    do
      local cSz = rOrbsDB.targetCombatIconSize or 28
      combatFrame:SetSize(cSz, cSz)
      combatFrame:SetPoint("CENTER", targetOrb, "CENTER",
        0 + (rOrbsDB.targetCombatIconOffX or 0),
        iconOff + (rOrbsDB.targetCombatIconOffY or 0))
    end
    local combatTex = combatFrame:CreateTexture(nil, "ARTWORK")
    combatTex:SetAllPoints(combatFrame)
    combatTex:SetAtlas("UI-HUD-UnitFrame-Player-CombatIcon")
    ns.TargetCombatIcon = combatFrame
    combatFrame:Hide()

    -- Live-update helper
    ns.UpdateTargetCombatIconLayout = function()
      local f = ns.TargetCombatIcon
      if not f then return end
      local to = ns.TargetHealthOrb
      if not to then return end
      local oSize = ns.cfg and ns.cfg.units and ns.cfg.units.targetHealth and ns.cfg.units.targetHealth.size or 160
      local iOff = math.floor(oSize * 0.55)
      local sz = rOrbsDB.targetCombatIconSize or 28
      f:SetSize(sz, sz)
      f:ClearAllPoints()
      f:SetPoint("CENTER", to, "CENTER",
        0 + (rOrbsDB.targetCombatIconOffX or 0),
        iOff + (rOrbsDB.targetCombatIconOffY or 0))
    end

    local function UpdateTargetCombat()
      if rOrbsDB and rOrbsDB.targetCombatIconShow == false then
        combatFrame:Hide(); return
      end
      if UnitExists("target") and UnitAffectingCombat("target") then
        combatFrame:Show()
      else
        combatFrame:Hide()
      end
    end
    local listener = CreateFrame("Frame", nil, UIParent)
    listener:RegisterEvent("UNIT_FLAGS")
    listener:RegisterEvent("PLAYER_TARGET_CHANGED")
    listener:RegisterEvent("PLAYER_ENTERING_WORLD")
    listener:SetScript("OnEvent", function(self, event, unit)
      if event == "UNIT_FLAGS" and unit ~= "target" then return end
      UpdateTargetCombat()
    end)
    UpdateTargetCombat()
  end)

  -- LEADER INDICATOR (crown) – standalone frame, uses leader.tga
  -- Not registered as self.LeaderIndicator to avoid oUF managing the texture.
  -- We drive visibility ourselves via PARTY_LEADER_CHANGED.
  C_Timer.After(0, function()
    local targetOrb = ns.TargetHealthOrb
    if not targetOrb then return end
    local oSize = ns.cfg and ns.cfg.units and ns.cfg.units.targetHealth and ns.cfg.units.targetHealth.size or 160
    local iOff = math.floor(oSize * 0.55)

    local lFrame = CreateFrame("Frame", "rOrbsTargetLeaderFrame", UIParent)
    lFrame:SetFrameStrata("MEDIUM")
    lFrame:SetFrameLevel(60)
    do
      local lSz = rOrbsDB.targetLeaderIconSize or 22
      lFrame:SetSize(lSz, lSz)
      lFrame:SetPoint("CENTER", targetOrb, "CENTER",
        0 + (rOrbsDB.targetLeaderIconOffX or 0),
        iOff + 14 + (rOrbsDB.targetLeaderIconOffY or 0))
    end
    local lTex = lFrame:CreateTexture(nil, "ARTWORK")
    lTex:SetAllPoints(lFrame)
    lTex:SetTexture(ns.mediapath .. "leader")
    ns.TargetLeaderIcon = lFrame
    lFrame:Hide()

    ns.UpdateTargetLeaderIconLayout = function()
      local f = ns.TargetLeaderIcon
      if not f then return end
      local to = ns.TargetHealthOrb
      if not to then return end
      local oSz = ns.cfg and ns.cfg.units and ns.cfg.units.targetHealth and ns.cfg.units.targetHealth.size or 160
      local iO = math.floor(oSz * 0.55)
      local sz = rOrbsDB.targetLeaderIconSize or 22
      f:SetSize(sz, sz)
      f:ClearAllPoints()
      f:SetPoint("CENTER", to, "CENTER",
        0 + (rOrbsDB.targetLeaderIconOffX or 0),
        iO + 14 + (rOrbsDB.targetLeaderIconOffY or 0))
    end

    local function UpdateTargetLeader()
      if rOrbsDB and rOrbsDB.targetLeaderIconShow == false then
        lFrame:Hide(); return
      end
      if UnitExists("target") and UnitIsPlayer("target") and UnitIsGroupLeader("target") then
        lFrame:Show()
      else
        lFrame:Hide()
      end
    end
    local lListener = CreateFrame("Frame", nil, UIParent)
    lListener:RegisterEvent("PARTY_LEADER_CHANGED")
    lListener:RegisterEvent("GROUP_ROSTER_UPDATE")
    lListener:RegisterEvent("PLAYER_TARGET_CHANGED")
    lListener:RegisterEvent("PLAYER_ENTERING_WORLD")
    lListener:SetScript("OnEvent", function() UpdateTargetLeader() end)
    UpdateTargetLeader()
  end)

  function ns.UpdateTargetNameClassIcon()
    local orb = ns.TargetHealthOrb
    if not orb or not orb.nameFrame or not orb.nameFrame.classIcon or not orb.nameFrame.text then return end
    local icon = orb.nameFrame.classIcon
    local show = not (rOrbsDB and rOrbsDB.targetNameClassIcon == false)
    if not show or not orb.nameFrame:IsShown() or not UnitExists("target") or not UnitIsPlayer("target") then
      icon:Hide()
      return
    end
    local _, class = UnitClass("target")
    if not class then
      icon:Hide()
      return
    end
    local fontSize = (rOrbsDB and rOrbsDB.targetNameFontSize) or 22
    local iconSz = math.max(12, math.floor(fontSize * 0.82))
    icon:SetSize(iconSz, iconSz)
    icon:ClearAllPoints()
    icon:SetPoint("RIGHT", orb.nameFrame.text, "LEFT", -2, 0)
    if ns.ApplyTargetNameClassIconTexture and ns.ApplyTargetNameClassIconTexture(icon, class) then
      icon:Show()
      return
    end
    icon:Hide()
  end

  -- QUEST OBJECTIVE ICON (yellow ! beside target name)
  C_Timer.After(0, function()
    local targetOrb = ns.TargetHealthOrb
    if not targetOrb or not targetOrb.nameFrame or not targetOrb.nameFrame.questIcon then return end

    local function isTargetQuestObjective()
      if not UnitExists("target") then return false end
      if C_QuestLog and C_QuestLog.UnitIsRelatedToActiveQuest then
        local ok, related = pcall(C_QuestLog.UnitIsRelatedToActiveQuest, "target")
        if ok and related then return true end
      end
      local okBoss, isBoss = pcall(UnitIsQuestBoss, "target")
      return okBoss and isBoss
    end

    ns.UpdateTargetQuestIconLayout = function()
      local orb = ns.TargetHealthOrb
      if not orb or not orb.nameFrame or not orb.nameFrame.questIcon or not orb.nameFrame.text then return end
      local icon = orb.nameFrame.questIcon
      local glow = orb.nameFrame.questIconGlow
      local sz = rOrbsDB.targetQuestIconSize or 18
      icon:SetSize(sz, sz)
      icon:ClearAllPoints()
      icon:SetPoint("LEFT", orb.nameFrame.text, "RIGHT",
        -1 + (rOrbsDB.targetQuestIconOffX or 0),
        rOrbsDB.targetQuestIconOffY or 0)
      if glow then
        glow:SetSize(sz * (20 / 14.5), sz * (24 / 14.5))
        glow:ClearAllPoints()
        glow:SetPoint("CENTER", icon, "CENTER", 0, 0.5)
      end
    end

    ns.UpdateTargetQuestIcon = function()
      local orb = ns.TargetHealthOrb
      if not orb or not orb.nameFrame or not orb.nameFrame.questIcon then return end
      local icon = orb.nameFrame.questIcon
      local glow = orb.nameFrame.questIconGlow
      local function hideQuestIcon()
        icon:Hide()
        if glow then glow:Hide() end
      end
      if rOrbsDB and rOrbsDB.targetQuestIconShow == false then
        hideQuestIcon()
        return
      end
      if not orb.nameFrame:IsShown() then
        hideQuestIcon()
        return
      end
      if isTargetQuestObjective() then
        icon:SetAtlas("QuestNormal")
        icon:SetTexCoord(0, 1, 0, 1)
        icon:SetDesaturated(false)
        icon:SetVertexColor(1, 1, 1)
        icon:Show()
        if glow then glow:Hide() end
      else
        hideQuestIcon()
      end
    end

    local qListener = CreateFrame("Frame", nil, UIParent)
    qListener:RegisterEvent("PLAYER_TARGET_CHANGED")
    qListener:RegisterEvent("UNIT_QUEST_LOG_CHANGED")
    qListener:RegisterEvent("QUEST_LOG_UPDATE")
    qListener:RegisterEvent("PLAYER_ENTERING_WORLD")
    qListener:SetScript("OnEvent", function(_, event, unit)
      if event == "UNIT_QUEST_LOG_CHANGED" and unit ~= "target" then return end
      ns.UpdateTargetQuestIcon()
    end)
    ns.UpdateTargetQuestIconLayout()
    ns.UpdateTargetQuestIcon()
    ns.UpdateTargetNameClassIcon()
  end)
end

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

local function Init()
  cfg = ns.cfg
  if cfg.units.targetHealth and cfg.units.targetHealth.show then
    -- Check if target orb is enabled in settings
    if rOrbsDB and rOrbsDB.targetOrbEnabled == false then return end
    db = ns.db
    ns:RegisterInit(function()
      local styleName = "rOrbs: Target Health"
      
      -- Unregister if exists
      local f = _G["rOrbsTargetHealthFrame"]
      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["rOrbsTargetHealthFrame"] = nil
      end
      
      oUF:RegisterStyle(styleName, createStyle)
      oUF:SetActiveStyle(styleName)
      local frame = oUF:Spawn("target", "rOrbsTargetHealthFrame")

      if ns.ClickCast and ns.ClickCast.ApplyAll then
        ns.ClickCast.ApplyAll()
      end

      -- oUF wrapper needs no mouse; orb button handles input.
      frame:EnableMouse(false)

      UnregisterUnitWatch(frame)


      frame:SetAlpha(0)
      frame:Show()
      frame:UpdateAllElements("ForceUpdate")

      if ns.RegisterBlizzardEditModeHost then
        C_Timer.After(0, function()
          local orb = _G["rOrbsTargetHealthOrb"]
          if orb then ns.RegisterBlizzardEditModeHost(orb, "targetHealthOrb", { kind = "targetSubOrb" }) end
        end)
      end

      local _fading      = false
      local _fadeElapsed = 0
      local _fadeDir     = 0
      local _fadeDur     = 0
      local _fadeStartA  = 0
      local _hasTarget   = 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)
          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

      local poller = CreateFrame("Frame", nil, UIParent)
      poller:SetScript("OnUpdate", function(self, dt)
        if frame._rOrbsSkipAlphaFadePoller then return end
        local orbPoll = _G["rOrbsTargetHealthOrb"]
        if orbPoll and orbPoll._rOrbsSkipAlphaFadePoller then return end
        local hasTarget = UnitExists("target") and true or false
        if hasTarget == _hasTarget then return end
        _hasTarget = hasTarget
        if hasTarget then
          StartFade(1, 0.20)
        else
          StartFade(-1, 0.20)
        end
      end)

      _hasTarget = UnitExists("target") and true or false
      frame:SetAlpha(_hasTarget and 1 or 0)

      frame._rOrbsResyncTargetAlpha = function()
        if _fading then
          animator:Hide()
          _fading = false
        end
        _hasTarget = UnitExists("target") and true or false
        local alpha = _hasTarget and 1 or 0
        frame:SetAlpha(alpha)
        local orb = _G["rOrbsTargetHealthOrb"]
        -- Visibility is driven by the oUF shell; orb must inherit (alpha 1).
        if orb then orb:SetAlpha(1) end
      end

      frame._rOrbsDeferTargetAlphaResync = function()
        frame._rOrbsSkipAlphaFadePoller = true
        local orb = _G["rOrbsTargetHealthOrb"]
        if orb then orb._rOrbsSkipAlphaFadePoller = true end
        local delays = { 0, 0.05, 0.15, 0.5 }
        for i = 1, #delays do
          local delay = delays[i]
          C_Timer.After(delay, function()
            if frame and frame._rOrbsResyncTargetAlpha then
              frame._rOrbsResyncTargetAlpha()
            end
          end)
        end
        C_Timer.After(0.6, function()
          if frame then frame._rOrbsSkipAlphaFadePoller = nil end
          if orb then orb._rOrbsSkipAlphaFadePoller = nil end
          if frame and frame._rOrbsResyncTargetAlpha then
            frame._rOrbsResyncTargetAlpha()
          end
        end)
      end

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

ns:RegisterInit(Init)
