"""AST node definitions for MicroBasic.

Shape follows the confirmed grammar in docs/reverse_engineering.md plus the
manual's documented syntax for constructs whose bytecode we didn't need to
special-case at the AST level (e.g. compound assignment desugars to a plain
AssignStmt at parse time -- see parser.py -- so there's no AugAssign node).
"""

from __future__ import annotations

from dataclasses import dataclass, field


class Node:
    """Base class for all AST nodes."""


# -- Expressions -------------------------------------------------------

class Expr(Node):
    pass


@dataclass
class IntLiteral(Expr):
    value: int  # always non-negative; unary '-' is a UnaryOp wrapping this


@dataclass
class BoolLiteral(Expr):
    value: bool


@dataclass
class StringLiteral(Expr):
    value: str


@dataclass
class VarRef(Expr):
    name: str
    index: Expr | None = None  # set for array access: name[index]


@dataclass
class UnaryOp(Expr):
    op: str  # "-" (negate) or "Not"
    operand: Expr


@dataclass
class BinaryOp(Expr):
    op: str
    left: Expr
    right: Expr


@dataclass
class Call(Expr):
    """A function call used as an expression, e.g. GetValue(_TEMP, 1).

    Also doubles as a statement (SetCommand(...), SetConfig(...), Wait(...))
    when wrapped in ExprStmt -- see below.
    """
    name: str
    args: list[Expr] = field(default_factory=list)


@dataclass
class IncDecExpr(Expr):
    """++x / --x / x++ / x-- used where the result is consumed."""
    var: str
    op: str  # "++" or "--"
    prefix: bool


# -- Statements ----------------------------------------------------------

class Stmt(Node):
    pass


@dataclass
class Program(Node):
    statements: list[Stmt] = field(default_factory=list)


@dataclass
class Label(Stmt):
    name: str


@dataclass
class DimStmt(Stmt):
    name: str
    var_type: str  # "Integer" | "Boolean"
    array_len: int | None = None  # None for scalars


@dataclass
class OptionExplicitStmt(Stmt):
    """`Option Explicit` directive. Accepted and currently a no-op --
    codegen doesn't yet enforce declare-before-use. TODO."""


@dataclass
class AssignStmt(Stmt):
    """Always a plain '=' by the time this reaches codegen -- compound
    assignment operators (+=, -=, *=, /=, <<=, >>=) are desugared into this
    (target = target op value) during parsing, since that's confirmed to be
    exactly how the real compiler treats them (see reverse_engineering.md,
    batch 3)."""
    target: VarRef
    value: Expr


@dataclass
class IfStmt(Stmt):
    condition: Expr
    then_block: list[Stmt]
    elseif_clauses: list[tuple[Expr, list[Stmt]]] = field(default_factory=list)
    else_block: list[Stmt] | None = None


@dataclass
class GotoStmt(Stmt):
    label: str


@dataclass
class GosubStmt(Stmt):
    label: str


@dataclass
class ReturnStmt(Stmt):
    pass


@dataclass
class TerminateStmt(Stmt):
    pass


@dataclass
class WaitStmt(Stmt):
    milliseconds: Expr


@dataclass
class PrintStmt(Stmt):
    args: list[Expr] = field(default_factory=list)


@dataclass
class ExprStmt(Stmt):
    """A bare call used for side effects, e.g. SetCommand(...), SetConfig(...)."""
    call: Call


@dataclass
class IncDecStmt(Stmt):
    """Bare x++ / x-- / ++x / --x as a full statement (result discarded).
    Compiles to the compact 3-instruction form (LOAD, INC/DEC, STORE)
    rather than IncDecExpr's 4-instruction consumed form."""
    var: str
    op: str  # "++" or "--"


LoopKind = str  # "For" | "While" | "Do"


@dataclass
class ExitStmt(Stmt):
    kind: LoopKind


@dataclass
class ContinueStmt(Stmt):
    kind: LoopKind


@dataclass
class WhileStmt(Stmt):
    """Covers both `While...End While` and `Do While...Loop` -- confirmed
    byte-identical, so the parser normalizes both source forms to this node."""
    condition: Expr
    body: list[Stmt] = field(default_factory=list)


@dataclass
class DoUntilStmt(Stmt):
    """`Do Until <cond> ... Loop` -- pre-test, exits when condition becomes
    true (mirror image of WhileStmt, which exits when condition is false)."""
    condition: Expr
    body: list[Stmt] = field(default_factory=list)


@dataclass
class DoLoopWhileStmt(Stmt):
    """`Do ... Loop While <cond>` -- post-test, body always runs >= 1 time,
    loops back while condition is true."""
    body: list[Stmt]
    condition: Expr


@dataclass
class DoLoopUntilStmt(Stmt):
    """`Do ... Loop Until <cond>` -- post-test, body always runs >= 1 time,
    loops back while condition is false (exits once true)."""
    body: list[Stmt]
    condition: Expr


@dataclass
class ForTraditionalStmt(Stmt):
    """Traditional `For var = init To limit [Step step] ... Next`.
    Confirmed to allocate two hidden Integer slots (limit, step) and use a
    step-sign-generic exit test -- see codegen.py."""
    var: str
    init: Expr
    limit: Expr
    step: Expr | None  # None means literal 1
    body: list[Stmt] = field(default_factory=list)


@dataclass
class ForCStyleStmt(Stmt):
    """`For var = init AndWhile cond [Evaluate stmt] ... Next`. Confirmed:
    if no Evaluate clause is given, the compiler auto-increments var by 1
    each iteration (reverse_engineering.md, batch 3)."""
    var: str
    init: Expr
    condition: Expr
    evaluate: Stmt | None
    body: list[Stmt] = field(default_factory=list)
