-- rOrbs_RunePoints.lua
-- Arc of rune icons around the health orb, one per combo/class point.
-- OnUpdate-based fading. Death Knights get colored runes per rune type.

local addonName, ns = ...

-- ── constants ────────────────────────────────────────────────────────────────
local MAX_RUNES  = 6
local FADE_IN    = 0.20
local FADE_OUT   = 0.15
local ARC_START  = 145
local ARC_END    = 35
local ARC_RADIUS = 0.88
local RUNE_FRAC  = 0.13

-- DK rune colors: Blood=red, Frost=lightblue, Unholy=green, Death=purple
local DK_RUNE_COLORS = {
    [1] = {0.8, 0.1, 0.1},   -- Blood
    [2] = {0.5, 0.8, 1.0},   -- Frost
    [3] = {0.2, 0.8, 0.2},   -- Unholy
    [4] = {0.6, 0.2, 0.9},   -- Death (transformed)
}

local MEDIA_PATH = "Interface\\AddOns\\rOrbs\\media\\"
local RUNE_TEXTURES = {
    MEDIA_PATH.."rune1", MEDIA_PATH.."rune2", MEDIA_PATH.."rune3",
    MEDIA_PATH.."rune4", MEDIA_PATH.."rune5", MEDIA_PATH.."rune6",
}

-- ── class power info ──────────────────────────────────────────────────────────
-- Returns: powerType (or "DK"), max, isDK
local function GetClassPowerInfo()
    local _, class = UnitClass("player")
    if class == "ROGUE" then
        return Enum.PowerType.ComboPoints, UnitPowerMax("player", Enum.PowerType.ComboPoints), false
    elseif class == "DRUID" and GetSpecialization() == 2 then
        return Enum.PowerType.ComboPoints, UnitPowerMax("player", Enum.PowerType.ComboPoints), false
    elseif class == "PALADIN" then
        return Enum.PowerType.HolyPower, UnitPowerMax("player", Enum.PowerType.HolyPower), false
    elseif class == "MONK" and GetSpecialization() == 3 then
        return Enum.PowerType.Chi, UnitPowerMax("player", Enum.PowerType.Chi), false
    elseif class == "WARLOCK" then
        local s = GetSpecialization()
        if s == 1 or s == 3 then
            return Enum.PowerType.SoulShards, UnitPowerMax("player", Enum.PowerType.SoulShards), false
        end
    elseif class == "MAGE" and GetSpecialization() == 1 then
        return Enum.PowerType.ArcaneCharges, UnitPowerMax("player", Enum.PowerType.ArcaneCharges), false
    elseif class == "EVOKER" then
        return Enum.PowerType.Essence, UnitPowerMax("player", Enum.PowerType.Essence), false
    elseif class == "DEATHKNIGHT" then
        return "DK", 6, true
    end
    return nil, 0, false
end

-- ── state ─────────────────────────────────────────────────────────────────────
local initialized  = false
local RunePointsFrame
local runeFrames   = {}
local runeTex      = {}   -- texture reference per rune (for vertex color)
local runeTarget   = {}   -- target alpha
local runeSpeed    = {}   -- alpha/sec (+ = fadein, - = fadeout, 0 = static)
local currentPower = 0
local currentMax   = 0
local currentIsDK  = false

-- ── OnUpdate fade driver ──────────────────────────────────────────────────────
local fadeDriver
local driverActive = false

local function UpdateFades(_, elapsed)
    local stillFading = false
    for i = 1, MAX_RUNES do
        local spd = runeSpeed[i]
        if spd ~= 0 then
            local f     = runeFrames[i]
            local alpha = f:GetAlpha() + spd * elapsed
            if spd > 0 then
                if alpha >= 1 then
                    alpha = 1; runeSpeed[i] = 0; f:SetAlpha(1)
                else
                    stillFading = true; f:SetAlpha(alpha)
                end
            else
                if alpha <= 0 then
                    alpha = 0; runeSpeed[i] = 0; f:SetAlpha(0); f:Hide()
                else
                    stillFading = true; f:SetAlpha(alpha)
                end
            end
        end
    end
    if not stillFading then
        driverActive = false
        fadeDriver:SetScript("OnUpdate", nil)
    end
end

local function EnsureDriver()
    if not driverActive then
        driverActive = true
        fadeDriver:SetScript("OnUpdate", UpdateFades)
    end
end

-- ── rune helpers ──────────────────────────────────────────────────────────────
local function FadeInRune(i)
    -- If already at target=1 and either visible or fading in → skip
    if runeTarget[i] == 1 and runeSpeed[i] >= 0 then return end
    runeTarget[i] = 1
    runeSpeed[i]  = 1 / FADE_IN
    runeFrames[i]:Show()
    EnsureDriver()
end

local function FadeOutRune(i)
    if runeTarget[i] == 0 and runeSpeed[i] <= 0 then return end
    runeTarget[i] = 0
    runeSpeed[i]  = -(1 / FADE_OUT)
    EnsureDriver()
end

local function ShowRune(i)
    -- Only snap to visible if not already heading there
    if runeTarget[i] == 1 then return end
    runeTarget[i] = 1
    runeSpeed[i]  = 0
    runeFrames[i]:SetAlpha(1)
    runeFrames[i]:Show()
end

local function HideRune(i)
    if runeTarget[i] == 0 and runeSpeed[i] == 0 then return end
    runeTarget[i] = 0
    runeSpeed[i]  = 0
    runeFrames[i]:SetAlpha(0)
    runeFrames[i]:Hide()
end

-- ── DK rune colors ────────────────────────────────────────────────────────────
local function UpdateDKColors()
    for i = 1, 6 do
        local runeType = GetRuneType and GetRuneType(i) or 1
        local c = DK_RUNE_COLORS[runeType] or DK_RUNE_COLORS[1]
        runeTex[i]:SetVertexColor(c[1], c[2], c[3], 1)
    end
end

local function ResetColors()
    for i = 1, MAX_RUNES do
        runeTex[i]:SetVertexColor(1, 1, 1, 1)
    end
end

-- ── positioning ───────────────────────────────────────────────────────────────
local function deg2rad(d) return d * math.pi / 180 end

local function PositionRunes(max)
    if not ns.HealthOrb then return end
    local orb     = ns.HealthOrb
    local orbSize = orb:GetWidth()
    if not orbSize or orbSize <= 0 then
        orbSize = (ns.cfg and ns.cfg.units and ns.cfg.units.player and ns.cfg.units.player.size) or 160
    end
    local rs     = math.max(20, math.min(36, math.floor(orbSize * RUNE_FRAC)))
    local radius = (orbSize * 0.5) * ARC_RADIUS
    local span   = ARC_START - ARC_END
    for i = 1, max do
        local frac = (max == 1) and 0.5 or ((i - 1) / (max - 1))
        local ang  = deg2rad(ARC_START - frac * span)
        runeFrames[i]:SetSize(rs, rs)
        runeFrames[i]:ClearAllPoints()
        runeFrames[i]:SetPoint("CENTER", orb, "CENTER", math.cos(ang)*radius, math.sin(ang)*radius)
    end
    for i = max + 1, MAX_RUNES do
        runeFrames[i]:ClearAllPoints()
        HideRune(i)
    end
end

-- ── DK update (runes are per-rune ready/not-ready, not a single power value) ──
local function UpdateDK()
    PositionRunes(6)
    UpdateDKColors()
    local prevPower = currentPower
    local newPower  = 0

    for i = 1, 6 do
        -- GetRuneCooldown: start, duration, runeReady
        local _, _, ready = GetRuneCooldown(i)
        if ready then newPower = newPower + (2^(i-1)) end  -- bitmask per rune
    end

    if newPower == prevPower then return end

    for i = 1, 6 do
        local bit    = 2^(i-1)
        local isReady    = (newPower % (bit*2) >= bit)
        local wasReady   = (prevPower % (bit*2) >= bit)
        if isReady and not wasReady then
            FadeInRune(i)
        elseif not isReady and wasReady then
            FadeOutRune(i)
        elseif isReady then
            ShowRune(i)
        else
            HideRune(i)
        end
    end
    currentPower = newPower
end

-- ── normal class update ───────────────────────────────────────────────────────
local function HideAll()
    for i = 1, MAX_RUNES do HideRune(i) end
    currentPower = 0
end

local function UpdateDisplay(newPower, max)
    if not max or max == 0 then HideAll(); return end
    PositionRunes(max)
    for i = 1, max do
        local wasActive = (i <= currentPower)
        local isActive  = (i <= newPower)
        if isActive and not wasActive then
            FadeInRune(i)
        elseif not isActive and wasActive then
            FadeOutRune(i)
        elseif isActive then
            ShowRune(i)
        else
            HideRune(i)
        end
    end
    for i = max + 1, MAX_RUNES do HideRune(i) end
    currentPower = newPower
end

-- ── main callback ─────────────────────────────────────────────────────────────
local function OnPowerUpdate()
    if rOrbsDB and rOrbsDB.runePointsShow == false then HideAll(); return end

    local powerType, max, isDK = GetClassPowerInfo()
    if not powerType or max == 0 then HideAll(); return end

    -- DK path
    if isDK then
        currentIsDK = true
        currentMax  = 6
        UpdateDK()
        return
    end

    currentIsDK = false
    local new = UnitPower("player", powerType)
    if new == currentPower and max == currentMax then return end
    if max ~= currentMax then
        currentMax   = max
        currentPower = 0
        PositionRunes(max)
    end
    UpdateDisplay(new, max)
end

-- ForceRefresh: ONLY on structural events (login, spec change, enter world).
-- Does NOT instantly hide runes — avoids the visual flash.
local function ForceRefresh()
    local _, class = UnitClass("player")
    -- Reset colors when switching away from DK
    if currentIsDK and class ~= "DEATHKNIGHT" then ResetColors() end
    currentPower = -1
    currentMax   = 0
    currentIsDK  = false
    OnPowerUpdate()
end

-- ── frame creation ────────────────────────────────────────────────────────────
local function CreateRuneFrames()
    for i = 1, MAX_RUNES do
        local f = CreateFrame("Frame", "rOrbsRunePoint"..i, UIParent)
        f:SetFrameStrata("HIGH")
        f:SetFrameLevel(60)
        f:EnableMouse(false)
        f:SetSize(30, 30)
        f:SetAlpha(0)
        f:Hide()
        local tex = f:CreateTexture(nil, "ARTWORK")
        tex:SetAllPoints(f)
        tex:SetTexture(RUNE_TEXTURES[i])
        tex:SetBlendMode("ADD")
        runeFrames[i] = f
        runeTex[i]    = tex
        runeTarget[i] = 0
        runeSpeed[i]  = 0
    end
    fadeDriver = CreateFrame("Frame", "rOrbsFadeDriver", UIParent)
end

-- ── init ─────────────────────────────────────────────────────────────────────
local function Initialize()
    if initialized then return end
    initialized = true
    CreateRuneFrames()

    RunePointsFrame = CreateFrame("Frame", "rOrbsRunePointsFrame", UIParent)

    -- UNIT_POWER_FREQUENT / RUNE_POWER_UPDATE: hot path, no full reset
    RunePointsFrame:RegisterEvent("UNIT_POWER_FREQUENT")
    RunePointsFrame:RegisterEvent("RUNE_POWER_UPDATE")
    -- structural events: full reset
    RunePointsFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    RunePointsFrame:RegisterEvent("PLAYER_TALENT_UPDATE")
    RunePointsFrame:RegisterEvent("UNIT_ENTERED_VEHICLE")
    RunePointsFrame:RegisterEvent("UNIT_EXITED_VEHICLE")
    RunePointsFrame:RegisterEvent("UNIT_DISPLAYPOWER")

    -- NOTE: SPELLS_CHANGED removed — fires constantly during combat, caused flicker

    RunePointsFrame:SetScript("OnEvent", function(self, event, unit)
        if event == "UNIT_POWER_FREQUENT" then
            if unit ~= "player" then return end
            OnPowerUpdate()
        elseif event == "RUNE_POWER_UPDATE" then
            -- DK rune changed
            if currentIsDK then OnPowerUpdate() end
        else
            -- Structural: spec change, login, vehicle
            if event == "UNIT_DISPLAYPOWER" and unit ~= "player" then return end
            ForceRefresh()
        end
    end)

    C_Timer.After(1.0, ForceRefresh)

    ns.RunePoints = {
        ForceRefresh  = ForceRefresh,
        PositionRunes = function()
            local _, max = GetClassPowerInfo()
            PositionRunes(max or currentMax or 0)
        end,
    }
end

local boot = CreateFrame("Frame")
boot:RegisterEvent("PLAYER_LOGIN")
boot:SetScript("OnEvent", function(self)
    self:UnregisterAllEvents()
    C_Timer.After(0.5, Initialize)
end)
