///|
/// The field references used by a contract's rules.
pub(all) struct ContractReferences {
  referenced_fields : Array[String]
  missing_fields : Array[String]
  unused_fields : Array[String]
} derive(Eq, Debug)

///|
/// Analyze rule-to-field dependencies before executing a contract.
pub fn analyze_contract_references(contract : Contract) -> ContractReferences {
  let referenced_fields = []
  for rule in contract.rules {
    for field in rule_field_references(rule) {
      if !referenced_fields.contains(field) {
        referenced_fields.push(field)
      }
    }
  }
  referenced_fields.sort()
  let declared_fields = contract_field_names(contract)
  let missing_fields = referenced_fields.filter(fn(field) {
    !declared_fields.contains(field)
  })
  let unused_fields = declared_fields.filter(fn(field) {
    !referenced_fields.contains(field)
  })
  { referenced_fields, missing_fields, unused_fields }
}

///|
/// Return the fields a rule reads, preserving the rule's declaration order.
pub fn rule_field_references(rule : Rule) -> Array[String] {
  match rule {
    Unique(fields~, ..) => deduplicate_fields(fields)
    Completeness(field~, ..) => [field]
    Enum(field~, ..) => [field]
    IntRange(field~, ..) => [field]
    CompareInts(left~, right~, ..) => deduplicate_fields([left, right])
    PatternMatch(field~, ..) => [field]
    StringLength(field~, ..) => [field]
    DistinctCount(field~, ..) => [field]
    RequiredIf(condition_field~, field~, ..) =>
      deduplicate_fields([condition_field, field])
    RowCount(..) => []
  }
}

///|
fn deduplicate_fields(fields : Array[String]) -> Array[String] {
  let result = []
  for field in fields {
    if !result.contains(field) {
      result.push(field)
    }
  }
  result
}