///|
/// Reusable sample caches for repeated timeline evaluation. Cache keys are
/// explicit so callers can invalidate only the track or resolution that
/// changed during editing.
pub struct SampleCacheKey2 {
  track_id : Int
  revision : Int
  samples : Int
  start : Double
  end : Double
} derive(Debug)

///|
pub fn sample_cache_key2(
  track_id : Int,
  revision : Int,
  samples : Int,
  start : Double,
  end : Double,
) -> SampleCacheKey2 {
  { track_id, revision, samples: samples.max(1), start, end }
}

///|
pub fn SampleCacheKey2::track_id(self : SampleCacheKey2) -> Int {
  self.track_id
}

///|
pub fn SampleCacheKey2::revision(self : SampleCacheKey2) -> Int {
  self.revision
}

///|
pub fn SampleCacheKey2::samples(self : SampleCacheKey2) -> Int {
  self.samples
}

///|
pub fn SampleCacheKey2::matches(
  self : SampleCacheKey2,
  other : SampleCacheKey2,
) -> Bool {
  self.track_id == other.track_id &&
  self.revision == other.revision &&
  self.samples == other.samples &&
  self.start == other.start &&
  self.end == other.end
}

///|
pub struct ScalarSampleCache2 {
  key : SampleCacheKey2
  times : Array[Double]
  values : Array[Double]
} derive(Debug)

///|
pub fn ScalarSampleCache2::new(
  key : SampleCacheKey2,
  values : Array[Double],
) -> ScalarSampleCache2 {
  let times : Array[Double] = []
  for index in 0.. SampleCacheKey2 {
  self.key
}

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

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

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

///|
pub fn ScalarSampleCache2::sample(
  self : ScalarSampleCache2,
  time : Double,
) -> Double {
  if self.values.length() == 0 {
    return 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.. Double {
  if self.values.length() < 2 {
    return 0.0
  }
  let epsilon = (self.key.end - self.key.start) /
    (self.values.length() - 1).to_double()
  (self.sample(time + epsilon) - self.sample(time - epsilon)) / (2.0 * epsilon)
}

///|
pub struct PointSampleCache2 {
  key : SampleCacheKey2
  values : Array[Point2]
} derive(Debug)

///|
pub fn PointSampleCache2::new(
  key : SampleCacheKey2,
  values : Array[Point2],
) -> PointSampleCache2 {
  { key, values: values.copy() }
}

///|
pub fn PointSampleCache2::key(self : PointSampleCache2) -> SampleCacheKey2 {
  self.key
}

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

///|
pub fn PointSampleCache2::sample(
  self : PointSampleCache2,
  time : Double,
) -> Point2 {
  if self.values.length() == 0 {
    return point2(x=0.0, y=0.0)
  }
  let ratio = if self.key.end == self.key.start {
    0.0
  } else {
    clamp01((time - self.key.start) / (self.key.end - self.key.start))
  }
  let position = ratio * (self.values.length() - 1).to_double()
  let lower = @math.floor(position)
    .to_int()
    .min(self.values.length() - 2)
    .max(0)
  self.values[lower].lerp(self.values[lower + 1], position - lower.to_double())
}

///|
pub struct ColorSampleCache2 {
  key : SampleCacheKey2
  values : Array[Rgba]
} derive(Debug)

///|
pub fn ColorSampleCache2::new(
  key : SampleCacheKey2,
  values : Array[Rgba],
) -> ColorSampleCache2 {
  { key, values: values.copy() }
}

///|
pub fn ColorSampleCache2::key(self : ColorSampleCache2) -> SampleCacheKey2 {
  self.key
}

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

///|
pub fn ColorSampleCache2::sample(
  self : ColorSampleCache2,
  time : Double,
) -> Rgba {
  if self.values.length() == 0 {
    return rgba(r=0.0, g=0.0, b=0.0, a=0.0)
  }
  let ratio = if self.key.end == self.key.start {
    0.0
  } else {
    clamp01((time - self.key.start) / (self.key.end - self.key.start))
  }
  let position = ratio * (self.values.length() - 1).to_double()
  let lower = @math.floor(position)
    .to_int()
    .min(self.values.length() - 2)
    .max(0)
  self.values[lower].lerp_linear(
    self.values[lower + 1],
    position - lower.to_double(),
  )
}

///|
pub struct CachePool2 {
  scalar : Array[ScalarSampleCache2]
  point : Array[PointSampleCache2]
  color : Array[ColorSampleCache2]
  capacity : Int
} derive(Debug)

///|
pub fn cache_pool2(capacity? : Int = 16) -> CachePool2 {
  { scalar: [], point: [], color: [], capacity: capacity.max(1) }
}

///|
pub fn CachePool2::capacity(self : CachePool2) -> Int {
  self.capacity
}

///|
pub fn CachePool2::scalar_count(self : CachePool2) -> Int {
  self.scalar.length()
}

///|
pub fn CachePool2::point_count(self : CachePool2) -> Int {
  self.point.length()
}

///|
pub fn CachePool2::color_count(self : CachePool2) -> Int {
  self.color.length()
}

///|
pub fn CachePool2::put_scalar(
  self : CachePool2,
  cache : ScalarSampleCache2,
) -> Unit {
  self.scalar.push(cache)
  while self.scalar.length() > self.capacity {
    ignore(self.scalar.remove(0))
  }
}

///|
pub fn CachePool2::put_point(
  self : CachePool2,
  cache : PointSampleCache2,
) -> Unit {
  self.point.push(cache)
  while self.point.length() > self.capacity {
    ignore(self.point.remove(0))
  }
}

///|
pub fn CachePool2::put_color(
  self : CachePool2,
  cache : ColorSampleCache2,
) -> Unit {
  self.color.push(cache)
  while self.color.length() > self.capacity {
    ignore(self.color.remove(0))
  }
}

///|
pub fn CachePool2::find_scalar(
  self : CachePool2,
  key : SampleCacheKey2,
) -> ScalarSampleCache2? {
  for cache in self.scalar {
    if cache.key.matches(key) {
      return Some(cache)
    }
  }
  None
}

///|
pub fn CachePool2::find_point(
  self : CachePool2,
  key : SampleCacheKey2,
) -> PointSampleCache2? {
  for cache in self.point {
    if cache.key.matches(key) {
      return Some(cache)
    }
  }
  None
}

///|
pub fn CachePool2::find_color(
  self : CachePool2,
  key : SampleCacheKey2,
) -> ColorSampleCache2? {
  for cache in self.color {
    if cache.key.matches(key) {
      return Some(cache)
    }
  }
  None
}

///|
pub fn CachePool2::clear(self : CachePool2) -> Unit {
  self.scalar.clear()
  self.point.clear()
  self.color.clear()
}