///|
/// Online Platt scaling for turning arbitrary scores into calibrated
/// probabilities.
pub struct OnlinePlattScaler {
  mut slope : Double
  mut intercept : Double
  learning_rate : Double
  mut steps : Int
}

///|
pub fn OnlinePlattScaler::new(
  learning_rate? : Double = 0.01,
) -> OnlinePlattScaler {
  { slope: 1.0, intercept: 0.0, learning_rate, steps: 0 }
}

///|
pub fn OnlinePlattScaler::predict(
  self : OnlinePlattScaler,
  score : Double,
) -> Double {
  sigmoid(self.slope * score + self.intercept)
}

///|
pub fn OnlinePlattScaler::update(
  self : OnlinePlattScaler,
  score : Double,
  label : Double,
) -> Unit {
  let prediction = self.predict(score)
  let error = prediction - clamp(label, 0.0, 1.0)
  self.slope -= self.learning_rate * error * score
  self.intercept -= self.learning_rate * error
  self.steps += 1
}

///|
pub fn OnlinePlattScaler::slope(self : OnlinePlattScaler) -> Double {
  self.slope
}

///|
pub fn OnlinePlattScaler::intercept(self : OnlinePlattScaler) -> Double {
  self.intercept
}

///|
pub fn OnlinePlattScaler::steps(self : OnlinePlattScaler) -> Int {
  self.steps
}

///|
pub fn OnlinePlattScaler::reset(self : OnlinePlattScaler) -> Unit {
  self.slope = 1.0
  self.intercept = 0.0
  self.steps = 0
}

///|
pub struct OnlineIsotonicCalibrator {
  scores : Array[Double]
  labels : Array[Double]
  capacity : Int
}

///|
pub fn OnlineIsotonicCalibrator::new(
  capacity? : Int = 256,
) -> OnlineIsotonicCalibrator {
  { scores: [], labels: [], capacity: if capacity < 2 { 2 } else { capacity } }
}

///|
pub fn OnlineIsotonicCalibrator::update(
  self : OnlineIsotonicCalibrator,
  score : Double,
  label : Double,
) -> Unit {
  self.scores.push(score)
  self.labels.push(clamp(label, 0.0, 1.0))
  if self.scores.length() > self.capacity {
    let _ = self.scores.remove(0)
    let _ = self.labels.remove(0)
  }
}

///|
pub fn OnlineIsotonicCalibrator::predict(
  self : OnlineIsotonicCalibrator,
  score : Double,
) -> Double {
  if self.scores.is_empty() {
    sigmoid(score)
  } else {
    let mut closest = 0
    let mut distance = (self.scores[0] - score).abs()
    for i in 1.. Int {
  self.scores.length()
}

///|
pub fn OnlineIsotonicCalibrator::reset(self : OnlineIsotonicCalibrator) -> Unit {
  self.scores.clear()
  self.labels.clear()
}