///|
pub fn reduction_cost_model() -> String {
  "moonbit-nodes-int-magnitude-utf8-v1"
}

///|
fn Expr::integer_magnitude(self : Expr) -> Int64 {
  match self {
    IntLiteral(n) => {
      let widened = n.to_int64()
      if widened < 0L {
        -widened
      } else {
        widened
      }
    }
    Hole(_) | BoolLiteral(_) | Ref(_) => 0L
    Let(_, value, body)
    | Add(value, body)
    | Subtract(value, body)
    | Less(value, body) => value.integer_magnitude() + body.integer_magnitude()
    Not(e) => e.integer_magnitude()
    If(condition, yes, no) =>
      condition.integer_magnitude() +
      yes.integer_magnitude() +
      no.integer_magnitude()
    Call(_, arguments) | Tuple(arguments) | Variant(_, _, arguments) =>
      arguments.fold(init=0L, (n, e) => n + e.integer_magnitude())
    Project(value, _) => value.integer_magnitude()
    Match(_, value, arms) =>
      arms.fold(init=value.integer_magnitude(), (n, arm) => {
        n + arm.body.integer_magnitude()
      })
  }
}

///|
pub fn CompleteProgram::cost(
  self : CompleteProgram,
) -> @contracts.ReductionCost {
  {
    nodes: self.size(),
    integer_magnitude: self.syntax.functions.fold(
      init=self.syntax.result.integer_magnitude(),
      (n, f) => n + f.body.integer_magnitude(),
    ),
    source_bytes: @utf8.encode(self.emit()).length(),
  }
}

///|
pub fn CompleteProgram::is_smaller_than(
  self : CompleteProgram,
  other : CompleteProgram,
) -> Bool {
  let candidate = self.cost()
  let incumbent = other.cost()
  cost_is_smaller(candidate, incumbent)
}

///|
fn cost_is_smaller(
  candidate : @contracts.ReductionCost,
  incumbent : @contracts.ReductionCost,
) -> Bool {
  if candidate.nodes != incumbent.nodes {
    candidate.nodes < incumbent.nodes
  } else if candidate.integer_magnitude != incumbent.integer_magnitude {
    candidate.integer_magnitude < incumbent.integer_magnitude
  } else {
    candidate.source_bytes < incumbent.source_bytes
  }
}