///|
pub(all) enum Direction {
  Increasing
  Decreasing
} derive(Debug)

///|
pub(all) struct Sample {
  x : Double
  y : Double
  weight : Double
} derive(Debug)

///|
pub(all) struct Block {
  start : Int
  end : Int
  weight : Double
  value : Double
} derive(Debug)

///|
pub(all) struct Fit {
  values : Array[Double]
  blocks : Array[Block]
  objective : Double
  direction : Direction
} derive(Debug)

///|
pub fn sample(x : Double, y : Double, weight? : Double = 1.0) -> Sample {
  { x, y, weight, }
}

///|
fn finite(x : Double) -> Bool {
  x == x && x.abs() <= 1.7976931348623157e308
}

///|
fn validate_series(
  y : Array[Double],
  weights : Array[Double],
) -> Result[Unit, String] {
  if y.length() == 0 {
    return Err("empty observations")
  }
  if y.length() != weights.length() {
    return Err("weight length mismatch")
  }
  let mut total = 0.0
  for i = 0; i < y.length(); i = i + 1 {
    if !finite(y[i]) {
      return Err("nonfinite observation")
    }
    if !finite(weights[i]) || weights[i] <= 0.0 {
      return Err("weights must be positive and finite")
    }
    total = total + weights[i]
    if !finite(total) {
      return Err("weight sum overflow")
    }
  }
  Ok(())
}

///|
pub fn validate_samples(samples : Array[Sample]) -> Result[Unit, String] {
  let y = []
  let weights = []
  for s in samples {
    if !finite(s.x) {
      return Err("nonfinite coordinate")
    }
    y.push(s.y)
    weights.push(s.weight)
  }
  validate_series(y, weights)
}

///|
fn before(a : Double, b : Double, direction : Direction) -> Bool {
  match direction {
    Increasing => a <= b
    Decreasing => a >= b
  }
}

///|
fn squared_loss(
  y : Array[Double],
  values : Array[Double],
  weights : Array[Double],
) -> Result[Double, String] {
  let mut loss = 0.0
  for i = 0; i < y.length(); i = i + 1 {
    let residual = values[i] - y[i]
    loss = loss + weights[i] * residual * residual
    if !finite(loss) {
      return Err("objective overflow")
    }
  }
  Ok(loss)
}

///|
#deprecated
pub extend Direction with @debug.Debug::{to_repr}

///|
#deprecated
pub extend Sample with @debug.Debug::{to_repr}

///|
#deprecated
pub extend Block with @debug.Debug::{to_repr}

///|
#deprecated
pub extend Fit with @debug.Debug::{to_repr}