---@class ActionQueue
---@field queue table<any, { coroutine: thread, expireTimeMs: number }>
actionQueue = {
    queue = {},
}

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

local close = coroutine.close

--- 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 id.
--- If the initial call of action() doesn't return NeedsMoreTime, this function will not bother adding it to the queue.
---@param id any
---@param maxTimeMs number
---@param action Action
function actionQueue:upsert(id, maxTimeMs, action)
    if self.queue[id] then
        close(self.queue[id].coroutine)
    end
    self.queue[id] = {
        coroutine = coroutine.create(action),
        expireTimeMs = maxTimeMs + playdate.getCurrentTimeMilliseconds(),
    }
end

--- The new action will not be added if an entry with the current id already exists in the queue.
---@param id any
---@param maxTimeMs number
---@param action Action
function actionQueue:newOnly(id, maxTimeMs, action)
    if self.queue[id] then
        return
    end
    self.queue[id] = {
        coroutine = coroutine.create(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.
---@param deltaSeconds number
function actionQueue:runWaiting(deltaSeconds)
    local currentTimeMs = playdate.getCurrentTimeMilliseconds()

    for id, actionObject in pairs(self.queue) do
        coroutine.resume(actionObject.coroutine, deltaSeconds)

        if currentTimeMs > actionObject.expireTimeMs then
            close(actionObject.coroutine)
        end

        if coroutine.status(actionObject.coroutine) == "dead" then
            self.queue[id] = nil
        end
    end
end

-- luacheck: ignore
if not playdate or playdate.TEST_MODE then
    return actionQueue
end
