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("target") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("target")
      GameTooltip:Show()
    end
  end)
  
  self:SetScript("OnLeave", function(frame)
    GameTooltip:Hide()
  end)

  -- Kein applyDragFunctionality auf self: der Parent-Frame ist nur der oUF-Container (leeres Quadrat).
  -- Verschieben/Skalieren passiert auf rOrbsTargetHealthOrb (siehe createOrb).
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
  bar:SetStatusBarColor(r, g, b)  -- sync C-side StatusBar state (prevents re-apply on SetValue)
  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("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: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
  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("target") or UnitIsDead("target") 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)
  -- 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)
  
  -- 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 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")
  
  -- 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)
  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

  -- 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)
  lowHP:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_lowhp_glow")
  lowHP:SetVertexColor(1, 0, 0, 1)
  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)
  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, 3)
  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

  -- 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["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
  orb.nameFrame = nameFrame

  -- 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)
  self.Health = self.rOrbsTargetHealthOrb.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 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)

  -- 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)
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")

      -- oUF-Wrapper braucht keine Mauseingaben; Orb-Button übernimmt alles.
      frame:EnableMouse(false)

      UnregisterUnitWatch(frame)


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

      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)
        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)

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

ns:RegisterInit(Init)
