///|
pub(all) struct Diagnostic {
  message : String
  // PKL-107: optional source position. `start` / `end` are absolute
  // byte offsets into the originating source; -1 means "unknown"
  // (synthetic diagnostic, post-eval rewrite, or a site that hasn't
  // been wired through yet). The CLI projects `start` onto a
  // `line:column` pair by walking the source.
  start : Int
  end : Int
} derive(Eq, Debug)

///|
/// Helper for the common path: a diagnostic with no recorded position.
/// Sites that have a CST node in hand should construct
/// `Diagnostic::{ message, start, end }` directly instead.
fn diag(message : String) -> Diagnostic {
  { message, start: -1, end: -1 }
}

///|
pub(all) struct UnsupportedSyntax {
  start : Int
  end : Int
  text : String
  kind : String
} derive(Eq, Debug)

///|
pub(all) enum BinaryOp {
  Add
  Subtract
  Multiply
  Divide
  IntDivide
  Modulo
  Power
  Equal
  NotEqual
  LessThan
  LessOrEqual
  GreaterThan
  GreaterOrEqual
  And
  Or
  NullCoalesce
  Is
  As
  Pipe
} derive(Eq, Debug)

///|
pub(all) enum UnaryOp {
  Negate
  Not
} derive(Eq, Debug)

///|
pub(all) enum Expr {
  IntLiteral(Int64)
  FloatLiteral(Double)
  BoolLiteral(Bool)
  StringLiteral(String)
  NullLiteral
  Identifier(String)
  ImportExpr(String)
  ImportGlobExpr(String)
  ObjectLiteral(Array[ObjectMember])
  TypedObjectLiteral(String, Array[ObjectMember])
  ListingLiteral(Array[Expr])
  MappingLiteral(Array[MappingEntry])
  MemberAccess(Expr, String)
  SafeMemberAccess(Expr, String)
  SubscriptAccess(Expr, Expr)
  AmendExpr(Expr, Array[ObjectMember])
  CallExpr(Expr, Array[Expr])
  LambdaExpr(Array[FunctionParameter], Expr, String?)
  LetExpr(String, String?, Expr, Expr)
  NonNullExpr(Expr)
  UnaryExpr(UnaryOp, Expr)
  BinaryExpr(BinaryOp, Expr, Expr)
  ConditionalExpr(Expr, Expr, Expr)
  ForGenerator(String, String?, Expr, Array[ObjectMember], String?, String?)
  // PKL-136: synthetic wrapper emitted by the parser when a `when (cond)` block
  // appears inside a Listing or Mapping body. The wrapped expression is a
  // `ConditionalExpr` whose branches are `ListingLiteral` (for Listing bodies)
  // or `MappingLiteral` (for Mapping bodies). The listing / mapping evaluator
  // recognises this variant and spreads the resulting collection's contents
  // into the parent body instead of attaching it as a single element / entry.
  WhenSpread(Expr)
  // PKL-128: string interpolation. The parser splits a `"... \(expr) ..."`
  // literal into alternating literal segments and embedded expressions;
  // the evaluator concatenates each part's rendered string form into the
  // final value. An interpolated string with zero `\(...)` segments
  // collapses back to a plain `StringLiteral` at parse time so the hot
  // path stays untouched.
  InterpolatedString(Array[Expr])
  // PKL-103: Apple Pkl's `f?(...)` null-safe call form. Today only
  // `read?(uri)` is wired (returns `null` instead of a diagnostic when
  // the URI resolves to nothing); the AST node generalises so future
  // null-safe intrinsics can reuse the dispatch.
  NullSafeCallExpr(Expr, Array[Expr])
  UnsupportedExpr
  ErrorExpr(String)
} derive(Eq, Debug)

///|
pub(all) struct ObjectMember {
  name : String
  type_name : String?
  value : Expr
  annotations : Array[Annotation]
} derive(Eq, Debug)

///|
pub(all) struct MappingEntry {
  key : Expr
  value : Expr
} derive(Eq, Debug)

///|
pub(all) struct ImportDecl {
  uri : String
  import_name : String
  is_glob : Bool
} derive(Eq, Debug)

///|
pub(all) enum ModuleRelationKind {
  ModuleAmends
  ModuleExtends
} derive(Eq, Debug)

///|
pub(all) struct ModuleRelation {
  kind : ModuleRelationKind
  uri : String
} derive(Eq, Debug)

///|
pub(all) struct Binding {
  name : String
  type_name : String?
  value : Expr
  exported : Bool
  is_const : Bool
  annotations : Array[Annotation]
  // PKL-140: True when the property was declared without a default
  // (`foo: Int?` / `external minInt: Int`). The evaluator skips
  // these — Apple Pkl's stdlib uses the form for host-bound slots
  // and abstract templates whose subclasses fill in the value.
  abstract_slot : Bool
  // PKL-pkspec-A: True when this binding was pre-registered for a
  // *sibling* object-body member (`new Foo { a = b; b = 2 }`) rather
  // than a module-/lexical-scope property. Sibling slots support
  // forward references between members of the same object body, but
  // they must NOT capture a same-named identifier that is referenced
  // transitively from an OUTER-scope binding's RHS — otherwise an
  // object body that assigns a field whose name collides with a
  // module property (`new Rendered { tests = ... }` over module
  // `tests`) hijacks the module property and produces a false
  // `cyclic property reference` when an outer local depends on the
  // real module property. Defaults to `false`; only the object-body
  // pre-registration sites set it `true`.
  sibling_slot : Bool
} derive(Eq, Debug)

///|
pub(all) enum Declaration {
  ClassDeclaration(ClassDecl)
  FunctionDeclaration(FunctionDecl)
  TypeAliasDeclaration(TypeAliasDecl)
} derive(Eq, Debug)

///|
/// PKL-128d: structured capture of a single annotation that precedes
/// a declaration. `class_name` holds the identifier after `@` (e.g.
/// `"Deprecated"`, `"ModuleInfo"`, `"my.pkg.Custom"`). `body_text`
/// is the verbatim source between the surrounding delimiters; the
/// `body_kind` field identifies which delimiters were used so a
/// downstream tool (pkldoc / codegen) can re-parse the body in the
/// matching mode without scanning back to the open token.
///
/// Forms recognised:
///   - `@Name`                    → body_kind = NoBody,    body_text = ""
///   - `@Name(arg, ...)`          → body_kind = ParenBody, body_text = inside parens
///   - `@Name { field = expr }`   → body_kind = BraceBody, body_text = inside braces
///
/// Capturing only the raw text keeps the slice tight: the
/// downstream consumer can lex/parse the body when needed, and the
/// AST avoids carrying a second Expr-tree shape for arguments that
/// the evaluator never sees.
pub(all) struct Annotation {
  class_name : String
  body_kind : AnnotationBodyKind
  body_text : String
} derive(Eq, Debug)

///|
pub(all) enum AnnotationBodyKind {
  NoBody
  ParenBody
  BraceBody
} derive(Eq, Debug)

///|
pub(all) struct ClassDecl {
  name : String
  // PKL-089: optional list of type parameter names (`class Box` →
  // `["T", "U"]`). Empty for non-generic classes. The names introduce
  // scope-local pseudo-types inside the class body that the typechecker
  // treats as `UnknownType` until instantiation-time binding (PKL-090)
  // lands; until then the field is purely a parser-side acknowledgement.
  type_parameters : Array[String]
  // PKL-116: parallel list of optional bounds (`class Box` →
  // `[Some("Number")]`). Length matches `type_parameters`; entries are
  // `None` when no bound was declared. The typechecker enforces
  // `type_accepts(bound, concrete)` when call-site substitution binds T.
  type_parameter_bounds : Array[String?]
  parent_name : String?
  properties : Array[ClassProperty]
  methods : Array[FunctionDecl]
  // PKL-128d: annotations attached to this `class` declaration in
  // source order. Empty when no annotation precedes the keyword.
  annotations : Array[Annotation]
  // PKL-117: true when `abstract` preceded the `class` keyword. The
  // class itself cannot be instantiated and may host abstract methods
  // that concrete subclasses must override.
  is_abstract : Bool
} derive(Eq, Debug)

///|
pub(all) struct ClassProperty {
  name : String
  type_name : String?
  value : Expr?
  annotations : Array[Annotation]
} derive(Eq, Debug)

///|
pub(all) struct FunctionDecl {
  name : String
  // PKL-090: optional list of type parameter names (`function id(...)` →
  // `["T"]`). Empty for non-generic functions. Names bind to UnknownType
  // inside the function body via the same scope-injection pattern PKL-089
  // uses for class declarations; call-site inference is deferred.
  type_parameters : Array[String]
  // PKL-116: parallel list of optional bounds (same shape as ClassDecl).
  type_parameter_bounds : Array[String?]
  parameters : Array[FunctionParameter]
  return_type_name : String?
  body : Expr?
  // PKL-128d: annotations attached to this `function` declaration in
  // source order. Empty when no annotation precedes the keyword.
  annotations : Array[Annotation]
  // PKL-148d: true when the function declaration carries the `const`
  // modifier. Class-default evaluation may call only const functions.
  is_const : Bool
  // PKL-117: true when `abstract` preceded the `function` keyword. An
  // abstract method has no body (`body = None`); the modifier is the
  // explicit marker, since a body-less function inside an abstract
  // class declaration would otherwise be ambiguous with a partial
  // declaration the parser had failed to consume.
  is_abstract : Bool
} derive(Eq, Debug)

///|
pub(all) struct FunctionParameter {
  name : String
  type_name : String?
} derive(Eq, Debug)

///|
pub(all) struct TypeAliasDecl {
  name : String
  // PKL-115: optional list of type parameter names (`typealias Box = ...` →
  // `["T"]`). Empty for non-generic aliases. The names introduce free type
  // variables in `target`; the typechecker substitutes them when an
  // instantiation site like `Box` is resolved.
  type_parameters : Array[String]
  target : String
  // PKL-128d: annotations attached to this `typealias` declaration in
  // source order. Empty when no annotation precedes the keyword.
  annotations : Array[Annotation]
} derive(Eq, Debug)

///|
pub(all) struct Program {
  module_name : String?
  module_relation : ModuleRelation?
  imports : Array[ImportDecl]
  declarations : Array[Declaration]
  bindings : Array[Binding]
  body : Expr?
  // PKL-128d: annotations attached to the `module` declaration in
  // source order. Empty when no annotation precedes the keyword (or
  // no explicit `module` header is present at all).
  module_annotations : Array[Annotation]
} derive(Eq, Debug)

///|
pub(all) struct ParseResult {
  root : @cst.SyntaxNode
  program : Program
  diagnostics : Array[Diagnostic]
  unsupported_syntax : Array[UnsupportedSyntax]
} derive(Eq)