-- ══════════════════════════════════════════════════════════════
-- LIB: AnimationHelper - Wiederverwendbare Animations-Utilities
-- Fade, Scale-Lerp, Slide, Easing-Funktionen
-- Kein Modul! Wird von allen Modulen genutzt.
-- ══════════════════════════════════════════════════════════════

local addonName, addon = ...

local AnimHelper = {}
addon.AnimHelper = AnimHelper

-- ── LOCAL API REFS (Performance) ──────────────────────────────
local math_min = math.min
local math_max = math.max
local math_sin = math.sin
local math_pi  = math.pi
local C_Timer  = C_Timer

-- ══════════════════════════════════════════════════════════════
-- EASING FUNKTIONEN
-- ══════════════════════════════════════════════════════════════

function AnimHelper.EaseOutQuad(t)
    return 1 - (1 - t) * (1 - t)
end

function AnimHelper.EaseInQuad(t)
    return t * t
end

function AnimHelper.EaseInOutQuad(t)
    if t < 0.5 then
        return 2 * t * t
    else
        return 1 - (-2 * t + 2)^2 / 2
    end
end

function AnimHelper.EaseOutBack(t)
    local c1 = 1.70158
    local c3 = c1 + 1
    return 1 + c3 * (t - 1)^3 + c1 * (t - 1)^2
end

function AnimHelper.EaseOutBounce(t)
    if t < 1 / 2.75 then
        return 7.5625 * t * t
    elseif t < 2 / 2.75 then
        t = t - 1.5 / 2.75
        return 7.5625 * t * t + 0.75
    elseif t < 2.5 / 2.75 then
        t = t - 2.25 / 2.75
        return 7.5625 * t * t + 0.9375
    else
        t = t - 2.625 / 2.75
        return 7.5625 * t * t + 0.984375
    end
end

-- ══════════════════════════════════════════════════════════════
-- FADE (Alpha-Übergang)
-- ══════════════════════════════════════════════════════════════

-- FadeIn: frame von 0 auf targetAlpha ueber duration Sekunden
function AnimHelper.FadeIn(frame, duration, targetAlpha, onFinish)
    if not frame then return end
    duration    = duration or 0.3
    targetAlpha = targetAlpha or 1.0

    frame:SetAlpha(0)
    frame:Show()

    local elapsed = 0
    frame:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + dt
        local progress = math_min(elapsed / duration, 1)
        self:SetAlpha(targetAlpha * AnimHelper.EaseOutQuad(progress))
        if progress >= 1 then
            self:SetScript("OnUpdate", nil)
            if onFinish then onFinish() end
        end
    end)
end

-- FadeOut: frame von currentAlpha auf 0, dann optional Hide
function AnimHelper.FadeOut(frame, duration, hideOnFinish, onFinish)
    if not frame or not frame:IsShown() then
        if onFinish then onFinish() end
        return
    end
    duration = duration or 0.3
    if hideOnFinish == nil then hideOnFinish = true end

    local startAlpha = frame:GetAlpha()
    local elapsed = 0

    frame:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + dt
        local progress = math_min(elapsed / duration, 1)
        self:SetAlpha(startAlpha * (1 - AnimHelper.EaseInQuad(progress)))
        if progress >= 1 then
            self:SetScript("OnUpdate", nil)
            if hideOnFinish then self:Hide() end
            if onFinish then onFinish() end
        end
    end)
end

-- ══════════════════════════════════════════════════════════════
-- SCALE LERP (Smooth Skalierung)
-- ══════════════════════════════════════════════════════════════

-- Pooled scale jobs - besser als pro-Frame OnUpdate
local scaleJobs = {}
local scaleTicker = CreateFrame("Frame")
scaleTicker:Hide()

scaleTicker:SetScript("OnUpdate", function(_, dt)
    local anyActive = false
    for i = #scaleJobs, 1, -1 do
        local job = scaleJobs[i]
        job.elapsed = job.elapsed + dt
        local progress = math_min(job.elapsed / job.duration, 1)

        local eased
        if job.easing == "OUT" then
            eased = AnimHelper.EaseOutQuad(progress)
        elseif job.easing == "BACK" then
            eased = AnimHelper.EaseOutBack(progress)
        else
            eased = AnimHelper.EaseInQuad(progress)
        end

        local scale = job.fromScale + (job.toScale - job.fromScale) * eased
        job.frame:SetScale(scale)

        if progress >= 1 then
            job.frame:SetScale(job.toScale)
            if job.onFinish then job.onFinish() end
            table.remove(scaleJobs, i)
        else
            anyActive = true
        end
    end
    if not anyActive then scaleTicker:Hide() end
end)

function AnimHelper.ScaleLerp(frame, fromScale, toScale, duration, easing, onFinish)
    if not frame then return end
    duration = duration or 0.2
    easing   = easing or "OUT"

    frame:SetScale(fromScale)

    -- Vorherigen Job fuer diesen Frame entfernen
    for i = #scaleJobs, 1, -1 do
        if scaleJobs[i].frame == frame then
            table.remove(scaleJobs, i)
        end
    end

    scaleJobs[#scaleJobs + 1] = {
        frame     = frame,
        fromScale = fromScale,
        toScale   = toScale,
        duration  = duration,
        elapsed   = 0,
        easing    = easing,
        onFinish  = onFinish,
    }
    scaleTicker:Show()
end

-- ══════════════════════════════════════════════════════════════
-- BOUNCE EFFECT (Scale up + back)
-- ══════════════════════════════════════════════════════════════

function AnimHelper.Bounce(frame, peakScale, duration)
    if not frame then return end
    peakScale = peakScale or 1.08
    duration  = duration or 0.3

    AnimHelper.ScaleLerp(frame, 1.0, peakScale, duration * 0.4, "OUT", function()
        AnimHelper.ScaleLerp(frame, peakScale, 1.0, duration * 0.6, "OUT")
    end)
end

-- ══════════════════════════════════════════════════════════════
-- SLIDE (Position-Übergang)
-- ══════════════════════════════════════════════════════════════

function AnimHelper.SlideIn(frame, direction, distance, duration, onFinish)
    if not frame then return end
    direction = direction or "BOTTOM"
    distance  = distance or 50
    duration  = duration or 0.25

    local point, rel, relPoint, baseX, baseY = frame:GetPoint(1)
    if not point then return end

    local offsetX, offsetY = 0, 0
    if direction == "BOTTOM" then offsetY = -distance
    elseif direction == "TOP" then offsetY = distance
    elseif direction == "LEFT" then offsetX = -distance
    elseif direction == "RIGHT" then offsetX = distance
    end

    frame:ClearAllPoints()
    frame:SetPoint(point, rel, relPoint, baseX + offsetX, baseY + offsetY)
    frame:SetAlpha(0)
    frame:Show()

    local elapsed = 0
    frame:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + dt
        local progress = math_min(elapsed / duration, 1)
        local eased = AnimHelper.EaseOutQuad(progress)

        self:ClearAllPoints()
        self:SetPoint(point, rel, relPoint,
            baseX + offsetX * (1 - eased),
            baseY + offsetY * (1 - eased))
        self:SetAlpha(eased)

        if progress >= 1 then
            self:SetScript("OnUpdate", nil)
            self:ClearAllPoints()
            self:SetPoint(point, rel, relPoint, baseX, baseY)
            self:SetAlpha(1)
            if onFinish then onFinish() end
        end
    end)
end

-- ══════════════════════════════════════════════════════════════
-- UTILITY: Klassenfarbkonvertierung
-- ══════════════════════════════════════════════════════════════

function AnimHelper.ClassColorHex(classId, classColors)
    local c = classColors and classColors[classId]
    if not c then return "ff808080" end
    if type(c) == "string" then return c end
    return string.format("ff%02x%02x%02x",
        math.floor((c[1] or 0.5) * 255),
        math.floor((c[2] or 0.5) * 255),
        math.floor((c[3] or 0.5) * 255))
end
