--[[
    Commander 3.0 - Task System

    Coroutine-based task wrapper for async execution.
    Tasks can be polled incrementally to stay within time budgets.
]]

local ADDON_NAME, C3 = ...

local Task = C3.class()
C3.Task = Task

-- Task states
Task.STATUS = {
    PENDING = "pending",
    RUNNING = "running",
    SUSPENDED = "suspended",
    COMPLETED = "completed",
    FAILED = "failed",
    CANCELLED = "cancelled",
}

-- ID counter
local taskIdCounter = 0

---Create a new Task
---@param fn function The function to execute as a coroutine
---@param context? table Optional execution context
function Task:init(fn, context)
    taskIdCounter = taskIdCounter + 1

    self.id = taskIdCounter
    self.fn = fn
    self.context = context or {}
    self.status = Task.STATUS.PENDING
    self.result = nil
    self.error = nil
    self.startTime = nil
    self.endTime = nil
    self.co = nil

    -- Callbacks
    self.onComplete = nil
    self.onError = nil
    self.onProgress = nil

    -- Create the coroutine
    self.co = coroutine.create(function()
        return fn(self.context)
    end)
end

---Poll the task once (resume coroutine)
---@return boolean stillRunning True if task is still running
function Task:Poll()
    if self.status == Task.STATUS.COMPLETED or
       self.status == Task.STATUS.FAILED or
       self.status == Task.STATUS.CANCELLED then
        return false
    end

    if self.status == Task.STATUS.PENDING then
        self.status = Task.STATUS.RUNNING
        self.startTime = GetTime()
    end

    -- Resume the coroutine
    local ok, result = coroutine.resume(self.co)

    if not ok then
        -- Error occurred
        self:_fail(result)
        return false
    end

    -- Check if coroutine is dead (completed)
    if coroutine.status(self.co) == "dead" then
        self:_complete(result)
        return false
    end

    -- Still running (yielded)
    self.status = Task.STATUS.SUSPENDED
    return true
end

---Run the task to completion (blocking)
---@return any result
function Task:Run()
    while self:Poll() do
        -- Keep polling until done
    end
    return self.result
end

---Cancel the task
function Task:Cancel()
    if self.status == Task.STATUS.COMPLETED or
       self.status == Task.STATUS.FAILED then
        return
    end

    self.status = Task.STATUS.CANCELLED
    self.endTime = GetTime()
end

---Check if task is still running
---@return boolean
function Task:IsRunning()
    return self.status == Task.STATUS.RUNNING or
           self.status == Task.STATUS.SUSPENDED
end

---Check if task is done (completed, failed, or cancelled)
---@return boolean
function Task:IsDone()
    return self.status == Task.STATUS.COMPLETED or
           self.status == Task.STATUS.FAILED or
           self.status == Task.STATUS.CANCELLED
end

---Get the task result (nil if not completed)
---@return any
function Task:GetResult()
    return self.result
end

---Get the task error (nil if no error)
---@return string?
function Task:GetError()
    return self.error
end

---Internal: Mark task as completed
---@param result any
function Task:_complete(result)
    self.status = Task.STATUS.COMPLETED
    self.result = result
    self.endTime = GetTime()

    if self.onComplete then
        local ok, err = pcall(self.onComplete, self, result)
        if not ok then
            C3:Error("Task onComplete callback error: " .. tostring(err))
        end
    end
end

---Internal: Mark task as failed
---@param err string
function Task:_fail(err)
    self.status = Task.STATUS.FAILED
    self.error = err
    self.endTime = GetTime()

    C3:Error("Task " .. self.id .. " failed: " .. tostring(err))

    if self.onError then
        local ok, callbackErr = pcall(self.onError, self, err)
        if not ok then
            C3:Error("Task onError callback error: " .. tostring(callbackErr))
        end
    end
end

---Set completion callback
---@param callback function(task, result)
---@return Task self For chaining
function Task:OnComplete(callback)
    self.onComplete = callback
    -- Fire immediately if already completed
    if self.status == Task.STATUS.COMPLETED then
        local ok, err = pcall(callback, self, self.result)
        if not ok then
            C3:Error("Task onComplete callback error: " .. tostring(err))
        end
    end
    return self
end

---Set error callback
---@param callback function(task, error)
---@return Task self For chaining
function Task:OnError(callback)
    self.onError = callback
    -- Fire immediately if already failed
    if self.status == Task.STATUS.FAILED then
        local ok, callbackErr = pcall(callback, self, self.error)
        if not ok then
            C3:Error("Task onError callback error: " .. tostring(callbackErr))
        end
    end
    return self
end

---Set progress callback
---@param callback function(task, progress)
---@return Task self For chaining
function Task:OnProgress(callback)
    self.onProgress = callback
    return self
end

---Report progress (call from within task function)
---@param progress any Progress data
function Task:ReportProgress(progress)
    if self.onProgress then
        self.onProgress(self, progress)
    end
end

---Create a simple task from a function
---@param fn function
---@param context? table
---@return Task
function Task.Create(fn, context)
    return Task:new(fn, context)
end

---Wrap a value in a completed task
---@param value any
---@return Task
function Task.Resolved(value)
    local task = Task:new(function() return value end)
    task:Run()
    return task
end

---Create a failed task
---@param err string
---@return Task
function Task.Rejected(err)
    local task = Task:new(function() error(err) end)
    task:Run()
    return task
end

return Task
