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

  self:SetScript("OnEnter", function(frame)
    if UnitExists("pet") then
      GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
      GameTooltip:SetUnit("pet")
      GameTooltip:Show()
    end
  end)

  self:SetScript("OnLeave", function(frame)
    GameTooltip:Hide()
  end)

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

-- Update color of spark/galaxies/bubbles/fill
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)
local function postUpdate(bar, unitId, cur, max)
  local orb = bar._orb
  local self = orb:GetParent()

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

  local orbcfg = db.char.PET_HEALTH or db.char.HEALTH
  local color = orbcfg.filling.color
  updateStatusBarColor(bar, color.r, color.g, color.b)

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

  -- hide/show text values
  local valcfg = (db.char.PET_HEALTH or db.char.HEALTH).value
  local userTextHide = rOrbsDB and rOrbsDB["textShow_PET_HEALTH"] == false
  local hoverMode    = rOrbsDB and rOrbsDB["textHover_PET_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_PET_HEALTH"] or "BOTH"
  local noSign   = rOrbsDB and rOrbsDB["hidePercentSign_PET_HEALTH"] == true
  if textMode == "PERCENT" then
    ns.RenderPercent(orb.values.top, "pet", true, noSign)
    orb.values.bottom:SetText("")
  elseif textMode == "VALUE" then
    ns.RenderAbbreviated(orb.values.top, "pet", true)
    orb.values.bottom:SetText("")
  else -- BOTH
    ns.RenderPercent(orb.values.top, "pet", true, noSign)
    ns.RenderAbbreviated(orb.values.bottom, "pet", true)
  end

  if isDead then
    bar:SetValue(0)
  end

  -- Update pet name text
  if orb.nameFrame and orb.nameFrame.text then
    if exists then
      orb.nameFrame.text:SetText(UnitName("pet") or "")
      if rOrbsDB and rOrbsDB["petNameColor"] then
        local nc = rOrbsDB["petNameColor"]
        orb.nameFrame.text:SetTextColor(nc.r, nc.g, nc.b)
      else
        orb.nameFrame.text:SetTextColor(1, 1, 1)
      end
    else
      orb.nameFrame.text:SetText("")
    end
  end
end

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

  if not UnitExists("pet") or UnitIsDead("pet") 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.PET_HEALTH or db.char.HEALTH
  local name = "rOrbsPetHealthOrb"
  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 = "pet"
  orb:SetSize(size, size)

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

  orb:SetAttribute("unit", "pet")
  orb:SetAttribute("*type1", "target")
  orb:SetAttribute("*type2", "togglemenu")

  orb.isHovered = false

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

  orb:SetScript("OnLeave", function(frame)
    frame.isHovered = false
    if frame.values and rOrbsDB and rOrbsDB["textHover_PET_HEALTH"] then
      frame.values:Hide()
    end
    GameTooltip:Hide()
  end)

  func.applyDragFunctionality(orb, "orb")

  -- Load saved position and scale
  C_Timer.After(0, function()
    local frameName = orb:GetName()
    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
    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
  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 (oUF element)
  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
  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))
  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
  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))
  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)
  local overlay = CreateFrame("Frame", "$parentOverlay", orb)
  overlay:SetAllPoints(orb)
  overlay:SetFrameLevel(orb:GetFrameLevel() + 10)
  overlay:EnableMouse(false)
  orb.overlay = overlay

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

  -- GLOSS
  local glossChoice = rOrbsDB and rOrbsDB["glossTex_PET_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
  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.petRingShow == false then ring:Hide() end
  orb.ring = ring

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

  local namePos  = rOrbsDB and rOrbsDB["petNamePos"]  or "TOP"
  local nameOffY = rOrbsDB and rOrbsDB["petNameOffY"] 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_PET_HEALTH"] or "Enchanted Land"
  local nameFontSize = rOrbsDB and rOrbsDB["petNameFontSize"] or 18
  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

  if rOrbsDB and rOrbsDB["petNameColor"] then
    local nc = rOrbsDB["petNameColor"]
    nameText:SetTextColor(nc.r, nc.g, nc.b)
  end

  if rOrbsDB and rOrbsDB["petNameShow"] == 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_PET_HEALTH"] or 0.5)
  orb.values = values

  local savedFontName = rOrbsDB and rOrbsDB["font_PET_HEALTH"]
  local fontRef = savedFontName or ns.cfg.fonts.textFont
  local topSize = (rOrbsDB and rOrbsDB["fontSize_PET_HEALTH"]) or 22
  local botSize = math.max(8, topSize - 10)

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

  values.bottom = func.createFontString(values, fontRef, botSize, "THINOUTLINE")
  values.bottom:SetPoint("CENTER", 0, -8)
  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.pet
  if not self.cfg.show then return end
  initUnitParameters(self)

  self.rOrbsPetHealthOrb = createOrb(self)
  ns.PetHealthOrb = self.rOrbsPetHealthOrb

  self.Health = self.rOrbsPetHealthOrb.fill
  self.Health.frequentUpdates = self.cfg.health.frequentUpdates
  self.Health.PostUpdate = postUpdate
  self.Health.colorClass = false
  self.Health.colorReaction = false
  self.Health.colorHealth = false

  hooksecurefunc(self.Health, "SetStatusBarColor", function(bar)
    local orbcfg = db.char.PET_HEALTH or db.char.HEALTH
    local color = orbcfg.filling.color
    updateStatusBarColor(bar, color.r, color.g, color.b)
  end)

  self.Health.colorSmooth = false
  self.Health.colorTapping = false
  self.Health.colorDisconnected = false
end

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

local function Init()
  cfg = ns.cfg
  if cfg.units.pet and cfg.units.pet.show then
    if rOrbsDB and rOrbsDB.petOrbEnabled == false then return end
    db = ns.db
    ns:RegisterInit(function()
      local styleName = "rOrbs: Pet Health"

      local f = _G["rOrbsPetHealthFrame"]
      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["rOrbsPetHealthFrame"] = nil
      end

      oUF:RegisterStyle(styleName, createStyle)
      oUF:SetActiveStyle(styleName)
      local frame = oUF:Spawn("pet", "rOrbsPetHealthFrame")
      frame:UpdateAllElements("ForceUpdate")

      -- Visibility via alpha (never call Hide/Show on secure frame from addon code)
      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 _hasPet      = 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

      -- Poller: checks pet existence each frame safely
      local poller = CreateFrame("Frame", nil, UIParent)
      poller:SetScript("OnUpdate", function(self, dt)
        local hasPet = UnitExists("pet") and true or false
        if hasPet == _hasPet then return end
        _hasPet = hasPet
        if hasPet then
          StartFade(1, 0.20)
          if frame and frame.UpdateAllElements then
            frame:UpdateAllElements("ForceUpdate")
          end
        else
          StartFade(-1, 0.20)
        end
      end)

      _hasPet = UnitExists("pet") and true or false
      frame:SetAlpha(_hasPet 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)
