///|
pub struct OverlapReport {
  minimum : Double
  maximum : Double
  treated_minimum : Double
  treated_maximum : Double
  control_minimum : Double
  control_maximum : Double
  common_support_lower : Double
  common_support_upper : Double
  overlap_fraction : Double
}

///|
pub fn overlap_report(
  propensity_scores : Array[Double],
  treatment : Array[Bool],
) -> OverlapReport {
  let treated = group_values(propensity_scores, treatment, true)
  let control = group_values(propensity_scores, treatment, false)
  let minimum = quantile(propensity_scores, 0.0)
  let maximum = quantile(propensity_scores, 1.0)
  let treated_minimum = quantile(treated, 0.0)
  let treated_maximum = quantile(treated, 1.0)
  let control_minimum = quantile(control, 0.0)
  let control_maximum = quantile(control, 1.0)
  let lower = if treated_minimum > control_minimum {
    treated_minimum
  } else {
    control_minimum
  }
  let upper = if treated_maximum < control_maximum {
    treated_maximum
  } else {
    control_maximum
  }
  let mut inside = 0
  for score in propensity_scores {
    if score >= lower && score <= upper {
      inside += 1
    }
  }
  {
    minimum,
    maximum,
    treated_minimum,
    treated_maximum,
    control_minimum,
    control_maximum,
    common_support_lower: lower,
    common_support_upper: upper,
    overlap_fraction: if propensity_scores.length() == 0 {
      0.0
    } else {
      inside.to_double() / propensity_scores.length().to_double()
    },
  }
}

///|
pub fn positivity_violations(
  propensity_scores : Array[Double],
  epsilon : Double,
) -> Int {
  let threshold = clamp(epsilon, 0.0, 0.5)
  let mut violations = 0
  for score in propensity_scores {
    if score <= threshold || score >= 1.0 - threshold {
      violations += 1
    }
  }
  violations
}

///|
pub fn propensity_calibration(
  propensity_scores : Array[Double],
  treatment : Array[Bool],
  bins : Int,
) -> Array[Double] {
  let result : Array[Double] = Array::new(
    capacity=if bins > 0 { bins } else { 0 },
  )
  if bins <= 0 {
    return result
  }
  for bin in 0..= lower &&
        (propensity_scores[i] < upper || bin == bins - 1) {
        expected += propensity_scores[i]
        if treatment[i] {
          observed += 1.0
        }
        count += 1
      }
    }
    if count == 0 {
      result.push(0.0)
    } else {
      result.push((observed - expected) / count.to_double())
    }
  }
  result
}

///|
pub fn standardized_residuals(
  outcomes : Array[Double],
  predictions : Array[Double],
) -> Array[Double] {
  let residual = Array::new(capacity=outcomes.length())
  let n = if outcomes.length() < predictions.length() {
    outcomes.length()
  } else {
    predictions.length()
  }
  for i in 0.. Double {
  let residuals = standardized_residuals(outcomes, predictions)
  let mut result = 0.0
  for value in residuals {
    result += value.abs()
  }
  if residuals.length() == 0 {
    0.0
  } else {
    result / residuals.length().to_double()
  }
}

///|
pub fn root_mean_squared_error(
  outcomes : Array[Double],
  predictions : Array[Double],
) -> Double {
  let n = if outcomes.length() < predictions.length() {
    outcomes.length()
  } else {
    predictions.length()
  }
  if n == 0 {
    return 0.0
  }
  let mut result = 0.0
  for i in 0..