local addonName, ns = ...

-- oUF_Diablo / rModelOrbTemplate reference constants (256x256 base orb).
local REF_SIZE = 256
local SPARK_TEX_H = 32
local SPARK_Y_OFF = 16
local SPARK_TEX_PATH = "Interface\\AddOns\\rOrbs\\media\\orb_spark"
local SPARK_MASK_PATH = "Interface\\AddOns\\rOrbs\\media\\orb_spark_mask"

ns.SPARK_TEX_PATH = SPARK_TEX_PATH
ns.SPARK_MASK_PATH = SPARK_MASK_PATH

ns.DiabloOrbRef = {
  size = REF_SIZE,
  sparkTexH = SPARK_TEX_H,
  sparkYOffset = SPARK_Y_OFF,
}

-- FillingStatusBar setup — 1:1 rModelOrbFillingMixin:OnLoad.
-- SetRotatesTexture(false) is critical: the bar texture shrinks from the bottom and its TOP
-- tracks the water-line. Without this the texture stays full orb size and the spark anchor
-- sits at the orb top instead of the fill edge.
function ns.ConfigureDiabloFillingStatusBar(fill)
  if not fill then return end
  fill:SetOrientation("VERTICAL")
  fill:SetRotatesTexture(false)
  fill:SetMinMaxValues(0, 1)
end

-- ClipFrame anchoring — 1:1 rModelOrbOverlayMixin:OnLoad + XML defaults.
function ns.SetupDiabloOrbClipFrame(clipFrame, fill, fillTex)
  if not clipFrame or not fill or not fillTex then return end
  clipFrame:ClearAllPoints()
  clipFrame:SetPoint("BOTTOMLEFT", fill, "BOTTOMLEFT")
  clipFrame:SetPoint("TOPRIGHT", fillTex, "TOPRIGHT")
  clipFrame:SetClipsChildren(true)
end

-- Spark opacity — 1:1 oUF_Diablo / rModelOrbConfig splitAlpha (vertex alpha, not SetAlpha).
function ns.SetDiabloOrbSparkAppearance(spark, r, g, b, splitAlpha)
  if not spark then return end
  if splitAlpha ~= nil then
    spark._splitAlpha = splitAlpha
  end
  local a = spark._splitAlpha or 1
  if r ~= nil then
    spark._sparkR, spark._sparkG, spark._sparkB = r, g, b
  else
    r = spark._sparkR or 1
    g = spark._sparkG or 1
    b = spark._sparkB or 1
  end
  spark:SetAlpha(1)
  spark:SetVertexColor(r, g, b, a)
end

function ns.GetOrbSparkColor(sparkCfg)
  if sparkCfg and sparkCfg.color then
    return sparkCfg.color.r or 1, sparkCfg.color.g or 1, sparkCfg.color.b or 1
  end
  return 1, 1, 1
end

function ns.IsOrbSparkMatchFill(orbType)
  if not orbType then return false end
  if ns.db and ns.db.char and ns.db.char[orbType] and ns.db.char[orbType].spark then
    return ns.db.char[orbType].spark.matchFillColor == true
  end
  if rOrbsDB and rOrbsDB["sparkMatchFill_" .. orbType] ~= nil then
    return rOrbsDB["sparkMatchFill_" .. orbType] == true
  end
  return false
end

function ns.ApplyOrbFillVisualColor(orb, orbType, r, g, b)
  if not orb then return 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
  if orbType and ns.IsOrbSparkMatchFill(orbType) then
    ns.ApplyOrbSparkFromConfig(orb, orbType, r, g, b)
  end
end

-- Apply spark RGB + alpha from db.char[orbType].spark (or fill color when coupled).
function ns.ApplyOrbSparkFromConfig(orb, orbType, fillR, fillG, fillB)
  if not orb or not orb.spark then return end
  local sparkCfg
  if orbType and ns.db and ns.db.char and ns.db.char[orbType] then
    sparkCfg = ns.db.char[orbType].spark
  end
  local r, g, b
  if ns.IsOrbSparkMatchFill(orbType) then
    if fillR ~= nil then
      r, g, b = fillR, fillG, fillB
    elseif orb.fillTex then
      local ok, cr, cg, cb = pcall(orb.fillTex.GetVertexColor, orb.fillTex)
      if ok and cr then
        r, g, b = cr, cg, cb
      else
        local fillCfg = ns.db and ns.db.char and ns.db.char[orbType] and ns.db.char[orbType].filling
        if fillCfg and fillCfg.color then
          r, g, b = fillCfg.color.r, fillCfg.color.g, fillCfg.color.b
        else
          r, g, b = 1, 1, 1
        end
      end
    else
      r, g, b = 1, 1, 1
    end
  else
    r, g, b = ns.GetOrbSparkColor(sparkCfg)
  end
  local a = (sparkCfg and sparkCfg.alpha) or 1
  ns.SetDiabloOrbSparkAppearance(orb.spark, r, g, b, a)
end

-- SparkTexture — 1:1 rModelOrbOverlayMixin:OnLoad (mask, anchor, blend, layer).
function ns.CreateDiabloOrbSpark(overlay, fillTex, size, color, alpha)
  if not overlay or not fillTex then return nil end
  local scale = (size and size > 0) and (size / REF_SIZE) or 1
  local sparkH = SPARK_TEX_H * scale
  local sparkOffY = SPARK_Y_OFF * scale

  local r = color and color.r or 1
  local g = color and color.g or 1
  local b = color and color.b or 1

  local spark = overlay:CreateTexture(nil, "BACKGROUND", nil, -4)
  spark:SetTexture(SPARK_TEX_PATH)
  spark:SetSize(size, sparkH)
  spark:SetBlendMode("ADD")

  local sparkMask = overlay:CreateMaskTexture()
  sparkMask:SetAllPoints(overlay)
  sparkMask:SetTexture(
    SPARK_MASK_PATH,
    "CLAMPTOBLACKADDITIVE",
    "CLAMPTOBLACKADDITIVE"
  )
  spark:AddMaskTexture(sparkMask)

  spark:SetPoint("TOP", fillTex, "TOP", 0, sparkOffY)

  ns.SetDiabloOrbSparkAppearance(spark, r, g, b, alpha or 1)

  spark._sparkMask = sparkMask
  return spark
end

-- Overlay glows — 1:1 rModelOrbTemplate OverlayFrame (GlowTexture + LowHealthTexture).
-- Both use SetAllPoints so the soft halo in orb_glow / orb_glow2 extends past the orb edge.
function ns.CreateDiabloOrbOverlayGlows(overlay)
  if not overlay then return end

  local glow = overlay:CreateTexture(nil, "BACKGROUND", nil, -3)
  glow:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_glow")
  glow:SetAllPoints(overlay)
  glow:SetBlendMode("BLEND")
  glow:SetVertexColor(0.8, 0, 0, 0)

  local lowHP = overlay:CreateTexture(nil, "BACKGROUND", nil, -2)
  lowHP:SetTexture("Interface\\AddOns\\rOrbs\\media\\orb_glow2")
  lowHP:SetAllPoints(overlay)
  lowHP:SetBlendMode("ADD")
  lowHP:SetVertexColor(1, 0, 0, 0)
  lowHP:Show()

  return glow, lowHP
end

-- Read unit fill percent for the visible bar (0..1). Secret-safe via C-side APIs only.
function ns.GetDiabloOrbFillPercent(unit, orbType)
  if not unit or not UnitExists(unit) then return 0 end
  if orbType == "POWER" then
    return UnitPowerPercent(unit, UnitPowerType(unit), true)
  end
  return UnitHealthPercent(unit, true)
end

-- Visible fill drive — 1:1 oUF_Diablo health/power PostUpdate.
function ns.DriveDiabloOrbFill(fill, unit, orbType, smoothing)
  if not fill or not unit or not UnitExists(unit) then return end
  local interp = smoothing or ns.ORB_FILL_SMOOTHING
  local pct = ns.GetDiabloOrbFillPercent(unit, orbType)
  fill:SetMinMaxValues(0, 1)
  fill:SetValue(pct, interp)
end

-- Hidden oUF proxy so the visible fill stays 0..1 like rModelOrbTemplate.FillingStatusBar.
function ns.CreateDiabloOrbOUFProxy(parent, elementName)
  local proxy = CreateFrame("StatusBar", nil, parent)
  proxy:SetSize(1, 1)
  proxy:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
  proxy:Hide()
  parent[elementName] = proxy
  return proxy
end

-- Wire oUF element to visible fill exactly like oUF_Diablo (hidden proxy + PostUpdate drive).
function ns.ConnectDiabloOrbElement(self, elementName, fill, orb, orbType, postUpdate)
  local proxy = ns.CreateDiabloOrbOUFProxy(self, elementName)
  proxy.frequentUpdates = ns.ORB_FILL_FREQUENT_UPDATES and true or false
  proxy.colorClass = false
  proxy.colorHealth = false
  proxy.colorPower = false
  proxy.colorReaction = false
  proxy.colorSmooth = false
  proxy.colorTapping = false
  proxy.colorDisconnected = false

  proxy.PostUpdate = function(_, unit, ...)
    ns.DriveDiabloOrbFill(fill, unit, orbType, ns.ORB_FILL_SMOOTHING)
    if postUpdate then
      postUpdate(fill, unit, ...)
    end
  end

  return proxy
end
