--[[
    Commander 3.0 - Parser

    Builds an Abstract Syntax Tree (AST) from tokens.
    Handles: pipelines, chains, commands, arguments.
]]

local ADDON_NAME, C3 = ...

local Parser = {}
Parser.__index = Parser
C3.Parser = Parser

local TOKEN = C3.Lexer.TOKEN

---Create a new Parser
---@param tokens table
---@return table
function Parser.new(tokens)
    local self = setmetatable({}, Parser)
    self.tokens = tokens
    self.pos = 1
    return self
end

---Get current token
---@return table?
function Parser:Current()
    return self.tokens[self.pos]
end

---Peek at current token type
---@return string?
function Parser:PeekType()
    local token = self:Current()
    return token and token.type
end

---Peek at current token value
---@return any
function Parser:PeekValue()
    local token = self:Current()
    return token and token.value
end

---Advance to next token
---@return table? previous
function Parser:Advance()
    local token = self:Current()
    self.pos = self.pos + 1
    return token
end

---Check if current token matches type
---@param tokenType string
---@return boolean
function Parser:Check(tokenType)
    return self:PeekType() == tokenType
end

---Consume token if it matches, otherwise error
---@param tokenType string
---@return table token
function Parser:Expect(tokenType)
    if not self:Check(tokenType) then
        error(string.format("Expected %s, got %s", tokenType, self:PeekType() or "EOF"))
    end
    return self:Advance()
end

---Consume token if it matches
---@param tokenType string
---@return table? token
function Parser:Match(tokenType)
    if self:Check(tokenType) then
        return self:Advance()
    end
    return nil
end

---Check if at end of input
---@return boolean
function Parser:IsAtEnd()
    return self:PeekType() == TOKEN.EOF or self.pos > #self.tokens
end

-------------------------------------------------------------------------------
-- Parsing
-------------------------------------------------------------------------------

---Parse the entire input
---@return table ast
function Parser:Parse()
    -- Top level: chain of commands
    return self:ParseChain()
end

---Parse a chain (cmd && cmd ; cmd)
---@return table
function Parser:ParseChain()
    local segments = {}

    -- First pipeline
    local pipeline = self:ParsePipeline()
    if not pipeline then
        return nil
    end

    local operator = nil

    while not self:IsAtEnd() do
        -- Check for chain operators
        if self:Check(TOKEN.AND) then
            self:Advance()
            table.insert(segments, {
                pipeline = pipeline,
                operator = "&&",
            })
            pipeline = self:ParsePipeline()
        elseif self:Check(TOKEN.SEMI) then
            self:Advance()
            table.insert(segments, {
                pipeline = pipeline,
                operator = ";",
            })
            pipeline = self:ParsePipeline()
        else
            break
        end
    end

    -- Add final segment
    if pipeline then
        table.insert(segments, {
            pipeline = pipeline,
            operator = nil,
        })
    end

    -- If only one segment with one stage, simplify
    if #segments == 1 and segments[1].pipeline.type == "Pipeline" and
       #segments[1].pipeline.stages == 1 then
        return segments[1].pipeline.stages[1]
    end

    -- If only one segment, return just the pipeline
    if #segments == 1 then
        return segments[1].pipeline
    end

    return {
        type = "Chain",
        segments = segments,
    }
end

---Parse a pipeline (cmd | cmd | cmd)
---@return table
function Parser:ParsePipeline()
    local stages = {}

    -- First command
    local cmd = self:ParseCommand()
    if not cmd then
        return nil
    end
    table.insert(stages, cmd)

    -- Check for pipes
    while self:Check(TOKEN.PIPE) do
        self:Advance() -- consume |
        cmd = self:ParseCommand()
        if cmd then
            table.insert(stages, cmd)
        else
            break
        end
    end

    if #stages == 1 then
        return {
            type = "Pipeline",
            stages = stages,
        }
    end

    return {
        type = "Pipeline",
        stages = stages,
    }
end

---Parse a single command
---@return table?
function Parser:ParseCommand()
    -- Skip if at end or at chain/pipe operator
    if self:IsAtEnd() or self:Check(TOKEN.AND) or self:Check(TOKEN.OR) or
       self:Check(TOKEN.SEMI) or self:Check(TOKEN.PIPE) then
        return nil
    end

    local name = nil
    local args = {}
    local flags = {}
    local raw = {}

    -- First token is the command name
    local firstToken = self:Current()
    if firstToken.type == TOKEN.WORD then
        name = self:Advance().value
    elseif firstToken.type == TOKEN.STRING then
        name = self:Advance().value
    elseif firstToken.type == TOKEN.VARIABLE then
        -- Bare variable: treat as implicit "echo $var"
        name = "echo"
        local token = self:Advance()
        table.insert(args, {
            type = "variable",
            name = token.value,
        })
        table.insert(raw, "$" .. token.value)
    else
        return nil
    end

    -- Parse arguments
    while not self:IsAtEnd() do
        local tokenType = self:PeekType()

        -- Stop at pipe or chain operators
        if tokenType == TOKEN.PIPE or tokenType == TOKEN.AND or
           tokenType == TOKEN.OR or tokenType == TOKEN.SEMI then
            break
        end

        if tokenType == TOKEN.WORD then
            local token = self:Advance()
            table.insert(args, {
                type = "word",
                value = token.value,
            })
            table.insert(raw, token.value)

        elseif tokenType == TOKEN.STRING then
            local token = self:Advance()
            table.insert(args, {
                type = "string",
                value = token.value,
            })
            table.insert(raw, token.value)

        elseif tokenType == TOKEN.NUMBER then
            local token = self:Advance()
            table.insert(args, {
                type = "number",
                value = token.value,
            })
            table.insert(raw, tostring(token.value))

        elseif tokenType == TOKEN.FLAG then
            local token = self:Advance()
            local flagName = token.value:gsub("^%-%-?", "") -- Remove leading dashes

            -- Check for flag value (--flag=value or --flag value)
            if self:Check(TOKEN.WORD) or self:Check(TOKEN.STRING) or self:Check(TOKEN.NUMBER) then
                local valueToken = self:Advance()
                flags[flagName] = valueToken.value
            else
                flags[flagName] = true
            end
            table.insert(raw, token.value)

        elseif tokenType == TOKEN.VARIABLE then
            local token = self:Advance()
            table.insert(args, {
                type = "variable",
                name = token.value,
            })
            table.insert(raw, "$" .. token.value)

        elseif tokenType == TOKEN.SUBSHELL then
            local token = self:Advance()
            table.insert(args, {
                type = "subshell",
                content = token.value,
            })
            table.insert(raw, "$(" .. token.value .. ")")

        elseif tokenType == TOKEN.GT or tokenType == TOKEN.LT or
               tokenType == TOKEN.GTE or tokenType == TOKEN.LTE or
               tokenType == TOKEN.EQ or tokenType == TOKEN.NEQ then
            -- Comparison operators (for where expressions)
            local token = self:Advance()
            table.insert(args, {
                type = "operator",
                value = token.value,
            })
            table.insert(raw, token.value)

        elseif tokenType == TOKEN.LPAREN then
            local token = self:Advance()
            table.insert(args, {
                type = "paren",
                value = "(",
            })
            table.insert(raw, "(")

        elseif tokenType == TOKEN.RPAREN then
            local token = self:Advance()
            table.insert(args, {
                type = "paren",
                value = ")",
            })
            table.insert(raw, ")")

        else
            break
        end
    end

    return {
        type = "Command",
        name = name,
        args = args,
        flags = flags,
        raw = raw,
    }
end

-------------------------------------------------------------------------------
-- Convenience Functions
-------------------------------------------------------------------------------

---Parse a string into an AST (Static Method)
---@param input string
---@return table? ast
function Parser.ParseString(input)
    if not input or input == "" then
        return nil
    end

    local tokens = C3.Lexer.TokenizeString(input)
    local parser = Parser.new(tokens)

    local ok, result = pcall(function()
        return parser:Parse()
    end)

    if not ok then
        C3:Error("Parse error: " .. tostring(result))
        return nil
    end

    return result
end

---Parse and dump AST for debugging
---@param input string
function Parser.Debug(input)
    local ast = Parser.ParseString(input)
    if ast then
        C3:Debug("AST:", C3.Util and C3.Util.Dump(ast) or "...")
    end
    return ast
end

return Parser
