---@alias ActionResult {}

---@type table<string, ActionResult>
-- selene: allow(unscoped_variables)
ActionResult = {
    Succeeded = {},
    Failed = {},
    NeedsMoreTime = {},
}

-- selene: allow(unscoped_variables)
actionQueue = {
    ---@type ({ action: Action, expireTimeMs: number })[]
    queue = {},
}

---@alias Action fun(deltaSeconds: number): ActionResult

--- Added actions will be called on every runWaiting() update.
--- They will continue to be executed until they return Succeeded or Failed instead of NeedsMoreTime.
---
--- Replaces any existing action with the given name.
--- If the initial call of action() doesn't return NeedsMoreTime, this function will not bother adding it to the queue.
---@param name string
---@param maxTimeMs number
---@param action Action
function actionQueue:upsert(name, maxTimeMs, action)
    if action(0) ~= ActionResult.NeedsMoreTime then
        return
    end

    self.queue[name] = {
        action = action,
        expireTimeMs = maxTimeMs + playdate.getCurrentTimeMilliseconds(),
    }
end

--- Must be called on every playdate.update() to check for (and run) any waiting tasks.
--- Actions that return NeedsMoreTime will not be removed from the queue unless they have expired.
function actionQueue:runWaiting(deltaSeconds)
    local currentTimeMs = playdate.getCurrentTimeMilliseconds()
    for name, actionObject in pairs(self.queue) do
        local result = actionObject.action(deltaSeconds)
        if
            result ~= ActionResult.NeedsMoreTime
            or currentTimeMs > actionObject.expireTimeMs
        then
            self.queue[name] = nil
        end
    end
end