///|
pub fn validate_same_length(
  first : Array[Double],
  second : Array[Double],
) -> Bool {
  first.length() == second.length()
}

///|
pub fn validate_non_empty(data : Array[Double]) -> Bool {
  data.length() > 0
}

///|
pub fn validate_window(window : Int, data_length : Int) -> Bool {
  window > 0 && data_length >= 0
}

///|
pub fn validate_trim_percent_value(trim_percent : Double) -> Bool {
  trim_percent >= 0.0 && trim_percent < 0.5
}

///|
pub fn validate_probability_value(probability : Double) -> Bool {
  probability >= 0.0 && probability <= 1.0
}

///|
pub fn validate_weights(weights : Array[Double]) -> Bool {
  for weight in weights {
    if weight < 0.0 {
      return false
    }
  }
  true
}

///|
pub fn validate_matrix(matrix : Array[Array[Double]]) -> Bool {
  if matrix.length() == 0 {
    return true
  }
  let width = matrix[0].length()
  for row in matrix {
    if row.length() != width {
      return false
    }
  }
  true
}

///|
pub fn validate_square_matrix(matrix : Array[Array[Double]]) -> Bool {
  validate_matrix(matrix) &&
  (matrix.length() == 0 || matrix[0].length() == matrix.length())
}

///|
pub fn validate_finite_range(
  data : Array[Double],
  lower : Double,
  upper : Double,
) -> Bool {
  if lower > upper {
    return false
  }
  for value in data {
    if value < lower || value > upper {
      return false
    }
  }
  true
}

///|
pub fn validate_sorted(data : Array[Double]) -> Bool {
  is_sorted_non_decreasing(data) || is_sorted_non_increasing(data)
}

///|
pub fn validate_confidence_value(confidence : Double) -> Bool {
  confidence > 0.0 && confidence < 1.0
}

///|
pub fn validate_positive(value : Double) -> Bool {
  value > 0.0
}

///|
pub fn validate_non_negative(value : Double) -> Bool {
  value >= 0.0
}

///|
pub fn validate_count(value : Int) -> Bool {
  value >= 0
}

///|
pub fn validate_cluster_configuration(
  cluster_count : Int,
  max_iter : Int,
  tol : Double,
) -> Bool {
  cluster_count > 0 && max_iter > 0 && tol > 0.0
}

///|
pub fn validate_regression_inputs(x : Array[Double], y : Array[Double]) -> Bool {
  x.length() > 0 && x.length() == y.length()
}

///|
pub fn validate_bootstrap_configuration(
  data : Array[Double],
  replicates : Int,
  sample_size : Int,
) -> Bool {
  data.length() > 0 && replicates > 0 && sample_size > 0
}

///|
pub fn validate_stream_configuration(capacity : Int, clip : Double) -> Bool {
  capacity > 0 && clip > 0.0
}

///|
pub fn validate_detector_configuration(
  window : Int,
  threshold : Double,
) -> Bool {
  window > 0 && threshold > 0.0
}

///|
pub fn validate_probability_grid(probabilities : Array[Double]) -> Bool {
  for probability in probabilities {
    if !validate_probability_value(probability) {
      return false
    }
  }
  true
}

///|
pub fn validate_monotone_grid(values : Array[Double]) -> Bool {
  is_sorted_non_decreasing(values)
}

///|
pub fn validate_no_empty_rows(matrix : Array[Array[Double]]) -> Bool {
  for row in matrix {
    if row.length() == 0 {
      return false
    }
  }
  true
}

///|
pub fn validation_score(data : Array[Double]) -> Double {
  if data.length() == 0 {
    return 0.0
  }
  let checks = [
    validate_non_empty(data),
    validate_sorted(data),
    is_constant(data),
    validate_non_negative(mad(data)),
    validate_non_negative(interquartile_range(data)),
  ]
  let mut passed = 0
  for check in checks {
    if check {
      passed += 1
    }
  }
  passed.to_double() / checks.length().to_double()
}

///|
pub fn robust_input_score(data : Array[Double]) -> Double {
  if data.length() == 0 {
    0.0
  } else {
    1.0 - outlier_fraction(outlier_indices_z(data), data.length())
  }
}

///|
pub fn compare_input_scores(
  first : Array[Double],
  second : Array[Double],
) -> Double {
  robust_input_score(first) - robust_input_score(second)
}

///|
pub fn validation_report(data : Array[Double]) -> Array[Double] {
  [
    validation_score(data),
    robust_input_score(data),
    duplicate_fraction(data),
    monotonicity_score(data),
    zero_fraction(data),
  ]
}