///|
/// QuickCheck compatibility helpers for legacy PBT tests.

///|
struct CheckConfig {
  cases : Int
  max_size : Int
  seed : Int
  max_shrinks : Int
  discard_ratio : Int
}

///|
pub fn CheckConfig::seed(self : CheckConfig) -> Int {
  self.seed
}

///|
pub fn CheckConfig::new(
  cases : Int,
  max_size : Int,
  seed : Int,
  max_shrinks : Int,
  discard_ratio? : Int = 10,
) -> CheckConfig {
  { cases, max_size, seed, max_shrinks, discard_ratio }
}

///|
pub fn CheckConfig::default() -> CheckConfig {
  CheckConfig::new(100, 30, 0, 100)
}

///|
struct CheckResult {
  passed : Bool
  stats : String?
}

///|
pub fn CheckResult::passed(self : CheckResult) -> Bool {
  self.passed
}

///|
pub fn CheckResult::stats(self : CheckResult) -> String? {
  self.stats
}

///|
fn[A] property_from_result(
  name : String,
  f : (A) -> Result[Unit, String],
) -> (A) -> @pbt.Property {
  fn(x : A) {
    match f(x) {
      Ok(_) => @pbt.counterexample(true, name)
      Err(msg) => @pbt.counterexample(false, name + ": " + msg)
    }
  }
}

///|
pub fn[A : Show] assert_check(
  name : String,
  gen : @pbt.Gen[A],
  check : (A) -> Result[Unit, String],
  config? : CheckConfig = CheckConfig::default(),
  shrink? : (A) -> Iter[A],
) -> Unit {
  let cfg = config
  let _ = cfg
  let prop_fn = property_from_result(name, check)
  let prop = match shrink {
    Some(shr) => @pbt.forall_shrink(gen, shr, prop_fn)
    None => @pbt.forall(gen, prop_fn)
  }
  try! @pbt.quick_check(prop)
}

///|
pub fn[A : Show] check_with_stats(
  gen : @pbt.Gen[A],
  check : (A) -> (Result[Unit, String], String?),
  config? : CheckConfig = CheckConfig::default(),
) -> CheckResult {
  let cfg = config
  let _ = cfg
  let prop = @pbt.forall(gen, fn(x) {
    let (res, label) = check(x)
    let base = match res {
      Ok(_) => @pbt.counterexample(true, "")
      Err(msg) => @pbt.counterexample(false, msg)
    }
    match label {
      Some(lbl) => @pbt.classify(base, true, lbl)
      None => base
    }
  })
  let result : Result[Unit, Error] = try? @pbt.quick_check(prop)
  match result {
    Ok(_) => CheckResult::{ passed: true, stats: None }
    Err(err) => CheckResult::{ passed: false, stats: Some("\{err}") }
  }
}

///|
/// Size-driven array generator compatible with previous Gen::array_of.
pub fn[T] array_of(gen : @pbt.Gen[T]) -> @pbt.Gen[Array[T]] {
  @pbt.sized(fn(size) { @pbt.Gen::array_with_size(gen, size) })
}

///|
/// Simple integer shrinker for legacy shrink_int usage.
pub fn shrink_int(value : Int) -> Iter[Int] {
  let candidates : Array[Int] = if value == 0 { [] } else { [0, value / 2] }
  candidates.iter()
}