local easing_lib = require "lib.utils.easing"
local lerp = require "lib.utils.utils".lerp

--- Обобщенная асинхронная функция (Task).
--- По сути это функция, принимающая коллбэк: `fun(callback: fun(value: T): nil): nil`
--- @generic T
--- @alias Task fun(callback: fun(value: T): nil): nil

local task = {}
local activeTweens = {} -- list of tweens
-- We also need a way to track tweens by target to support cancellation/replacement
-- Let's stick to a simple list for update, but maybe add a helper to find by target?
-- Or, just allow multiple tweens per target (which is valid for different properties).
-- But if we animate the SAME property, we have a conflict.
-- For now, let's just add task.cancel(target) which kills ALL tweens for that target.

--- Обновление всех активных анимаций (твинов).
--- Нужно вызывать в love.update(dt)
function task.update(dt)
    for i = #activeTweens, 1, -1 do
        local t = activeTweens[i]
        if not t.cancelled then
            t.elapsed = t.elapsed + dt * 1000
            local progress = math.min(t.elapsed / t.duration, 1)
            local value = t.easing(progress)

            for key, targetValue in pairs(t.properties) do
                t.target[key] = lerp(t.initial[key], targetValue, value)
            end

            if progress >= 1 then
                t.completed = true
                table.remove(activeTweens, i)
                if t.resolve then t.resolve() end
            end
        else
            table.remove(activeTweens, i)
        end
    end
end

--- Возвращает Completer — объект, который позволяет вручную завершить таску.
--- @generic T
--- @return { complete: fun(val: T) }, Task<T> future
function task.completer()
    local c = { completed = false, value = nil, cb = nil }
    function c:complete(val)
        if self.completed then return end
        self.completed = true
        self.value = val
        if self.cb then self.cb(val) end
    end

    local future = function(callback)
        if c.completed then
            callback(c.value)
        else
            c.cb = callback
        end
    end
    return c, future
end

--- Отменяет все активные твины для указанного объекта.
--- @param target table
function task.cancel(target)
    for _, t in ipairs(activeTweens) do
        if t.target == target then
            t.cancelled = true
        end
    end
end

--- Создает таску, которая плавно меняет свойства объекта.
--- Автоматически отменяет предыдущие твины для этого объекта (чтобы не было конфликтов).
--- @param target table Объект, свойства которого меняем
--- @param properties table Набор конечных значений { key = value }
--- @param duration number Длительность в мс
--- @param easing function? Функция смягчения (по умолчанию linear)
--- @return Task<nil>
function task.tween(target, properties, duration, easing)
    task.cancel(target) -- Cancel previous animations on this target

    local initial = {}
    for k, _ in pairs(properties) do
        initial[k] = target[k]
        if type(initial[k]) == "table" and initial[k].copy then
            initial[k] = initial[k]:copy() -- Для Vec3
        end
    end

    local comp, future = task.completer()
    table.insert(activeTweens, {
        target = target,
        initial = initial,
        properties = properties,
        duration = duration or 1000,
        easing = easing or easing_lib.linear,
        elapsed = 0,
        resolve = function() comp:complete() end
    })
    return future
end

--- Возвращает таску, которая завершится сразу с переданным значением.
function task.fromValue(val)
    return function(callback) callback(val) end
end

--- Возвращает новый Task, который завершится после завершения всех переданных `tasks`.
--- @param tasks Task[]
--- @return Task<any[]>
function task.wait(tasks)
    if #tasks == 0 then return task.fromValue({}) end
    local count = #tasks
    local results = {}

    return function(callback)
        for i, t in ipairs(tasks) do
            t(function(result)
                results[i] = result
                count = count - 1
                if count == 0 then callback(results) end
            end)
        end
    end
end

--- Последовательно объединяет два `Task` в один.
--- @param t Task
--- @param onCompleted fun(value: any): Task
--- @return Task
function task.chain(t, onCompleted)
    return function(callback)
        t(function(value)
            local t2 = onCompleted(value)
            t2(callback)
        end)
    end
end

return task
