--[[
    Commander 3.0 - Kernel (Coroutine Scheduler)

    Manages async task execution with strict time budgets.
    Goal: NEVER freeze the game client.

    Default budget: 2ms per frame
]]

local ADDON_NAME, C3 = ...

local Kernel = {}
Kernel.__index = Kernel
C3.Kernel = Kernel

-- Default time budget in milliseconds
local DEFAULT_TIME_BUDGET_MS = 2

-- Task priority levels
-- HIGH: UI navigation, simple commands (cd, pwd, clear)
-- NORMAL: Standard commands and queries
-- LOW: Background tasks, heavy computations
Kernel.PRIORITY = {
    HIGH = 1,
    NORMAL = 2,
    LOW = 3,
}

-- Commands that should run at HIGH priority (fast, UI-related)
local HIGH_PRIORITY_COMMANDS = {
    cd = true,
    pwd = true,
    clear = true,
    cls = true,
    help = true,
    history = true,
    echo = true,
    watches = true,
    unwatch = true,
}

---Create a new Kernel instance
---@return table
function Kernel.new()
    local self = setmetatable({}, Kernel)

    -- Priority queues: separate queues for each priority level
    self.queues = {
        [Kernel.PRIORITY.HIGH] = {},
        [Kernel.PRIORITY.NORMAL] = {},
        [Kernel.PRIORITY.LOW] = {},
    }
    self.running = nil              -- Currently executing task
    self.timeBudgetMs = DEFAULT_TIME_BUDGET_MS
    self.ticker = nil               -- C_Timer ticker
    self.paused = false             -- Pause execution

    -- Statistics
    self.stats = {
        tasksRun = 0,
        tasksCompleted = 0,
        tasksFailed = 0,
        totalTimeMs = 0,
    }

    return self
end

---Determine priority for a command
---@param input string The command input
---@return number priority
local function getPriorityForCommand(input)
    if not input then return Kernel.PRIORITY.NORMAL end

    -- Extract first word (command name)
    local cmd = input:match("^%s*(%S+)")
    if cmd then
        cmd = cmd:lower()
        if HIGH_PRIORITY_COMMANDS[cmd] then
            return Kernel.PRIORITY.HIGH
        end
    end

    return Kernel.PRIORITY.NORMAL
end

---Set the time budget per frame
---@param ms number Milliseconds (1-16 recommended)
function Kernel:SetTimeBudget(ms)
    self.timeBudgetMs = math.max(0.1, math.min(16, ms))
end

---Get the time budget
---@return number
function Kernel:GetTimeBudget()
    return self.timeBudgetMs
end

---Schedule a task for execution
---@param task table Task object
---@param priority? number Optional priority (defaults to NORMAL)
---@return table task The scheduled task
function Kernel:Schedule(task, priority)
    priority = priority or Kernel.PRIORITY.NORMAL
    task._priority = priority

    table.insert(self.queues[priority], task)
    self.stats.tasksRun = self.stats.tasksRun + 1

    -- Start the ticker if not running
    if not self.ticker then
        self:_startTicker()
    end

    return task
end

---Create and schedule a task from a function
---@param fn function The function to execute
---@param context? table Optional context
---@return table task The scheduled task
function Kernel:Run(fn, context)
    local task = C3.Task:new(fn, context)
    return self:Schedule(task)
end

---Execute a command string (main entry point)
---@param input string Command to execute
---@param session? table Optional session
---@param priority? number Optional priority override
---@return table task The scheduled task
function Kernel:Execute(input, session, priority)
    -- Auto-detect priority if not specified
    priority = priority or getPriorityForCommand(input)

    -- This will be connected to the parser/executor later
    -- For now, create a simple task that parses and runs
    local task = C3.Task:new(function(ctx)
        -- Parse input
        local ast = C3.Parser.ParseString(input)
        if not ast then
            error("Failed to parse: " .. input)
        end

        -- Execute pipeline
        return C3.Pipeline:Execute(ast, ctx)
    end, {
        session = session,
        input = input,
    })

    return self:Schedule(task, priority)
end

---Execute synchronously (blocking) - for headless/API use
---@param input string Command to execute
---@return any result
function Kernel:ExecuteSync(input)
    -- Parse
    local ast = C3.Parser.ParseString(input)
    if not ast then
        error("Failed to parse: " .. input)
    end

    -- Execute directly (no async)
    return C3.Pipeline:ExecuteSync(ast, {
        input = input,
    })
end

---Cancel a task
---@param taskOrId table|number Task object or task ID
---@return boolean success
function Kernel:Cancel(taskOrId)
    local taskId = type(taskOrId) == "table" and taskOrId.id or taskOrId

    for priority = Kernel.PRIORITY.HIGH, Kernel.PRIORITY.LOW do
        local queue = self.queues[priority]
        for i, task in ipairs(queue) do
            if task.id == taskId then
                task:Cancel()
                table.remove(queue, i)
                return true
            end
        end
    end

    return false
end

---Cancel all tasks
function Kernel:CancelAll()
    for priority = Kernel.PRIORITY.HIGH, Kernel.PRIORITY.LOW do
        local queue = self.queues[priority]
        for _, task in ipairs(queue) do
            task:Cancel()
        end
        self.queues[priority] = {}
    end
    self:_stopTicker()
end

---Pause task execution
function Kernel:Pause()
    self.paused = true
end

---Resume task execution
function Kernel:Resume()
    self.paused = false
end

---Check if kernel is busy (has tasks)
---@return boolean
function Kernel:IsBusy()
    for priority = Kernel.PRIORITY.HIGH, Kernel.PRIORITY.LOW do
        if #self.queues[priority] > 0 then
            return true
        end
    end
    return false
end

---Get number of pending tasks
---@param priority? number Optional: count only specific priority
---@return number
function Kernel:GetQueueLength(priority)
    if priority then
        return #self.queues[priority]
    end

    local total = 0
    for p = Kernel.PRIORITY.HIGH, Kernel.PRIORITY.LOW do
        total = total + #self.queues[p]
    end
    return total
end

---Get next task from priority queues (HIGH first, then NORMAL, then LOW)
---@return table? task
---@return number? priority
function Kernel:_getNextTask()
    for priority = Kernel.PRIORITY.HIGH, Kernel.PRIORITY.LOW do
        local queue = self.queues[priority]
        if #queue > 0 then
            return queue[1], priority
        end
    end
    return nil, nil
end

---Remove first task from a specific priority queue
---@param priority number
function Kernel:_removeFirstTask(priority)
    table.remove(self.queues[priority], 1)
end

---Main tick function (called every frame by ticker)
function Kernel:Tick()
    if self.paused or not self:IsBusy() then
        return
    end

    local startTime = debugprofilestop()
    local budgetMs = self.timeBudgetMs

    while true do
        local task, priority = self:_getNextTask()
        if not task then break end

        self.running = task

        -- Poll the task
        local stillRunning = task:Poll()

        if not stillRunning then
            -- Task is done, remove from queue
            self:_removeFirstTask(priority)

            if task.status == C3.Task.STATUS.COMPLETED then
                self.stats.tasksCompleted = self.stats.tasksCompleted + 1
            elseif task.status == C3.Task.STATUS.FAILED then
                self.stats.tasksFailed = self.stats.tasksFailed + 1
            end
        else
            -- Rotate to avoid starving other tasks of the same priority
            local queue = self.queues[priority]
            table.remove(queue, 1)
            table.insert(queue, task)
        end

        self.running = nil

        -- Check time budget
        local elapsed = debugprofilestop() - startTime
        if elapsed > budgetMs then
            break  -- Continue next frame
        end
    end

    -- Track total time
    local totalElapsed = debugprofilestop() - startTime
    self.stats.totalTimeMs = self.stats.totalTimeMs + totalElapsed

    -- Stop ticker if all queues are empty
    if not self:IsBusy() then
        self:_stopTicker()
    end
end

---Start the frame ticker
function Kernel:_startTicker()
    if self.ticker then return end

    -- Try C_Timer first (Retail)
    -- For Classic/Era, C_Timer.NewTicker(0) may not fire every frame
    -- so we fall back to OnUpdate frame if needed
    if C3.Version:IsRetail() then
        self.ticker = C_Timer.NewTicker(0, function()
            self:Tick()
        end)
    else
        -- Classic fallback: use OnUpdate frame
        if not self._tickerFrame then
            self._tickerFrame = CreateFrame("Frame")
        end
        self._tickerFrame:SetScript("OnUpdate", function()
            self:Tick()
        end)
        self.ticker = self._tickerFrame -- Non-nil marker
    end

    C3:Debug("Kernel: Ticker started")
end

---Stop the frame ticker
function Kernel:_stopTicker()
    if not self.ticker then return end

    if C3.Version:IsRetail() then
        self.ticker:Cancel()
    else
        -- Classic fallback: clear OnUpdate
        if self._tickerFrame then
            self._tickerFrame:SetScript("OnUpdate", nil)
        end
    end
    self.ticker = nil

    C3:Debug("Kernel: Ticker stopped")
end

---Get statistics
---@return table
function Kernel:GetStats()
    return {
        tasksRun = self.stats.tasksRun,
        tasksCompleted = self.stats.tasksCompleted,
        tasksFailed = self.stats.tasksFailed,
        totalTimeMs = self.stats.totalTimeMs,
        queueLength = self:GetQueueLength(),
        queueHigh = #self.queues[Kernel.PRIORITY.HIGH],
        queueNormal = #self.queues[Kernel.PRIORITY.NORMAL],
        queueLow = #self.queues[Kernel.PRIORITY.LOW],
        isRunning = self.ticker ~= nil,
    }
end

---Reset statistics
function Kernel:ResetStats()
    self.stats = {
        tasksRun = 0,
        tasksCompleted = 0,
        tasksFailed = 0,
        totalTimeMs = 0,
    }
end

-- Create the default kernel instance
local defaultKernel = Kernel.new()

-- Convenience wrappers on C3
function C3:Schedule(task)
    return defaultKernel:Schedule(task)
end

function C3:Run(fn, context)
    return defaultKernel:Run(fn, context)
end

-- Store reference
C3._kernel = defaultKernel

return Kernel
