///|
/// Batch sampling helpers used by exporters, benchmark harnesses, and
/// analysis tools. The series stores timestamps beside values so non-uniform
/// editorial edits remain observable.
pub struct ScalarSeries2 {
  times : Array[Double]
  values : Array[Double]
} derive(Debug)

///|
pub fn scalar_series2(
  times : Array[Double],
  values : Array[Double],
) -> ScalarSeries2 {
  { times: times.copy(), values: values.copy() }
}

///|
pub fn ScalarSeries2::times(self : ScalarSeries2) -> Array[Double] {
  self.times.copy()
}

///|
pub fn ScalarSeries2::values(self : ScalarSeries2) -> Array[Double] {
  self.values.copy()
}

///|
pub fn ScalarSeries2::length(self : ScalarSeries2) -> Int {
  self.times.length().min(self.values.length())
}

///|
pub fn ScalarSeries2::duration(self : ScalarSeries2) -> Double {
  if self.length() < 2 {
    0.0
  } else {
    self.times[self.length() - 1] - self.times[0]
  }
}

///|
pub fn ScalarSeries2::sample(self : ScalarSeries2, time : Double) -> Double {
  let count = self.length()
  if count == 0 {
    return 0.0
  }
  if time <= self.times[0] {
    return self.values[0]
  }
  if time >= self.times[count - 1] {
    return self.values[count - 1]
  }
  for index in 0..<(count - 1) {
    if time <= self.times[index + 1] {
      let span = self.times[index + 1] - self.times[index]
      let ratio = if span == 0.0 {
        0.0
      } else {
        (time - self.times[index]) / span
      }
      return self.values[index] +
        (self.values[index + 1] - self.values[index]) * ratio
    }
  }
  self.values[count - 1]
}

///|
pub fn ScalarSeries2::derivatives(self : ScalarSeries2) -> Array[Double] {
  let values : Array[Double] = []
  let count = self.length()
  for index in 0.. Double {
  let mut area = 0.0
  for index in 1.. (Double, Double) {
  if self.length() == 0 {
    return (0.0, 0.0)
  }
  let mut minimum = self.values[0]
  let mut maximum = minimum
  for index in 1.. ScalarSeries2 {
  let actual = count.max(1)
  let times : Array[Double] = []
  let values : Array[Double] = []
  let start = if self.length() == 0 { 0.0 } else { self.times[0] }
  let end = if self.length() == 0 { 0.0 } else { self.times[self.length() - 1] }
  for index in 0.. Double,
) -> ScalarSeries2 {
  let values : Array[Double] = []
  for value in self.values {
    values.push(mapper(value))
  }
  scalar_series2(self.times, values)
}

///|
pub struct PointSeries2 {
  times : Array[Double]
  values : Array[Point2]
} derive(Debug)

///|
pub fn point_series2(
  times : Array[Double],
  values : Array[Point2],
) -> PointSeries2 {
  { times: times.copy(), values: values.copy() }
}

///|
pub fn PointSeries2::times(self : PointSeries2) -> Array[Double] {
  self.times.copy()
}

///|
pub fn PointSeries2::values(self : PointSeries2) -> Array[Point2] {
  self.values.copy()
}

///|
pub fn PointSeries2::sample(self : PointSeries2, time : Double) -> Point2 {
  if self.values.length() == 0 {
    return point2(x=0.0, y=0.0)
  }
  if time <= self.times[0] {
    return self.values[0]
  }
  let last = self.values.length() - 1
  if time >= self.times[last] {
    return self.values[last]
  }
  for index in 0.. Array[Double] {
  let values : Array[Double] = []
  if self.values.length() == 0 {
    return values
  }
  values.push(0.0)
  for index in 1.. PointSeries2 {
  let actual = count.max(1)
  let times : Array[Double] = []
  let values : Array[Point2] = []
  let start = if self.times.length() == 0 { 0.0 } else { self.times[0] }
  let end = if self.times.length() == 0 {
    0.0
  } else {
    self.times[self.times.length() - 1]
  }
  for index in 0.. ColorSeries2 {
  { times: times.copy(), values: values.copy() }
}

///|
pub fn ColorSeries2::sample(self : ColorSeries2, time : Double) -> Rgba {
  if self.values.length() == 0 {
    return rgba(r=0.0, g=0.0, b=0.0, a=0.0)
  }
  if time <= self.times[0] {
    return self.values[0]
  }
  let last = self.values.length() - 1
  if time >= self.times[last] {
    return self.values[last]
  }
  for index in 0.. ScalarSeries2 {
  let actual = count.max(1)
  let times : Array[Double] = []
  let values : Array[Double] = []
  for index in 0.. ScalarSeries2 {
  let actual = count.max(1)
  let times : Array[Double] = []
  let values : Array[Double] = []
  for index in 0..