///|
/// Machine-readable contract checks for production analytics inputs.
pub struct ContractCheck {
  name : String
  passed : Bool
  observed : Double
  expected : Double
  message : String
}

///|
pub struct ContractSummary {
  checks : Array[ContractCheck]
  passed : Int
  failed : Int
  score : Double
}

///|
pub fn contract_check(
  name : String,
  passed : Bool,
  observed : Double,
  expected : Double,
  message : String,
) -> ContractCheck {
  { name, passed, observed, expected, message }
}

///|
pub fn contract_summary(checks : Array[ContractCheck]) -> ContractSummary {
  let mut passed = 0
  for check in checks {
    if check.passed {
      passed += 1
    }
  }
  {
    checks,
    passed,
    failed: checks.length() - passed,
    score: if checks.length() == 0 {
      1.0
    } else {
      passed.to_double() / checks.length().to_double()
    },
  }
}

///|
pub fn contract_numeric(data : Array[Double]) -> ContractSummary {
  let checks = [
    contract_check(
      "non-empty",
      data.length() > 0,
      data.length().to_double(),
      1.0,
      "input must contain observations",
    ),
    contract_check(
      "finite",
      validation_finite(data, "numeric").failed == 0,
      data.length().to_double(),
      data.length().to_double(),
      "values must be finite",
    ),
    contract_check(
      "variance",
      sample_variance(data) >= 0.0,
      sample_variance(data),
      0.0,
      "variance cannot be negative",
    ),
  ]
  contract_summary(checks)
}

///|
pub fn contract_pair(
  left : Array[Double],
  right : Array[Double],
) -> ContractSummary {
  let checks = [
    contract_check(
      "same-length",
      left.length() == right.length(),
      left.length().to_double(),
      right.length().to_double(),
      "paired inputs must align",
    ),
    contract_check(
      "left-finite",
      validation_finite(left, "left").failed == 0,
      left.length().to_double(),
      left.length().to_double(),
      "left values must be finite",
    ),
    contract_check(
      "right-finite",
      validation_finite(right, "right").failed == 0,
      right.length().to_double(),
      right.length().to_double(),
      "right values must be finite",
    ),
  ]
  contract_summary(checks)
}

///|
pub fn contract_probability(data : Array[Double]) -> ContractSummary {
  let checks = [
    contract_check(
      "probability-range",
      validation_probability(data, "probability").failed == 0,
      min_value(data),
      0.0,
      "probabilities must be bounded",
    ),
    contract_check(
      "probability-upper",
      max_value(data) <= 1.0,
      max_value(data),
      1.0,
      "probabilities must not exceed one",
    ),
  ]
  contract_summary(checks)
}

///|
pub fn contract_matrix(matrix : Array[Array[Double]]) -> ContractSummary {
  let report = validation_matrix(matrix, "matrix")
  contract_summary([
    contract_check(
      "matrix-valid",
      report.failed == 0,
      report.score,
      1.0,
      "matrix must be rectangular and finite",
    ),
  ])
}

///|
pub fn contract_passed(summary : ContractSummary) -> Bool {
  summary.failed == 0
}

///|
pub fn contract_score(summary : ContractSummary) -> Double {
  summary.score
}

///|
pub fn contract_names(summary : ContractSummary) -> Array[String] {
  let result = []
  for check in summary.checks {
    result.push(check.name)
  }
  result
}

///|
pub fn contract_failures(summary : ContractSummary) -> Array[ContractCheck] {
  let result = []
  for check in summary.checks {
    if !check.passed {
      result.push(check)
    }
  }
  result
}

///|
pub fn contract_lines(summary : ContractSummary) -> Array[String] {
  let lines = [
    "passed=" + summary.passed.to_string(),
    "failed=" + summary.failed.to_string(),
    "score=" + summary.score.to_string(),
  ]
  for check in summary.checks {
    lines.push(
      check.name +
      "|" +
      check.passed.to_string() +
      "|" +
      check.observed.to_string() +
      "|" +
      check.expected.to_string() +
      "|" +
      check.message,
    )
  }
  lines
}

///|
pub fn contract_string(summary : ContractSummary) -> String {
  contract_lines(summary).join("\n")
}

///|
pub fn contract_merge(
  left : ContractSummary,
  right : ContractSummary,
) -> ContractSummary {
  let checks = left.checks.copy()
  for check in right.checks {
    checks.push(check)
  }
  contract_summary(checks)
}

///|
pub fn contract_batch(data_sets : Array[Array[Double]]) -> Array[Double] {
  let result = []
  for data in data_sets {
    result.push(contract_score(contract_numeric(data)))
  }
  result
}

///|
pub fn contract_quality_gate(data : Array[Double], threshold : Double) -> Bool {
  contract_score(contract_numeric(data)) >= threshold
}

///|
pub fn contract_summary_vector(summary : ContractSummary) -> Array[Double] {
  [
    summary.passed.to_double(),
    summary.failed.to_double(),
    summary.score,
    if contract_passed(summary) {
      1.0
    } else {
      0.0
    },
  ]
}