local pi = math.pi
local sin = math.sin
local cos = math.cos
local abs = math.abs

local function backIn(t, s) s = s or 1.70158; return t * t * ((s + 1) * t - s) end
local function backOut(t, s) s = s or 1.70158; t = t - 1; return 1 + t * t * ((s + 1) * t + s) end
local function backInOut(t, s)
    s = s or 1.70158
    if t < 0.5 then
        local tt = 2 * t
        return (tt * tt * ((s * 1.525 + 1) * tt - s * 1.525)) / 2
    else
        local tt = 2 * t - 2
        return (2 + tt * tt * ((s * 1.525 + 1) * tt + s * 1.525)) / 2
    end
end

-- Bounce (helper)
local function bounceOut(t)
    local n1 = 7.5625
    local d1 = 2.75
    if t < 1 / d1 then
        return n1 * t * t
    elseif t < 2 / d1 then
        t = t - 1.5 / d1
        return n1 * t * t + 0.75
    elseif t < 2.5 / d1 then
        t = t - 2.25 / d1
        return n1 * t * t + 0.9375
    else
        t = t - 2.625 / d1
        return n1 * t * t + 0.984375
    end
end

easing = {} local me = easing

function me.linear(t) return t end

-- Sine
function me.easeInSine(t) return 1 - cos((t * pi) / 2) end
function me.easeOutSine(t) return sin((t * pi) / 2) end
function me.easeInOutSine(t) return -(cos(pi * t) - 1) / 2 end

-- Quad
function me.easeInQuad(t) return t * t end
function me.easeOutQuad(t) return 1 - (1 - t) * (1 - t) end
function me.easeInOutQuad(t)
    if t < 0.5 then return 2 * t * t end
    return 1 - ((-2 * t + 2)^2) / 2
end

-- Cubic
function me.easeInCubic(t) return t * t * t end
function me.easeOutCubic(t) return 1 - ((1 - t)^3) end
function me.easeInOutCubic(t)
    if t < 0.5 then return 4 * t * t * t end
    return 1 - ((-2 * t + 2)^3) / 2
end

-- Expo
function me.easeInExpo(t) return (t == 0) and 0 or (2^(10 * t - 10)) end
function me.easeOutExpo(t) return (t == 1) and 1 or 1 - (2^(-10 * t)) end
function me.easeInOutExpo(t)
    if t == 0 then return 0 end
    if t == 1 then return 1 end
    if t < 0.5 then return (2^(20 * t - 10)) / 2 end
    return (2 - (2^(-20 * t + 10))) / 2
end

-- Back (overshoot)
function me.easeInBack(t) return backIn(t) end
function me.easeOutBack(t) return backOut(t) end
function me.easeInOutBack(t) return backInOut(t) end

-- Elastic
function me.easeInElastic(t)
    if t == 0 then return 0 end
    if t == 1 then return 1 end
    local c4 = (2 * pi) / 3
    return -(2^(10 * t - 10)) * sin((t * 10 - 10.75) * c4)
end
function me.easeOutElastic(t)
    if t == 0 then return 0 end
    if t == 1 then return 1 end
    local c4 = (2 * pi) / 3
    return (2^(-10 * t)) * sin((t * 10 - 0.75) * c4) + 1
end
function me.easeInOutElastic(t)
    if t == 0 then return 0 end
    if t == 1 then return 1 end
    local c5 = (2 * pi) / 4.5
    if t < 0.5 then
        return -((2^(20 * t - 10)) * sin((20 * t - 11.125) * c5)) / 2
    end
    return ((2^(-20 * t + 10)) * sin((20 * t - 11.125) * c5)) / 2 + 1
end

-- Bounce
function me.easeInBounce(t) return 1 - bounceOut(1 - t) end
function me.easeOutBounce(t) return bounceOut(t) end
function me.easeInOutBounce(t)
    if t < 0.5 then return (1 - bounceOut(1 - 2 * t)) / 2 end
    return (1 + bounceOut(2 * t - 1)) / 2
end


tweening = {
    list = {},
} me = tweening

function me.add(from, to, duration, easingFun, callback)
    local tween = {
        from = from,
        to = to,
        duration = math.max(0.000001, duration or 0.001),
        easing = easingFun,
        callback = callback,
        t = 0,
    }
    me.list[#me.list + 1] = tween
    return tween
end

function me.update(dt)
    if #me.list==0 then return end

    -- iterate backwards so we can remove finished tweens safely
    for i = #me.list, 1, -1 do
        local tw = me.list[i]
        tw.t = tw.t + (dt or 0)
        local progress = tw.t / tw.duration
        if progress >= 1 then
            -- finished: ensure final value, call callback with finished=true, remove
            local value = tw.to
            if tw.callback then
                -- callback(value, normalizedProgress, finished)
                tw.callback(value, 1, true)
            end
            table.remove(me.list, i)
        else
            -- in progress
            local alpha = tw.easing(progress)
            local value = tw.from + (tw.to - tw.from) * alpha
            if tw.callback then tw.callback(value, progress, false) end
        end
    end
end

function me.clear()
    for i = #me.list, 1, -1 do table.remove(me.list, i) end
end

-- Optional helper: cancel a specific tween (pass the tween returned by add)
function me.cancel(tween)
    for i = #me.list, 1, -1 do
        if me.list[i] == tween then
            table.remove(me.list, i)
            return true
        end
    end
    return false
end
