"""
Nexa Language — AST Node Definitions v5
New: YieldStatement, StructDef, DocString
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional


# ── Literals ─────────────────────────────────────────────────────────────────
@dataclass
class NumberLiteral:   value: float
@dataclass
class StringLiteral:   value: str
@dataclass
class BoolLiteral:     value: bool
@dataclass
class NullLiteral:     pass
@dataclass
class FString:
    parts: List[Any]
@dataclass
class SelfRef:         pass


# ── Expressions ──────────────────────────────────────────────────────────────
@dataclass
class Identifier:      name: str

@dataclass
class BinaryOp:
    op: str; left: Any; right: Any

@dataclass
class UnaryOp:
    op: str; operand: Any

@dataclass
class Assignment:
    name: str; value: Any

@dataclass
class MemberAssignment:
    obj: Any; member: str; value: Any

@dataclass
class IndexAssignment:
    obj: Any; index: Any; value: Any

@dataclass
class CompoundAssignment:
    name: str; op: str; value: Any

@dataclass
class FunctionCall:
    callee: Any
    args: List[Any]
    kwargs: dict = field(default_factory=dict)

@dataclass
class MemberAccess:
    obj: Any; member: str

@dataclass
class IndexAccess:
    obj: Any; index: Any

@dataclass
class SliceExpr:
    start: Optional[Any]
    stop: Optional[Any]
    step: Optional[Any] = None

@dataclass
class ListLiteral:
    elements: List[Any]

@dataclass
class DictLiteral:
    pairs: List[tuple]

@dataclass
class SetLiteral:
    elements: List[Any]

@dataclass
class AwaitExpr:       expr: Any
@dataclass
class PipelineExpr:    stages: List[Any]

@dataclass
class NewExpr:
    class_name: str
    args: List[Any]
    kwargs: dict = field(default_factory=dict)

@dataclass
class ListComp:
    expr: Any; var: str; iterable: Any
    condition: Optional[Any] = None

@dataclass
class TernaryExpr:
    """condition ? then_val : else_val (written as: then_val if condition else else_val)"""
    condition: Any; then_val: Any; else_val: Any

# v25: Implicit Returns (Expression Control Flow)
@dataclass
class BlockExpr:
    stmts: List[Any]

@dataclass
class IfExpr:
    condition: Any
    then_block: List[Any]
    else_block: List[Any]

@dataclass
class TryExpr:
    try_block: List[Any]
    catch_bind: str
    catch_block: List[Any]
    finally_block: List[Any] = field(default_factory=list)

@dataclass
class MatchExpr:
    subject: Any
    cases: List[Any]
    default_body: List[Any]

# ── Statements ────────────────────────────────────────────────────────────────
@dataclass
class LetStatement:
    name: str; value: Any
    type_hint: Optional[str] = None
    line: int = 0

@dataclass
class DestructuredAssignment:
    """let [a, b] = [1, 2] or let {x, y} = pt"""
    targets: List[str]
    value: Any
    is_dict: bool = False

@dataclass
class BreakStatement: pass

@dataclass
class ContinueStatement: pass

@dataclass
class ReturnStatement:
    value: Any
    line: int = 0
@dataclass
class YieldStatement:   value: Any         # NEW v5: generators

@dataclass
class ForStatement:
    var: str; iterable: Any; body: List[Any]

@dataclass
class WhileStatement:
    condition: Any; body: List[Any]

# TryCatch was a duplicate of TryExpr. Alias preserved for backward compatibility.
# TryExpr (line ~119) is the single canonical try/catch AST node.
# New code must use TryExpr; TryCatch will be removed in a future version.
TryCatch = None  # will be assigned after TryExpr definition (see bottom of file)

@dataclass
class ThrowStatement:
    value: Any
    type_name: Optional[str] = None  # v21: typed throw
    line: int = 0

@dataclass
class RangeExpr:        # v21: 0..10  or  0..=10
    start: Any; end: Any; inclusive: bool = False

@dataclass
class InterfaceDef:     # v21: interface Printable { def print() }
    name: str
    method_names: List[str]
    defaults: Dict[str, Any] = field(default_factory=dict)

@dataclass
class NullableType:     # v21: String?
    inner: str

@dataclass
class CaseClause:
    pattern: Any
    body: List[Any]

@dataclass
class MatchStatement:
    expr: Any
    cases: List[CaseClause]
    default_case: Optional[List[Any]] = None

# NOTE: EnumDef v5 stub removed — canonical v23 definition is below (line ~343).
# The v5 version (name: str, members: List[str]) was shadowed by the richer v23
# version (name, type_params, variants). Removing the stub stops the ambiguity.

@dataclass
class Decorator:
    expr: Any

@dataclass
class FromImportStatement:
    module: str
    names: List[str]

@dataclass
class SuperRef:
    pass

@dataclass
class LambdaExpr:
    params: List[str]
    body: List[Any]

@dataclass
class TernaryOp:
    condition: Any
    true_expr: Any
    false_expr: Any

@dataclass
class NullishCoalescing:
    left: Any
    right: Any

@dataclass
class OptionalAccess:
    obj: Any
    member: str

@dataclass
class SpreadExpr:
    expr: Any

@dataclass
class FunctionDef:
    name: str
    params: List[str]
    body: List[Any]
    is_async: bool = False
    param_types: Dict[str, str] = field(default_factory=dict)
    return_type: Optional[str] = None
    defaults: Dict[str, Any] = field(default_factory=dict)
    docstring: Optional[str] = None
    decorators: List[Decorator] = field(default_factory=list)
    visibility: str = "public"  # v21: 'private' | 'public'

@dataclass
class ClassDef:
    name: str; body: List[Any]; base: Optional[str] = None
    decorators: List[Decorator] = field(default_factory=list)
    implements: List[str] = field(default_factory=list)  # v21: interface list

@dataclass
class StructDef:
    """struct Point { x: float  y: float }  — lightweight typed record"""
    name: str
    fields: List[tuple]   # [(field_name, type_hint), ...]

@dataclass
class ModelDef:
    name: str; layers: List[Any]; config: dict

@dataclass
class TrainStatement:
    model_name: str; data: dict; config: dict

@dataclass
class PredictExpr:
    model_name: str; input_data: Any

@dataclass
class ImportStatement:
    module: str; alias: Optional[str]; is_file: bool = False

@dataclass
class TestBlock:
    name: str; body: List[Any]

@dataclass
class AssertStatement:
    kind: str; args: List[Any]

@dataclass
class ExprStatement:    expr: Any

@dataclass
class Program:          statements: List[Any]

# ── v22 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class LoopStatement:        # loop { ... } — infinite loop, exits via break
    body: List[Any]

@dataclass
class AbstractMethodDef:    # abstract def method(params) — no body
    name: str
    params: List[str]

@dataclass
class ExportStatement:      # export { foo, bar }
    names: List[str]

@dataclass
class GenericTypeParam:     # <T> type parameter placeholder
    name: str

@dataclass
class DestructurePattern:   # pattern for match case  {x, y}  or  [h, ...t]
    kind: str               # 'dict' | 'list' | 'class'
    fields: List[Any]
    class_name: Optional[str] = None

@dataclass
class NonNullAssertion:     # expr! — assert not null at runtime
    expr: Any

@dataclass
class NullableAnnotation:   # Type?  — nullable type hint
    inner: str

# ── v23 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class EnumVariant:          # Some(T) or None inside an enum block
    name: str
    fields: List[str]       # field/type names

@dataclass
class EnumDef:              # enum Option<T> { Some(T)  None }
    name: str
    type_params: List[str]
    variants: List[EnumVariant]

@dataclass
class VariantCall:          # Option.Some(42)
    enum_name: str
    variant_name: str
    args: List[Any]

@dataclass
class EnumVariantPattern:   # match pattern:  case Option.Some(val)
    enum_name: str
    variant_name: str
    binds: List[str]        # variable names to bind

@dataclass
class BreakIfStatement:     # break if expr  — inline conditional break
    condition: Any

# ── v24 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class IfLetStatement:       # if let Pattern = expr { } else { }
    pattern: Any            # EnumVariantPattern or Identifier
    expr: Any
    then_body: List[Any]
    else_body: List[Any]

@dataclass
class WhileLetStatement:    # while let Pattern = expr { }
    pattern: Any
    expr: Any
    body: List[Any]

@dataclass
class SealedClassDef:       # sealed class Expr { class Num(value: Int) }
    name: str
    variants: List[Any]     # list of inner ClassDef-like with fields

@dataclass
class SealedVariant:        # inner variant of a sealed class
    name: str
    fields: List[tuple]     # [(name, type_hint), ...]

@dataclass
class MacroDef:             # macro assert_eq!(a, b) { if a != b { } }
    name: str
    params: List[str]
    body: List[Any]

@dataclass
class MacroCall:            # assert_eq!(x, 5)
    name: str
    args: List[Any]

@dataclass
class StructUpdate:         # { ...base, x: 10, y: 20 }
    base: Any
    updates: dict

@dataclass
class QuestionExpr:         # expr?  — propagate None/Err
    expr: Any
    # NOTE: TypeAlias and ConstStatement are defined in v27 section below (single canonical definitions)

@dataclass
class TypedMatchCase:       # case String s if s.len() > 10: ...
    type_name: str          # "String", "Int", "Float", etc.
    bind_name: str          # variable name to bind value
    guard: Any              # optional guard expression (or None)
    body: List[Any]

@dataclass
class BenchBlock:           # bench "name" { }  — benchmarking block
    name: str
    body: List[Any]
    iterations: int = 100

# ── v25 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class VarStatement:         # var x = 5  — explicit mutable binding
    name: str
    value: Any
    type_hint: Optional[str] = None
    line: int = 0

@dataclass
class NativeAssertStatement:  # assert expr  — clean assertion syntax
    condition: Any
    message: Optional[Any] = None   # optional: assert expr, "message"

@dataclass
class TraitDef:             # trait Printable { def print(self) }
    name: str
    methods: List[Any]      # list of FunctionDef (may have bodies = default impls)

@dataclass
class TraitImpl:            # impl Printable for Dog { def print(self) { } }
    trait_name: str
    class_name: str
    methods: List[Any]

@dataclass
class UseStatement:         # use std::math::sin  or  use std::math::{sin, cos}
    path: List[str]         # ["std", "math", "sin"]
    names: List[str]        # specific names to import (empty = import whole path)
    alias: Optional[str] = None

@dataclass
class UnionType:            # Int | String | None  — union type annotation
    types: List[str]

@dataclass
class SlashEqAssignment:    # x /= 2  — divide-assign
    name: str
    value: Any

# ── v26 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class AsyncGeneratorDef:    # async def gen() { await f(); yield item }
    name: str
    params: List[Any]
    body: List[Any]
    return_type: Optional[str] = None
    param_types: Optional[dict] = None

@dataclass
class AsyncForStatement:    # async for item in generator() { }
    var: str
    iterable: Any
    body: List[Any]

@dataclass
class GenericTypeAnnotation:   # List<Int>, Dict<String, Int>, Result<T, E>
    base: str                  # e.g. "List"
    params: List[str]          # e.g. ["Int"]

@dataclass
class RuntimeNullError:        # Raised when null assigned to non-nullable at runtime
    var_name: str
    expected_type: str

# ── v27 New Nodes ─────────────────────────────────────────────────────────────

@dataclass
class ConstStatement:          # const MAX: Int = 100  — immutable cross-file constant
    name: str
    value: Any
    type_hint: Optional[str] = None

@dataclass
class TypeAlias:               # type UserId = Int
    name: str
    target: Any                # str or GenericTypeAnnotation

@dataclass
class NewtypeStatement:        # newtype Celsius = Float  — distinct, non-interchangeable
    name: str
    base_type: str

@dataclass
class PipeErrExpr:             # expr |? handler  — error pipe: runs handler only on Err
    value: Any
    handler: Any

@dataclass
class TemplateString:          # template """..."""  — multi-line interpolated template
    parts: List[Any]           # list of ("lit", str) or ("expr", str)
    dialect: str = "text"      # "text", "sql", "html" — for compile-time validation

@dataclass
class MultiReturnExpr:         # return (a, b) — true multi-return (not tuple sugar)
    values: List[Any]

@dataclass
class SpawnBlock:              # spawn { task a = f(); task b = g(); await [a, b] }
    tasks: List[Any]           # list of (name, expr) tuples
    await_all: bool = True

@dataclass
class ChannelExpr:             # Channel<String>(capacity=10)
    item_type: str
    capacity: Any = None

@dataclass
class GenericRuntimeCheck:     # internal: inserted by compiler for List<Int> enforcement
    target: Any
    item_type: str

@dataclass
class EffectTag:               # @pure / @effect(io, network) — functional effect system
    effects: List[str]         # empty = pure

@dataclass
class ExpectExpr:              # expect(x).to_equal(5) — rich test matchers
    subject: Any
    method: str
    args: List[Any]

@dataclass
class DestructuredParam:       # def f({ name, age }: User) — parameter destructuring
    fields: List[str]          # field names to bind
    type_name: Optional[str] = None
    is_array: bool = False     # True for [first, ...rest] pattern
    rest_name: Optional[str] = None

@dataclass
class MacroCallStmt:           # sql!("SELECT * ...") — macro invocation
    name: str
    args: List[Any]


# ── Backward-compatibility aliases (v0.2 cleanup) ─────────────────────────────
# These old node names are kept as aliases so existing code doesn't break.
# They will be removed in v0.4. New code should use the canonical names.
TryCatch    = TryExpr     # TryCatch → TryExpr  (canonical: try_block, catch_bind, catch_block, finally_block)
TernaryExpr = TernaryOp   # TernaryExpr → TernaryOp  (canonical: condition, true_expr, false_expr)
