--[[
    Commander 3.0 - Execution Context

    Passed to commands to provide access to:
    - Session environment
    - Pipeline input/output
    - Helper methods (Produce, Transform, Text, Error)
]]

local ADDON_NAME, C3 = ...

local Context = C3.class()
C3.Context = Context

-- Object pool for context reuse (reduces GC pressure in watch loops)
local contextPool = {}
local POOL_MAX_SIZE = 20

---Acquire a context from the pool or create new
---@param options table
---@return table context
function Context.Acquire(options)
    options = options or {}

    local ctx = table.remove(contextPool)
    if ctx then
        -- Reuse pooled context - reset fields
        ctx.session = options.session
        ctx.input = options.input
        ctx.args = options.args or {}
        ctx.flags = options.flags or {}
        ctx._input = options._input
        return ctx
    end

    -- Create new context
    return Context:new(options)
end

---Release a context back to the pool
---@param ctx table The context to release
function Context.Release(ctx)
    if #contextPool >= POOL_MAX_SIZE then
        return -- Pool is full, let it be GC'd
    end

    -- Clear references to allow GC of held objects
    ctx.session = nil
    ctx.input = nil
    ctx.args = nil
    ctx.flags = nil
    ctx._input = nil

    table.insert(contextPool, ctx)
end

---Get pool statistics
---@return table stats
function Context.GetPoolStats()
    return {
        available = #contextPool,
        maxSize = POOL_MAX_SIZE,
    }
end

function Context:init(options)
    options = options or {}

    self.session = options.session
    self.input = options.input
    self.args = options.args or {}
    self.flags = options.flags or {}
    self._input = options._input -- Pipeline input from previous stage
end

---Get pipeline input from previous command
---@return any
function Context:GetInput()
    return self._input
end

---Check if we have pipeline input
---@return boolean
function Context:HasInput()
    return self._input ~= nil
end

---Get argument by index (1-based)
---@param index number
---@return any
function Context:GetArg(index)
    local arg = self.args[index]
    if not arg then
        return nil
    end

    -- Handle different arg types
    if arg.type == "variable" then
        -- Resolve variable
        return C3.Executor.ResolveVariable(arg.name, self)
    elseif arg.type == "subshell" then
        -- Execute subshell
        return C3.Executor.ExecuteSync(arg.content, self.session)
    elseif arg.type == "string" or arg.type == "number" or arg.type == "word" then
        return arg.value
    elseif arg.value ~= nil then
        return arg.value
    else
        -- Fallback for raw values
        return arg
    end
end

---Get all arguments as values
---@return table
function Context:GetArgs()
    local result = {}
    for i, _ in ipairs(self.args) do
        table.insert(result, self:GetArg(i))
    end
    return result
end

---Get argument formatted for expression parsing (strings get quoted)
---@param index number
---@return string
function Context:GetExprArg(index)
    local arg = self.args[index]
    if not arg then
        return nil
    end

    -- For expression parsing, strings need to be quoted
    if arg.type == "string" then
        return '"' .. arg.value .. '"'
    elseif arg.type == "variable" then
        -- Resolve and quote if result is string
        local val = C3.Executor.ResolveVariable(arg.name, self)
        if type(val) == "string" then
            return '"' .. val .. '"'
        end
        return tostring(val)
    elseif arg.value ~= nil then
        return tostring(arg.value)
    else
        return tostring(arg)
    end
end

---Get all arguments formatted for expression parsing
---@return table
function Context:GetExprArgs()
    local result = {}
    for i, _ in ipairs(self.args) do
        table.insert(result, self:GetExprArg(i))
    end
    return result
end

---Get raw argument (without resolving variables/subshells)
---@param index number
---@return string
function Context:GetRawArg(index)
    local arg = self.args[index]
    if not arg then
        return nil
    end

    if arg.type == "variable" then
        return "$" .. arg.name
    elseif arg.type == "subshell" then
        return "$(" .. arg.content .. ")"
    elseif arg.value ~= nil then
        return tostring(arg.value)
    else
        return tostring(arg)
    end
end

---Get all arguments as raw strings (without resolving)
---@return table
function Context:GetRawArgs()
    local result = {}
    for i, _ in ipairs(self.args) do
        local raw = self:GetRawArg(i)
        if raw then
            table.insert(result, raw)
        end
    end
    return result
end

---Get flag value
---@param name string
---@return any
function Context:GetFlag(name)
    return self.flags[name]
end

---Check if flag is set
---@param name string
---@return boolean
function Context:HasFlag(name)
    return self.flags[name] ~= nil
end

---Create a producer iterator
---WARNING: This uses coroutine.wrap() which prevents yielding during
---iteration. For large datasets, use ProduceBatched() instead.
---@param generatorFn function(emit)
---@return function iterator
function Context:Produce(generatorFn)
    return C3.Pipeline.Producer(generatorFn)
end

---Build an array with periodic yielding to prevent freezing
---Use this instead of Produce() for commands that iterate over many items.
---@param generatorFn function(emit) Call emit(obj) for each object
---@param yieldInterval? number How often to yield (default 50)
---@return table array The collected results
function Context:ProduceBatched(generatorFn, yieldInterval)
    yieldInterval = yieldInterval or 50
    local results = {}
    local count = 0

    generatorFn(function(obj)
        table.insert(results, obj)
        count = count + 1
        if count % yieldInterval == 0 then
            coroutine.yield()
        end
    end)

    return results
end

---Transform pipeline input
---@param transformFn function(obj) -> obj|nil
---@return function iterator
function Context:Transform(transformFn)
    return C3.Pipeline.Transform(self._input, transformFn)
end

---Consume pipeline input
---@param consumeFn function(obj)
function Context:Consume(consumeFn)
    return C3.Pipeline.Consume(self._input, consumeFn)
end

---Create a Text result
---@param content string
---@return table
function Context:Text(content)
    return C3.Types.Text(content)
end

---Create an Error result
---@param message string
---@return table
function Context:Error(message)
    return C3.Types.Error(message)
end

---Create a Component result (for rich output like icon grids)
---@param componentType string The component type (e.g., "IconGrid", "ProgressBar")
---@param data table Component data
---@return table
function Context:Component(componentType, data)
    return {
        _type = "Component",
        componentType = componentType,
        data = data,
    }
end

---Create an IconGrid component from an array of items
---@param items table[] Array of items with icon, name, etc.
---@return table
function Context:IconGrid(items)
    return self:Component("IconGrid", { items = items })
end

return Context
