///|
/// Paired x/y samples with a length invariant.
pub struct Series {
  xs : Array[Double]
  ys : Array[Double]
}

///|
/// Construct a series. The two input arrays must have the same length.
pub fn series(xs : Array[Double], ys : Array[Double]) -> Series raise DataError {
  if xs.length() != ys.length() {
    raise DataError::LengthMismatch(expected=xs.length(), actual=ys.length())
  }
  { xs: xs.copy(), ys: ys.copy(), }
}

///|
/// Number of samples in a series.
pub fn Series::length(self : Series) -> Int32 {
  self.xs.length()
}

///|
/// Return the x samples in their original order, including non-finite values.
pub fn Series::x(self : Series) -> Array[Double] {
  self.xs.copy()
}

///|
/// Return the y samples in their original order, including non-finite values.
pub fn Series::y(self : Series) -> Array[Double] {
  self.ys.copy()
}

///|
/// Convert paired samples to points without dropping missing values.
pub fn Series::points(self : Series) -> Array[Point] {
  let points = Array(capacity=self.length())
  for i in 0.. Bounds? {
  bounds_from_points(self.points())
}

///|
/// Compute limits from finite points only. Missing points are ignored.
pub fn bounds_from_points(points : Array[Point]) -> Bounds? {
  let mut result : Bounds? = None
  for p in points {
    if p.is_finite() {
      result = match result {
        None => Some(bounds(p.x(), p.x(), p.y(), p.y()))
        Some(current) =>
          Some(
            bounds(
              Double::min(current.xmin(), p.x()),
              Double::max(current.xmax(), p.x()),
              Double::min(current.ymin(), p.y()),
              Double::max(current.ymax(), p.y()),
            ),
          )
      }
    }
  }
  result
}