// Cedar expression AST — all expression and policy nodes.
// References:
//   Rust: cedar-policy-core/src/ast/ (ExprKind, Policy, TemplateBody)
//   Go:   cedar-go/x/exp/ast/ (node.go, policy.go, scope.go)

// ---------------------------------------------------------------------------
// Literal — atomic leaf values (subset of Value, used by Expr::Lit)
// ---------------------------------------------------------------------------

///|
/// A literal value that can appear directly in Cedar source text.
/// Set and Record are NOT literals — they are compound expressions.
pub(all) enum Literal {
  Bool(Bool)
  Long(Int64)
  String(String)
  EntityUID(EntityUID)
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Variables & Operators
// ---------------------------------------------------------------------------

///|
/// The four PARC variables that Cedar expressions can reference.
pub(all) enum VarKind {
  Principal
  Action
  Resource
  Context
} derive(Debug, Eq)

///|
/// Built-in unary operators.
pub(all) enum UnaryOp {
  Not // logical negation — argument must have Bool type
  Neg // integer negation — argument must have Long type
  IsEmpty // set/record emptiness test
} derive(Debug, Eq)

///|
/// Built-in binary operators.
pub(all) enum BinaryOp {
  Eq // total equality — different types return false, not error
  Ne // !=
  Less // <  — arguments must have Long type
  LessEq // <= — arguments must have Long type
  Gt // >  — arguments must have Long type
  Ge // >= — arguments must have Long type
  Add // +  — integer addition
  Sub // -  — integer subtraction
  Mul // *  — integer multiplication
  In_ // hierarchy membership — first is entity, second is entity or set of entities
  Contains // set membership test
  ContainsAll // set superset test
  ContainsAny // set intersection test (is intersection non-empty?)
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Expression AST
// ---------------------------------------------------------------------------

///|
/// The complete expression AST for Cedar.
/// Covers every node type from the Cedar language specification.
pub(all) enum Expr {
  // Literal value (Bool, Long, String, EntityUID)
  Lit(Literal)

  // Variable reference (principal / action / resource / context)
  Var(VarKind)

  // Ternary conditional: if test_expr then then_expr else else_expr
  If(Expr, Expr, Expr)

  // Boolean AND — short-circuit evaluation (left evaluated first)
  And(Expr, Expr)

  // Boolean OR — short-circuit evaluation (left evaluated first)
  Or(Expr, Expr)

  // Application of a built-in unary operator
  UnaryApp(UnaryOp, Expr)

  // Application of a built-in binary operator
  BinaryApp(BinaryOp, Expr, Expr)

  // Attribute access: expr.attr
  GetAttr(Expr, String)

  // Check whether an entity/record has a given attribute: expr has attr
  HasAttr(Expr, String)

  // Entity tag access: entity.getTag(tagExpr)
  GetTag(Expr, Expr)

  // Entity tag existence check: entity.hasTag(tagExpr)
  HasTag(Expr, Expr)

  // String pattern matching (IAM StringLike semantics): expr like pattern
  Like(Expr, Pattern)

  // Entity type test: expr is EntityType
  Is(Expr, EntityType)

  // Set literal: [e1, e2, ...]
  Set(Array[Expr])

  // Anonymous record literal: { key1: val1, key2: val2, ... }
  Record(Array[(String, Expr)])

  // Extension function / method call: name(args...)
  ExtensionApp(Name, Array[Expr])

  // Template slot reference (for policy templates)
  Slot(String)

  // Symbolic Unknown for partial evaluation
  // The optional Type provides a type annotation inferred from operator context.
  // None = untyped (CPE classic), Some(t) = typed (enables TPE-style shortcuts).
  Unknown(String, Type?)
} derive(Debug, Eq)

///|
/// Convenience constructor for Unknown.
/// Call `unknown("x")` for an untyped unknown, or `unknown("x", typ=EntityType("User"))` for a typed one.
pub fn unknown(name : String, typ? : Type) -> Expr {
  Unknown(name, typ)
}

// ---------------------------------------------------------------------------
// Expr builder — convenient constructors and chaining methods
// ---------------------------------------------------------------------------

// Leaf constructors (free functions)

///|
pub fn bool(b : Bool) -> Expr {
  Lit(Bool(b))
}

///|
pub fn long(l : Int64) -> Expr {
  Lit(Long(l))
}

///|
pub fn str(s : String) -> Expr {
  Lit(String(s))
}

///|
pub fn euid(type_ : String, id : String) -> Expr {
  Lit(EntityUID(EntityUID::{ type_, id }))
}

///|
pub fn principal() -> Expr {
  Var(Principal)
}

///|
pub fn action() -> Expr {
  Var(Action)
}

///|
pub fn resource() -> Expr {
  Var(Resource)
}

///|
pub fn context() -> Expr {
  Var(Context)
}

// Non-chaining constructors (free functions)

///|
pub fn if_(cond : Expr, t : Expr, e : Expr) -> Expr {
  If(cond, t, e)
}

///|
pub fn not_(e : Expr) -> Expr {
  UnaryApp(Not, e)
}

///|
pub fn neg(e : Expr) -> Expr {
  UnaryApp(Neg, e)
}

///|
pub fn is_empty(e : Expr) -> Expr {
  UnaryApp(IsEmpty, e)
}

///|
pub fn set(elements : Array[Expr]) -> Expr {
  Set(elements)
}

///|
pub fn record(pairs : Array[(String, Expr)]) -> Expr {
  Record(pairs)
}

///|
pub fn ext_call(name : Name, args : Array[Expr]) -> Expr {
  ExtensionApp(name, args)
}

// Chaining methods on Expr

///|
/// self == rhs
pub fn Expr::eq(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Eq, self, rhs)
}

///|
/// self != rhs
pub fn Expr::ne(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Ne, self, rhs)
}

///|
/// self < rhs
pub fn Expr::lt(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Less, self, rhs)
}

///|
/// self <= rhs
pub fn Expr::le(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(LessEq, self, rhs)
}

///|
/// self > rhs
pub fn Expr::gt(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Gt, self, rhs)
}

///|
/// self >= rhs
pub fn Expr::ge(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Ge, self, rhs)
}

///|
/// self + rhs
pub fn Expr::add(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Add, self, rhs)
}

///|
/// self - rhs
pub fn Expr::sub(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Sub, self, rhs)
}

///|
/// self * rhs
pub fn Expr::mul(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Mul, self, rhs)
}

///|
/// self && rhs  (keyword: and)
pub fn Expr::and_(self : Expr, rhs : Expr) -> Expr {
  And(self, rhs)
}

///|
/// self || rhs  (keyword: or)
pub fn Expr::or_(self : Expr, rhs : Expr) -> Expr {
  Or(self, rhs)
}

///|
/// self in rhs  (keyword: in)
pub fn Expr::in_(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(In_, self, rhs)
}

///|
/// self.contains(rhs)
pub fn Expr::contains(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(Contains, self, rhs)
}

///|
/// self.contains_all(rhs)
pub fn Expr::contains_all(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(ContainsAll, self, rhs)
}

///|
/// self.contains_any(rhs)
pub fn Expr::contains_any(self : Expr, rhs : Expr) -> Expr {
  BinaryApp(ContainsAny, self, rhs)
}

///|
/// self.get_tag(tag)
pub fn Expr::get_tag(self : Expr, tag : Expr) -> Expr {
  GetTag(self, tag)
}

///|
/// self.has_tag(tag)
pub fn Expr::has_tag(self : Expr, tag : Expr) -> Expr {
  HasTag(self, tag)
}

///|
/// self.attr
pub fn Expr::access(self : Expr, attr : String) -> Expr {
  GetAttr(self, attr)
}

///|
/// self has attr
pub fn Expr::has(self : Expr, attr : String) -> Expr {
  HasAttr(self, attr)
}

///|
/// self like pattern
pub fn Expr::like(self : Expr, pattern : Pattern) -> Expr {
  Like(self, pattern)
}

///|
/// self is EntityType  (keyword: is)
pub fn Expr::is_(self : Expr, ty : EntityType) -> Expr {
  Expr::Is(self, ty)
}

///|
/// self is ty AND self in entity  — desugared to and_ + is_ + in_
pub fn Expr::is_in(self : Expr, ty : EntityType, entity : Expr) -> Expr {
  let lhs = Expr::Is(self, ty)
  And(lhs, BinaryApp(In_, lhs, entity))
}