///|
pub(all) enum Decision {
  Interesting
  Uninteresting
  Unresolved
} derive(Debug, Eq, ToJson)

///|
/// Validated fixed-size evidence policy; None means an unresolved execution.
pub struct Policy {
  repeat : Int
  quorum : Int
  consecutive : Bool
}

///|
/// Bound evaluation cost and reject impossible quorum settings.
pub fn Policy::new(
  repeat : Int,
  quorum : Int,
  consecutive? : Bool = false,
) -> Result[Policy, String] {
  if repeat < 1 || repeat > 100 || quorum < 1 || quorum > repeat {
    Err("MR_CFG_STABILITY: require 1 <= quorum <= repeat <= 100")
  } else {
    Ok({ repeat, quorum, consecutive })
  }
}

///|
/// Decide only complete batches. Unknown outcomes pass only when success is
/// already certain, fail only when success is impossible, otherwise unresolved.
pub fn Policy::decide(self : Policy, observations : Array[Bool?]) -> Decision {
  if observations.length() != self.repeat {
    return Unresolved
  }
  let mut certain = 0
  let mut possible = 0
  let mut best_certain = 0
  let mut best_possible = 0
  for observation in observations {
    match observation {
      Some(true) => {
        certain += 1
        possible += 1
      }
      Some(false) =>
        if self.consecutive {
          certain = 0
          possible = 0
        }
      None => {
        possible += 1
        if self.consecutive {
          certain = 0
        }
      }
    }
    best_certain = best_certain.max(certain)
    best_possible = best_possible.max(possible)
  }
  if best_certain >= self.quorum {
    Interesting
  } else if best_possible < self.quorum {
    Uninteresting
  } else {
    Unresolved
  }
}

///|
pub(all) struct Statistics {
  attempts : Int
  resolved : Int
  passed : Int
  rate : Double
  lower : Double
  upper : Double
} derive(Debug, ToJson)

///|
/// Observed success rate and two-sided 95% Wilson interval among resolved runs.
/// Unresolved counts remain explicit; an empty sample has interval [0, 1].
pub fn summarize(observations : Array[Bool?]) -> Statistics {
  let mut resolved = 0
  let mut passed = 0
  for observation in observations {
    if observation is Some(value) {
      resolved += 1
      if value {
        passed += 1
      }
    }
  }
  if resolved == 0 {
    return {
      attempts: observations.length(),
      resolved,
      passed,
      rate: 0.0,
      lower: 0.0,
      upper: 1.0,
    }
  }
  let n = resolved.to_double()
  let rate = passed.to_double() / n
  let z = 1.959963984540054
  let denominator = 1.0 + z * z / n
  let center = (rate + z * z / (2.0 * n)) / denominator
  let radius = z *
    (rate * (1.0 - rate) / n + z * z / (4.0 * n * n)).sqrt() /
    denominator
  {
    attempts: observations.length(),
    resolved,
    passed,
    rate,
    lower: (center - radius).max(0.0),
    upper: (center + radius).min(1.0),
  }
}