///|
/// Assertions over finished fixtures, independent of the generation model.
pub(all) enum Expectation {
  RowCount(String, Int, Int)
  NonNull(String, String)
  IntegerBounds(String, String, Int, Int)
  TextLength(String, String, Int, Int)
  LessEqual(String, String, String)
  SumEquals(String, String, Int64)
  RelatedCount(String, String, String, String, Int, Int)
}

///|
/// checked_rows counts row visits across assertions. Multiple rules may examine
/// the same row. A failure cap truncates diagnostics, not assertion evaluation.
pub fn check_expectations(
  data : Dataset,
  expectations : Array[Expectation],
  max_issues? : Int = 100,
) -> ValidationReport {
  let issues : Array[Issue] = []
  let mut truncated = false
  let mut checked_rows = 0
  fn add(code : String, path : String, message : String) -> Unit {
    if issues.length() >= max_issues.max(1) {
      truncated = true
    } else {
      issues.push(issue(code, path, message))
    }
  }
  for expectation in expectations {
    let name = match expectation {
      RowCount(name, _, _)
      | NonNull(name, _)
      | IntegerBounds(name, _, _, _)
      | TextLength(name, _, _, _)
      | LessEqual(name, _, _)
      | SumEquals(name, _, _)
      | RelatedCount(name, _, _, _, _, _) => name
    }
    let table = match data.table(name) {
      Some(table) => table
      None => {
        add("expectation_table", name, "Assertion table is missing")
        continue
      }
    }
    match expectation {
      RowCount(_, min, max) =>
        if min < 0 || min > max {
          add("invalid_expectation", name, "Row count bounds are invalid")
        } else if table.rows.length() < min || table.rows.length() > max {
          add(
            "expected_row_count", name, "Row count falls outside asserted bounds",
          )
        }
      NonNull(_, field) =>
        for ri in 0.. {
        if min > max {
          add(
            "invalid_expectation",
            name + "." + field,
            "Integer bounds are reversed",
          )
          continue
        }
        for ri in 0.. value >= min && value <= max
            _ => false
          }
          if !valid {
            add(
              "expected_integer_bounds",
              validation_path(name, ri, field),
              "Expected an integer within bounds",
            )
          }
        }
      }
      TextLength(_, field, min, max) => {
        if min < 0 || min > max {
          add(
            "invalid_expectation",
            name + "." + field,
            "Text length bounds are invalid",
          )
          continue
        }
        for ri in 0.. {
              let length = text.to_array().length()
              length >= min && length <= max
            }
            _ => false
          }
          if !valid {
            add(
              "expected_text_length",
              validation_path(name, ri, field),
              "Expected text within Unicode scalar length bounds",
            )
          }
        }
      }
      LessEqual(_, left, right) =>
        for ri in 0.. a <= b
            _ => false
          }
          if !valid {
            add(
              "expected_order",
              validation_path(name, ri, left),
              "Expected two integers with left <= right",
            )
          }
        }
      SumEquals(_, field, expected) => {
        let mut sum = 0L
        let mut valid = true
        for ri in 0.. sum += value.to_int64()
            _ => {
              valid = false
              add(
                "expected_sum_type",
                validation_path(name, ri, field),
                "Sum expects non-null integers",
              )
            }
          }
        }
        if valid && sum != expected {
          add(
            "expected_sum",
            name + "." + field,
            "Integer sum differs from the expected value",
          )
        }
      }
      RelatedCount(_, foreign_key, parent_name, parent_key, min, max) => {
        if min < 0 || min > max {
          add("invalid_expectation", name, "Related count bounds are invalid")
          continue
        }
        let parent = match data.table(parent_name) {
          Some(table) => table
          None => {
            add(
              "expectation_table", parent_name, "Parent assertion table is missing",
            )
            continue
          }
        }
        let counts : Map[String, Int] = Map([])
        for ri in 0..
              add(
                "expectation_parent_key",
                validation_path(parent_name, ri, parent_key),
                "Parent key must be non-null",
              )
            Some(value) => {
              if counts.contains(value.key()) {
                add(
                  "expectation_parent_key",
                  validation_path(parent_name, ri, parent_key),
                  "Parent key must be unique",
                )
              }
              counts[value.key()] = 0
            }
          }
        }
        for ri in 0.. ()
            None =>
              add(
                "expectation_foreign_key",
                validation_path(name, ri, foreign_key),
                "Foreign key field is missing",
              )
            Some(value) =>
              match counts.get(value.key()) {
                Some(count) => counts[value.key()] = count + 1
                None =>
                  add(
                    "expectation_foreign_key",
                    validation_path(name, ri, foreign_key),
                    "No parent matches this foreign key",
                  )
              }
          }
        }
        // Report in parent row order, not hash-map iteration order.
        for ri in 0.. max {
              add(
                "expected_related_count",
                validation_path(parent_name, ri, parent_key),
                "Child count falls outside asserted bounds",
              )
            }
          }
        }
      }
    }
  }
  { issues, checked_rows, truncated, }
}