-- rOrbs: Pet Castbar Ring  v1
--
-- Animated cast ring around pet orb while pet is casting.
-- Same architecture as target-castbar.lua (secret-safe, GetTime-based).

local addon, ns = ...

local mediaPath = "Interface\\AddOns\\rOrbs\\media\\"
local GetTime   = GetTime
local math      = math

-- DB keys (separate from target cast ring)
local DB_ENABLED         = "petCastRingEnabled"
local DB_CAST_R          = "petCastRingCastR"
local DB_CAST_G          = "petCastRingCastG"
local DB_CAST_B          = "petCastRingCastB"
local DB_CHAN_R           = "petCastRingChanR"
local DB_CHAN_G           = "petCastRingChanG"
local DB_CHAN_B           = "petCastRingChanB"
local DB_SPARK_CAST_R    = "petCastRingSparkCastR"
local DB_SPARK_CAST_G    = "petCastRingSparkCastG"
local DB_SPARK_CAST_B    = "petCastRingSparkCastB"
local DB_SPARK_CHAN_R     = "petCastRingSparkChanR"
local DB_SPARK_CHAN_G     = "petCastRingSparkChanG"
local DB_SPARK_CHAN_B     = "petCastRingSparkChanB"
local DB_BG_ALPHA        = "petCastRingBgAlpha"
local DB_BG_R            = "petCastRingBgR"
local DB_BG_G            = "petCastRingBgG"
local DB_BG_B            = "petCastRingBgB"
local DB_BG_MODE         = "petCastRingBgMode"
local DB_FADE_TIME       = "petCastRingFadeTime"
local DB_RING_SCALE      = "petCastRingRingScale"

local DEF = {
    castR=0.2, castG=0.85, castB=0.2,   -- green cast (matches pet orb tint)
    chanR=0.5, chanG=1.0,  chanB=0.3,
    sparkCastR=0.4, sparkCastG=1.0, sparkCastB=0.4,
    sparkChanR=0.6, sparkChanG=1.0, sparkChanB=0.5,
    bgAlpha=0.7, bgR=0.3, bgG=1.0, bgB=0.3,
    bgMode="rose_bright", fadeTime=0.4, ringScale=1.0,
}

local FIXED_OVERSIZE = 1.5
local RING_TEX_SIZE  = 512

local function db(key, default)
    if rOrbsDB and rOrbsDB[key] ~= nil then return rOrbsDB[key] end
    return default
end

local castbarFrame = nil
local bgFrame      = nil
local bgFade = { active=false, fadeIn=false, current=0.0, target=0.0 }

local DEFAULT_DURATION  = 2.0
local learnedCastDur    = DEFAULT_DURATION
local learnedChannelDur = DEFAULT_DURATION

local function InitCastDB()
    if not rOrbsPetCastDB then
        rOrbsPetCastDB = { castDur = DEFAULT_DURATION, channelDur = DEFAULT_DURATION }
    end
    if rOrbsPetCastDB.castDur    and rOrbsPetCastDB.castDur > 0.3    then learnedCastDur    = rOrbsPetCastDB.castDur    end
    if rOrbsPetCastDB.channelDur and rOrbsPetCastDB.channelDur > 0.3 then learnedChannelDur = rOrbsPetCastDB.channelDur end
end

local function GetBestDuration(isChannel)
    return isChannel and learnedChannelDur or learnedCastDur
end

local function LearnDuration(measured, isChannel)
    if measured < 0.3 or measured > 30 then return end
    if isChannel then
        learnedChannelDur = (learnedChannelDur * 4 + measured) / 5
        if rOrbsPetCastDB then rOrbsPetCastDB.channelDur = learnedChannelDur end
    else
        learnedCastDur = (learnedCastDur * 4 + measured) / 5
        if rOrbsPetCastDB then rOrbsPetCastDB.castDur = learnedCastDur end
    end
end

local function GetBarPct(self, isChannel)
    if not self.castStartTime then return nil end
    local elapsed = GetTime() - self.castStartTime
    local dur = self.castDuration or DEFAULT_DURATION
    if dur <= 0 then return nil end
    local pct = elapsed / dur
    if isChannel then pct = 1.0 - pct end
    return math.max(0.0, math.min(1.0, pct))
end

local function ApplyBgColor()
    if not bgFrame then return end
    bgFrame.bgTexture:SetVertexColor(db(DB_BG_R,DEF.bgR), db(DB_BG_G,DEF.bgG), db(DB_BG_B,DEF.bgB))
end

local function StartFadeIn()
    if not bgFrame then return end
    local alpha = db(DB_BG_ALPHA, DEF.bgAlpha)
    bgFade.fadeIn=true; bgFade.active=true; bgFade.target=alpha
    bgFrame:SetAlpha(bgFade.current); bgFrame:Show()
end

local function StartFadeOut()
    if not bgFrame then return end
    bgFade.fadeIn=false; bgFade.active=true; bgFade.target=0.0
end

local function BgOnUpdate(self, elapsed)
    if not bgFade.active then return end
    local speed = 1.0 / math.max(db(DB_FADE_TIME, DEF.fadeTime), 0.05)
    if bgFade.fadeIn then
        bgFade.current = math.min(bgFade.current + elapsed*speed, bgFade.target)
        self:SetAlpha(bgFade.current)
        if bgFade.current >= bgFade.target then bgFade.active=false end
    else
        bgFade.current = math.max(bgFade.current - elapsed*speed, 0)
        self:SetAlpha(bgFade.current)
        if bgFade.current <= 0 then bgFade.active=false; self:Hide() end
    end
end

local function ApplyColors(f, isChannel)
    local rr, rg, rb, sr, sg, sb
    if isChannel then
        rr=db(DB_CHAN_R,DEF.chanR); rg=db(DB_CHAN_G,DEF.chanG); rb=db(DB_CHAN_B,DEF.chanB)
        sr=db(DB_SPARK_CHAN_R,DEF.sparkChanR); sg=db(DB_SPARK_CHAN_G,DEF.sparkChanG); sb=db(DB_SPARK_CHAN_B,DEF.sparkChanB)
    else
        rr=db(DB_CAST_R,DEF.castR); rg=db(DB_CAST_G,DEF.castG); rb=db(DB_CAST_B,DEF.castB)
        sr=db(DB_SPARK_CAST_R,DEF.sparkCastR); sg=db(DB_SPARK_CAST_G,DEF.sparkCastG); sb=db(DB_SPARK_CAST_B,DEF.sparkCastB)
    end
    if f.leftRingTexture  then f.leftRingTexture:SetVertexColor(rr,rg,rb)   end
    if f.rightRingTexture then f.rightRingTexture:SetVertexColor(rr,rg,rb)  end
    if f.leftRingSpark    then f.leftRingSpark:SetVertexColor(sr,sg,sb)     end
    if f.rightRingSpark   then f.rightRingSpark:SetVertexColor(sr,sg,sb)    end
end

local function Disable(self)
    self:SetScript("OnUpdate", nil)
    self:Hide()
    self.isCasting    = false
    self.isChanneling = false
    self.castStartTime = nil
    self.castDuration  = nil
    StartFadeOut()
end

local function OnUpdate(self, elapsed)
    local pct = GetBarPct(self, self.isChanneling)
    if pct == nil then return end
    if pct > 0.5 then
        self.leftRingTexture:SetRotation( math.rad(self.leftRingTexture.baseDeg  - 180*(pct*2-1)))
        self.rightRingTexture:SetRotation(math.rad(self.rightRingTexture.baseDeg - 180))
    else
        self.leftRingTexture:SetRotation( math.rad(self.leftRingTexture.baseDeg))
        self.rightRingTexture:SetRotation(math.rad(self.rightRingTexture.baseDeg - 180*(pct*2)))
    end
    if self.rightRingSpark then
        self.rightRingSpark:SetRotation(math.rad(self.rightRingSpark.baseDeg - 180*(pct*2)))
    end
    if self.leftRingSpark then
        self.leftRingSpark:SetRotation(math.rad(self.leftRingSpark.baseDeg - 180*(pct*2-1)))
    end
end

local function Enable(self, isChannel)
    self.castStartTime = GetTime()
    self.castDuration  = GetBestDuration(isChannel)
    self.isCasting     = not isChannel
    self.isChanneling  = isChannel
    ApplyColors(self, isChannel)
    ApplyBgColor()
    self:Show()
    self:SetScript("OnUpdate", OnUpdate)
    StartFadeIn()
end

local function CheckCasting(self)
    if not UnitExists("pet") then Disable(self); return end

    local name = UnitCastingInfo("pet")
    if name then
        if not self.isCasting then Enable(self, false) end
        return
    end

    name = UnitChannelInfo("pet")
    if name then
        if not self.isChanneling then Enable(self, true) end
        return
    end

    Disable(self)
end

local function OnEvent(self, event, unitArg)
    if unitArg and unitArg ~= "pet" then return end

    if event == "UNIT_SPELLCAST_START" then
        Enable(self, false); return
    end
    if event == "UNIT_SPELLCAST_CHANNEL_START" then
        Enable(self, true); return
    end

    if event == "UNIT_SPELLCAST_DELAYED"
    or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
        return
    end

    -- STOP / FAILED / SUCCEEDED / INTERRUPTED
    if self.castStartTime then
        LearnDuration(GetTime() - self.castStartTime, self.isChanneling)
    end
    Disable(self)
end

local function CreateBgFrame(parent)
    local w = RING_TEX_SIZE
    local f = CreateFrame("Frame", "rOrbsPetCastBg", parent)
    f:SetSize(w,w); f:SetPoint("CENTER", parent, "CENTER", 0, 0)
    f:SetFrameStrata("BACKGROUND"); f:SetFrameLevel(1)
    f:SetAlpha(0); f:Hide()
    local tex = f:CreateTexture(nil, "BACKGROUND", nil, 0)
    tex:SetTexture(mediaPath.."compass-rose-bright")
    tex:SetSize(w,w); tex:SetPoint("CENTER")
    f.bgTexture = tex
    f:SetScript("OnUpdate", BgOnUpdate)
    return f
end

local function CreateRingFrame(parent)
    local w = RING_TEX_SIZE
    local f = CreateFrame("Frame", "rOrbsPetCastRing", parent)
    f:SetSize(w,w); f:SetPoint("CENTER", parent, "CENTER", 0, 0)
    f:SetFrameStrata("HIGH")
    f:SetFrameLevel(parent:GetFrameLevel() + 20)
    f:Hide()

    local sf1 = CreateFrame("ScrollFrame", nil, f)
    sf1:SetSize(w/2,w); sf1:SetPoint("LEFT", f, "LEFT", 0, 0)
    local sc1 = CreateFrame("Frame"); sc1:SetSize(w,w); sf1:SetScrollChild(sc1)
    local rt1 = sc1:CreateTexture(nil,"ARTWORK",nil,1)
    rt1:SetTexture(mediaPath.."compass-rose-ring"); rt1:SetSize(w,w); rt1:SetPoint("CENTER")
    rt1.baseDeg=-180; f.leftRingTexture=rt1
    local rs1 = sc1:CreateTexture(nil,"ARTWORK",nil,2)
    rs1:SetTexture(mediaPath.."compass-rose-spark"); rs1:SetSize(w,w); rs1:SetPoint("CENTER")
    rs1:SetBlendMode("ADD"); rs1.baseDeg=-180; f.leftRingSpark=rs1

    local sf2 = CreateFrame("ScrollFrame", nil, f)
    sf2:SetSize(w/2,w); sf2:SetPoint("RIGHT", f, "RIGHT", 0, 0); sf2:SetHorizontalScroll(w/2)
    local sc2 = CreateFrame("Frame"); sc2:SetSize(w,w); sf2:SetScrollChild(sc2)
    local rt2 = sc2:CreateTexture(nil,"ARTWORK",nil,1)
    rt2:SetTexture(mediaPath.."compass-rose-ring"); rt2:SetSize(w,w); rt2:SetPoint("CENTER")
    rt2.baseDeg=0; f.rightRingTexture=rt2
    local rs2 = sc2:CreateTexture(nil,"ARTWORK",nil,2)
    rs2:SetTexture(mediaPath.."compass-rose-spark"); rs2:SetSize(w,w); rs2:SetPoint("CENTER")
    rs2:SetBlendMode("ADD"); rs2.baseDeg=0; f.rightRingSpark=rs2

    f:RegisterUnitEvent("UNIT_SPELLCAST_START",         "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_STOP",          "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_FAILED",        "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_FAILED_QUIET",  "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED",   "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED",     "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED",       "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE","pet")
    f:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP",  "pet")
    f:SetScript("OnEvent", OnEvent)

    return f
end

local function UpdateRingScale()
    local orbFrame = _G["rOrbsPetHealthOrb"]
    if not orbFrame then return end
    local baseScale = (orbFrame:GetWidth() * FIXED_OVERSIZE) / RING_TEX_SIZE
    local ringMult  = db(DB_RING_SCALE, DEF.ringScale)
    if castbarFrame then castbarFrame:SetScale(baseScale * ringMult) end
    if bgFrame      then bgFrame:SetScale(baseScale) end
end

ns.PetCastRing = {
    ApplyColors  = function()
        if castbarFrame then ApplyColors(castbarFrame, castbarFrame.isChanneling) end
    end,
    ApplyScale   = UpdateRingScale,
    ApplyBgAlpha = function(v)
        if bgFrame and bgFade.current > 0 then
            bgFade.target=v; bgFade.current=v; bgFrame:SetAlpha(v)
        end
    end,
    ApplyBgColor    = ApplyBgColor,
    ApplyBackground = function()
        if db(DB_BG_MODE, DEF.bgMode) ~= "rose_bright" then
            if bgFrame then bgFrame:SetAlpha(0); bgFrame:Hide() end
            bgFade.current=0; bgFade.active=false
        end
    end,
    Rebuild = function()
        if castbarFrame then
            castbarFrame:SetScript("OnUpdate", nil)
            castbarFrame:UnregisterAllEvents()
            castbarFrame:Hide(); castbarFrame=nil
        end
        if bgFrame then bgFrame:SetScript("OnUpdate",nil); bgFrame:Hide(); bgFrame=nil end
        bgFade.current=0; bgFade.active=false
        local orb = _G["rOrbsPetHealthOrb"]
        if orb then
            bgFrame      = CreateBgFrame(orb)
            castbarFrame = CreateRingFrame(orb)
            UpdateRingScale()
        end
    end,
}

local function Init()
    InitCastDB()

    if rOrbsDB then
        local defs = {
            [DB_ENABLED]=true,
            [DB_CAST_R]=DEF.castR,   [DB_CAST_G]=DEF.castG,   [DB_CAST_B]=DEF.castB,
            [DB_CHAN_R]=DEF.chanR,   [DB_CHAN_G]=DEF.chanG,   [DB_CHAN_B]=DEF.chanB,
            [DB_SPARK_CAST_R]=DEF.sparkCastR, [DB_SPARK_CAST_G]=DEF.sparkCastG, [DB_SPARK_CAST_B]=DEF.sparkCastB,
            [DB_SPARK_CHAN_R]=DEF.sparkChanR, [DB_SPARK_CHAN_G]=DEF.sparkChanG, [DB_SPARK_CHAN_B]=DEF.sparkChanB,
            [DB_BG_ALPHA]=DEF.bgAlpha, [DB_BG_R]=DEF.bgR, [DB_BG_G]=DEF.bgG, [DB_BG_B]=DEF.bgB,
            [DB_BG_MODE]=DEF.bgMode, [DB_FADE_TIME]=DEF.fadeTime, [DB_RING_SCALE]=DEF.ringScale,
        }
        for k,v in pairs(defs) do if rOrbsDB[k]==nil then rOrbsDB[k]=v end end
    end

    -- Wait for rOrbsPetHealthOrb (spawned from pet-health.lua)
    local orbFrame = _G["rOrbsPetHealthOrb"]
    if not orbFrame then C_Timer.After(0.5, Init); return end
    if not db(DB_ENABLED, true) then return end

    if castbarFrame then
        castbarFrame:SetScript("OnUpdate", nil)
        castbarFrame:UnregisterAllEvents()
        castbarFrame:Hide(); castbarFrame=nil
    end
    if bgFrame then bgFrame:SetScript("OnUpdate",nil); bgFrame:Hide(); bgFrame=nil end
    bgFade.current=0; bgFade.active=false

    bgFrame      = CreateBgFrame(orbFrame)
    castbarFrame = CreateRingFrame(orbFrame)
    UpdateRingScale()

    local sf = CreateFrame("Frame")
    sf:RegisterEvent("UI_SCALE_CHANGED"); sf:RegisterEvent("PLAYER_ENTERING_WORLD")
    sf:SetScript("OnEvent", UpdateRingScale)

    -- Check bei Login ob Pet gerade wirkt
    C_Timer.After(1.2, function()
        if not castbarFrame or not UnitExists("pet") then return end
        local name = UnitCastingInfo("pet")
        if name then Enable(castbarFrame, false); return end
        name = UnitChannelInfo("pet")
        if name then Enable(castbarFrame, true) end
    end)
end

ns:RegisterInit(function() C_Timer.After(0.3, Init) end)
