--[[
    Commander 3.0 - Executor

    High-level command execution that ties together:
    - Lexer (tokenization)
    - Parser (AST building)
    - Pipeline (execution)
    - Kernel (async scheduling)
]]

local ADDON_NAME, C3 = ...

local Executor = {}
C3.Executor = Executor

-- NOTE: Context class is now in core/context.lua

-- Simple commands that never yield and can skip the coroutine scheduler
local SYNC_COMMANDS = {
    echo = true, cd = true, pwd = true, clear = true, cls = true,
    help = true, history = true, version = true, test = true,
    watches = true, unwatch = true, gold = true, ilvl = true,
    dismount = true, du = true, df = true,
}

-------------------------------------------------------------------------------
-- Executor
-------------------------------------------------------------------------------

---Execute a command string asynchronously
---@param input string
---@param session? table
---@return table task
function Executor.Execute(input, session)
    -- Parse
    local ast = C3.Parser.ParseString(input)
    if not ast then
        return C3.Task.Rejected("Failed to parse: " .. input)
    end

    -- Sync fast-path: simple commands skip the coroutine scheduler
    if ast.type == "Command" and SYNC_COMMANDS[ast.name] then
        local ok, result = pcall(C3.Pipeline.ExecuteSync, C3.Pipeline, ast, {
            session = session,
            input = input,
        })
        if ok then
            return C3.Task.Resolved(result)
        else
            return C3.Task.Rejected(tostring(result))
        end
    end

    -- Async path: pipelines, chains, and non-sync commands
    local task = C3._kernel:Run(function()
        return C3.Pipeline:Execute(ast, {
            session = session,
            input = input,
        })
    end)

    return task
end

---Execute a command string synchronously
---@param input string
---@param session? table
---@return any result
function Executor.ExecuteSync(input, session)
    -- Parse (using safe static method)
    local ast = C3.Parser.ParseString(input)
    if not ast then
        return C3.Types.Error("Failed to parse: " .. input)
    end

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

---Resolve variables in arguments
---@param args table
---@param context table
---@return table resolved
function Executor.ResolveArgs(args, context)
    local resolved = {}

    for _, arg in ipairs(args) do
        if arg.type == "variable" then
            -- Resolve variable
            local value = Executor.ResolveVariable(arg.name, context)
            table.insert(resolved, {
                type = "resolved",
                value = value,
            })
        elseif arg.type == "subshell" then
            -- Execute subshell
            local result = Executor.ExecuteSync(arg.content, context.session)
            table.insert(resolved, {
                type = "resolved",
                value = result,
            })
        else
            table.insert(resolved, arg)
        end
    end

    return resolved
end

-- Built-in variables (module-level for reuse)
-- Note: health/mana use C3.Secrets for WoW 12.0 compatibility
local builtins = {
    player = function() return (UnitName("player")) end,
    target = function() return (UnitName("target")) or "" end,
    focus = function() return (UnitName("focus")) or "" end,
    class = function() return (select(2, UnitClass("player"))) end,
    level = function() return UnitLevel("player") end,
    zone = function() return GetZoneText() end,
    subzone = function() return GetSubZoneText() end,
    gold = function() return GetMoney() end,
    health = function()
        -- Use Secrets module for WoW 12.0+ secret value handling
        if C3.Secrets then
            return C3.Secrets.GetHealthPercent("player")
        end
        -- Fallback for pre-12.0
        local max = UnitHealthMax("player")
        if max == 0 then return 0 end
        return math.floor(UnitHealth("player") / max * 100)
    end,
    mana = function()
        -- Use Secrets module for WoW 12.0+ secret value handling
        if C3.Secrets then
            return C3.Secrets.GetPowerPercent("player")
        end
        -- Fallback for pre-12.0
        local max = UnitPowerMax("player")
        if max == 0 then return 0 end
        return math.floor(UnitPower("player") / max * 100)
    end,
}

-- Unit property dispatch table
-- Note: health uses C3.Secrets for WoW 12.0 compatibility
local unitProperties = {
    class = function(unit) return (select(2, UnitClass(unit))) end,
    level = function(unit) return UnitLevel(unit) end,
    health = function(unit)
        -- Use Secrets module for WoW 12.0+ secret value handling
        if C3.Secrets then
            return C3.Secrets.GetHealthPercent(unit)
        end
        -- Fallback for pre-12.0
        local max = UnitHealthMax(unit)
        if max == 0 then return 0 end
        return math.floor(UnitHealth(unit) / max * 100)
    end,
    power = function(unit)
        -- Use Secrets module for WoW 12.0+ secret value handling
        if C3.Secrets then
            return C3.Secrets.GetPowerPercent(unit)
        end
        -- Fallback for pre-12.0
        local max = UnitPowerMax(unit)
        if max == 0 then return 0 end
        return math.floor(UnitPower(unit) / max * 100)
    end,
    name = function(unit) return UnitName(unit) end,
}

---Get property from a unit (player/target/focus)
---@param unitName string The unit name (player, target, focus)
---@param prop string The property to get
---@return any
local function getUnitProperty(unitName, prop)
    local handler = unitProperties[prop]
    if not handler then return nil end
    return handler(unitName)
end

---Resolve a variable
---@param name string
---@param context table
---@return any
function Executor.ResolveVariable(name, context)
    -- Check for property access (e.g., $target.class)
    local base, prop = name:match("^([^.]+)%.(.+)$")

    if base and prop then
        -- Get base value from builtins
        if builtins[base] then
            local baseValue = builtins[base]()
            if type(baseValue) == "table" then
                return baseValue[prop]
            end
        end

        -- Get base value from session env
        if context.session and context.session.env and context.session.env[base] then
            local baseValue = context.session.env[base]
            if type(baseValue) == "table" then
                return baseValue[prop]
            end
        end

        -- Unit property access ($target.class, $player.health, etc.)
        if base == "target" or base == "focus" or base == "player" then
            return getUnitProperty(base, prop)
        end

        return nil
    end

    -- Simple variable lookup - builtins first
    if builtins[name] then
        return builtins[name]()
    end

    -- Session environment
    if context.session and context.session.env and context.session.env[name] then
        return context.session.env[name]
    end

    return nil
end

-------------------------------------------------------------------------------
-- Built-in Commands
-------------------------------------------------------------------------------

-- Echo command (for testing)
C3.Commands.echo = function(ctx)
    local args = ctx:GetArgs()
    local text = table.concat(args, " ")

    if ctx:HasInput() then
        -- If we have pipeline input, echo that
        local input = ctx:GetInput()
        if type(input) == "table" then
            if input._type then
                return input -- Pass through typed object
            else
                -- Array of objects, pass through
                return input
            end
        else
            return C3.Types.Text(tostring(input))
        end
    end

    return C3.Types.Text(text)
end

-- Help command
C3.Commands.help = function(ctx)
    local cmdName = ctx:GetArg(1)

    if cmdName then
        local cmd = C3.Commands[cmdName]
        if cmd and type(cmd) == "table" and cmd.help then
            return C3.Types.Text(cmd.help)
        elseif cmd then
            return C3.Types.Text("No help available for: " .. cmdName)
        else
            return C3.Types.Error("Unknown command: " .. cmdName)
        end
    end

    -- List all commands
    local result = { C3.Types.Text("Available commands:") }
    local names = {}
    for name in pairs(C3.Commands) do
        table.insert(names, name)
    end
    table.sort(names)

    for _, name in ipairs(names) do
        table.insert(result, C3.Types.Text("  " .. name))
    end

    return result
end

-- Version command
C3.Commands.version = function(ctx)
    return C3.Types.Text(string.format("%s v%s (%s)",
        C3.NAME,
        C3.VERSION,
        C3.Version:GetFlavor()
    ))
end

-- Test command (for development)
C3.Commands.test = function(ctx)
    local results = {}

    table.insert(results, C3.Types.Text("Test results:"))
    table.insert(results, C3.Types.Text("  Kernel: " .. (C3._kernel and "OK" or "FAIL")))
    table.insert(results, C3.Types.Text("  Parser: " .. (C3.Parser and "OK" or "FAIL")))
    table.insert(results, C3.Types.Text("  Pipeline: " .. (C3.Pipeline and "OK" or "FAIL")))
    table.insert(results, C3.Types.Text("  Types: " .. (C3.Types and "OK" or "FAIL")))
    table.insert(results, C3.Types.Text("  Flavor: " .. C3.Version:GetFlavor()))
    table.insert(results, C3.Types.Text("  TOC: " .. C3.Version._toc))

    return results
end

-- Types command - defined later in registry after C3.Command is available

-------------------------------------------------------------------------------
-- Initialize
-------------------------------------------------------------------------------

-- Wire up C3:Execute to use Executor
function C3:Execute(input, session)
    return Executor.Execute(input, session)
end

function C3:Query(input)
    return Executor.ExecuteSync(input)
end

return Executor
