///|
/// One parsed Rego module: its package, imports and rules.
pub(all) struct Module {
  package_path : Array[String]
  imports : Array[Import]
  rules : Array[Rule]
  position : Position
} derive(Eq, @debug.Debug)

///|
pub extend Module with Eq::{equal, not_equal}

///|
pub extend Module with @debug.Debug::{to_repr}

///|
pub fn Module::new(
  package_path : Array[String],
  imports : Array[Import],
  rules : Array[Rule],
  position : Position,
) -> Module {
  { package_path, imports, rules, position, }
}

///|
/// One `import` declaration. `path` keeps every segment, so
/// `import data.foo.bar as baz` is `["data", "foo", "bar"]` with alias `baz`.
pub(all) struct Import {
  path : Array[String]
  alias_name : String?
  position : Position
} derive(Eq, @debug.Debug)

///|
pub extend Import with Eq::{equal, not_equal}

///|
pub extend Import with @debug.Debug::{to_repr}

///|
pub fn Import::new(
  path : Array[String],
  alias_name : String?,
  position : Position,
) -> Import {
  { path, alias_name, position, }
}

///|
/// The shape of a rule head.
pub(all) enum RuleKind {
  /// `name := value` or `name if { body }`
  Complete
  /// `name contains element if { body }`
  PartialSet
  /// `name[key] := value if { body }`
  PartialObject
  /// `name(args) := value if { body }`
  Function
} derive(Eq, @debug.Debug)

///|
pub extend RuleKind with Eq::{equal, not_equal}

///|
pub extend RuleKind with @debug.Debug::{to_repr}

///|
pub extend RuleKind with Show::{to_string, output}

///|
pub impl Show for RuleKind with fn output(self, logger) {
  logger.write_string(
    match self {
      Complete => "complete"
      PartialSet => "partial set"
      PartialObject => "partial object"
      Function => "function"
    },
  )
}

///|
/// One rule, including its head, optional value, body and `else` branches.
///
/// An empty `body` means the rule is a fact. A `None` value means the rule
/// produces `true` when its body holds, as in `allow if { ... }`.
pub(all) struct Rule {
  kind : RuleKind
  name : String
  args : Array[String]
  key : Term?
  value : Term?
  body : Array[Expr]
  is_default : Bool
  else_rules : Array[ElseRule]
  position : Position
} derive(Eq, @debug.Debug)

///|
pub extend Rule with Eq::{equal, not_equal}

///|
pub extend Rule with @debug.Debug::{to_repr}

///|
fn Rule::head(
  kind : RuleKind,
  name : String,
  args : Array[String],
  key : Term?,
  position : Position,
) -> Rule {
  {
    kind,
    name,
    args,
    key,
    value: None,
    body: [],
    is_default: false,
    else_rules: [],
    position,
  }
}

///|
/// A complete rule: `name := value` or `name if { body }`.
pub fn Rule::complete(
  name : String,
  value : Term?,
  body : Array[Expr],
  position : Position,
) -> Rule {
  let rule = Rule::head(Complete, name, [], None, position)
  { ..rule, value, body, }
}

///|
/// A partial set rule: `name contains element if { body }`.
pub fn Rule::partial_set(
  name : String,
  element : Term,
  body : Array[Expr],
  position : Position,
) -> Rule {
  let rule = Rule::head(PartialSet, name, [], Some(element), position)
  { ..rule, body, }
}

///|
/// A partial object rule: `name[key] := value if { body }`.
pub fn Rule::partial_object(
  name : String,
  key : Term,
  value : Term?,
  body : Array[Expr],
  position : Position,
) -> Rule {
  let rule = Rule::head(PartialObject, name, [], Some(key), position)
  { ..rule, value, body, }
}

///|
/// A function rule: `name(args) := value if { body }`.
pub fn Rule::function(
  name : String,
  args : Array[String],
  value : Term?,
  body : Array[Expr],
  position : Position,
) -> Rule {
  let rule = Rule::head(Function, name, args, None, position)
  { ..rule, value, body, }
}

///|
/// One `else` branch of a rule.
pub(all) struct ElseRule {
  value : Term?
  body : Array[Expr]
  position : Position
} derive(Eq, @debug.Debug)

///|
pub extend ElseRule with Eq::{equal, not_equal}

///|
pub extend ElseRule with @debug.Debug::{to_repr}

///|
pub fn ElseRule::new(
  value : Term?,
  body : Array[Expr],
  position : Position,
) -> ElseRule {
  { value, body, position, }
}

///|
/// A comparison operator inside an expression.
pub(all) enum CompareOp {
  CmpEqual
  CmpNotEqual
  CmpLess
  CmpLessEqual
  CmpGreater
  CmpGreaterEqual
} derive(Eq, @debug.Debug)

///|
pub extend CompareOp with Eq::{equal, not_equal}

///|
pub extend CompareOp with @debug.Debug::{to_repr}

///|
pub extend CompareOp with Show::{to_string, output}

///|
pub impl Show for CompareOp with fn output(self, logger) {
  logger.write_string(
    match self {
      CmpEqual => "=="
      CmpNotEqual => "!="
      CmpLess => "<"
      CmpLessEqual => "<="
      CmpGreater => ">"
      CmpGreaterEqual => ">="
    },
  )
}

///|
/// The comparison operator for one token, or `None` when the token is not a
/// comparison.
pub fn CompareOp::from_token(kind : TokenKind) -> CompareOp? {
  match kind {
    EqualEqual => Some(CmpEqual)
    NotEqual => Some(CmpNotEqual)
    Less => Some(CmpLess)
    LessEqual => Some(CmpLessEqual)
    Greater => Some(CmpGreater)
    GreaterEqual => Some(CmpGreaterEqual)
    _ => None
  }
}

///|
/// One expression in a rule body.
pub(all) struct Expr {
  kind : ExprKind
  position : Position
} derive(Eq, @debug.Debug)

///|
pub extend Expr with Eq::{equal, not_equal}

///|
pub extend Expr with @debug.Debug::{to_repr}

///|
pub fn Expr::new(kind : ExprKind, position : Position) -> Expr {
  { kind, position, }
}

///|
/// The shape of one expression.
pub(all) enum ExprKind {
  /// A bare term, which is true when the term is defined and not false.
  Term(Term)
  /// `term := term`
  Assign(Term, Term)
  /// `term = term`
  Unify(Term, Term)
  /// `left op right`
  Compare(CompareOp, Term, Term)
  /// `some x, y`
  SomeVars(Array[Term])
  /// `some x, y in collection`
  SomeIn(Array[Term], Term)
  /// `every x in collection { body }`
  Every(Array[Term], Term, Array[Expr])
  /// `not { body }` or `not expr`
  Not(Array[Expr])
  /// `expr with target as value`
  With(Expr, Array[WithModifier])
} derive(Eq, @debug.Debug)

///|
pub extend ExprKind with Eq::{equal, not_equal}

///|
pub extend ExprKind with @debug.Debug::{to_repr}

///|
pub fn Expr::term(term : Term, position : Position) -> Expr {
  Expr::new(Term(term), position)
}

///|
pub fn Expr::assign(target : Term, value : Term, position : Position) -> Expr {
  Expr::new(Assign(target, value), position)
}

///|
pub fn Expr::unify(target : Term, value : Term, position : Position) -> Expr {
  Expr::new(Unify(target, value), position)
}

///|
pub fn Expr::compare(
  op : CompareOp,
  left : Term,
  right : Term,
  position : Position,
) -> Expr {
  Expr::new(Compare(op, left, right), position)
}

///|
pub fn Expr::some_vars(vars : Array[Term], position : Position) -> Expr {
  Expr::new(SomeVars(vars), position)
}

///|
pub fn Expr::some_in(
  vars : Array[Term],
  collection : Term,
  position : Position,
) -> Expr {
  Expr::new(SomeIn(vars, collection), position)
}

///|
pub fn Expr::every(
  vars : Array[Term],
  collection : Term,
  body : Array[Expr],
  position : Position,
) -> Expr {
  Expr::new(Every(vars, collection, body), position)
}

///|
pub fn Expr::not(body : Array[Expr], position : Position) -> Expr {
  Expr::new(Not(body), position)
}

///|
pub fn Expr::with_modifiers(
  expr : Expr,
  modifiers : Array[WithModifier],
  position : Position,
) -> Expr {
  Expr::new(With(expr, modifiers), position)
}

///|
/// One `with target as value` modifier.
pub(all) struct WithModifier {
  target : Term
  value : Term
} derive(Eq, @debug.Debug)

///|
pub extend WithModifier with Eq::{equal, not_equal}

///|
pub extend WithModifier with @debug.Debug::{to_repr}

///|
pub fn WithModifier::new(target : Term, value : Term) -> WithModifier {
  { target, value, }
}

///|
/// A term: a value-producing piece of syntax.
///
/// Numbers keep their literal text, because Rego numbers are arbitrary
/// precision and the evaluator decides how to represent them. A leading minus
/// is folded into the literal by the parser.
pub(all) enum Term {
  NullTerm
  BoolTerm(Bool)
  NumberTerm(String)
  StringTerm(String)
  Var(String)
  Ref(Term, Array[RefStep])
  ArrayLit(Array[Term])
  ObjectLit(Array[(Term, Term)])
  SetLit(Array[Term])
  Call(Term, Array[Term])
  ArrayComprehension(Array[Expr], Term)
  SetComprehension(Array[Expr], Term)
  ObjectComprehension(Array[Expr], Term, Term)
} derive(Eq, @debug.Debug)

///|
pub extend Term with Eq::{equal, not_equal}

///|
pub extend Term with @debug.Debug::{to_repr}

///|
pub fn Term::null() -> Term {
  NullTerm
}

///|
pub fn Term::boolean(value : Bool) -> Term {
  BoolTerm(value)
}

///|
pub fn Term::number(literal : String) -> Term {
  NumberTerm(literal)
}

///|
pub fn Term::string(value : String) -> Term {
  StringTerm(value)
}

///|
pub fn Term::variable(name : String) -> Term {
  Var(name)
}

///|
pub fn Term::reference(base : Term, steps : Array[RefStep]) -> Term {
  Ref(base, steps)
}

///|
pub fn Term::array_lit(items : Array[Term]) -> Term {
  ArrayLit(items)
}

///|
pub fn Term::object_lit(pairs : Array[(Term, Term)]) -> Term {
  ObjectLit(pairs)
}

///|
pub fn Term::set_lit(items : Array[Term]) -> Term {
  SetLit(items)
}

///|
pub fn Term::call(name : String, args : Array[Term]) -> Term {
  Call(Term::variable(name), args)
}

///|
pub fn Term::call_term(callee : Term, args : Array[Term]) -> Term {
  Call(callee, args)
}

///|
pub fn Term::array_comprehension(body : Array[Expr], value : Term) -> Term {
  ArrayComprehension(body, value)
}

///|
pub fn Term::set_comprehension(body : Array[Expr], value : Term) -> Term {
  SetComprehension(body, value)
}

///|
pub fn Term::object_comprehension(
  body : Array[Expr],
  key : Term,
  value : Term,
) -> Term {
  ObjectComprehension(body, key, value)
}

///|
/// One step of a reference: a field access or an index expression.
pub(all) enum RefStep {
  Field(String)
  Index(Term)
} derive(Eq, @debug.Debug)

///|
pub extend RefStep with Eq::{equal, not_equal}

///|
pub extend RefStep with @debug.Debug::{to_repr}

///|
pub fn RefStep::field(name : String) -> RefStep {
  Field(name)
}

///|
pub fn RefStep::index(term : Term) -> RefStep {
  Index(term)
}