///|
/// Interpolation curves for animated or time-varying pattern parameters.
pub(all) enum Easing {
  Linear
  Smooth
  EaseIn
  EaseOut
  EaseInOut
  Elastic
} derive(Eq, Debug, ToJson)

///|
pub fn ease(kind : Easing, time : Double) -> Double {
  let t = if time < 0.0 { 0.0 } else if time > 1.0 { 1.0 } else { time }
  match kind {
    Linear => t
    Smooth => t * t * (3.0 - 2.0 * t)
    EaseIn => t * t
    EaseOut => 1.0 - (1.0 - t) * (1.0 - t)
    EaseInOut =>
      if t < 0.5 {
        2.0 * t * t
      } else {
        1.0 - @math.pow(-2.0 * t + 2.0, 2.0) / 2.0
      }
    Elastic =>
      if t == 0.0 || t == 1.0 {
        t
      } else {
        -@math.pow(2.0, 10.0 * t - 10.0) *
        @math.sin((t * 10.0 - 10.75) * 2.0 * @math.PI / 3.0)
      }
  }
}

///|
pub fn animate_double(
  start : Double,
  end : Double,
  time : Double,
  easing? : Easing = Linear,
) -> Double {
  start + (end - start) * ease(easing, time)
}

///|
pub fn animate_vec2(
  start : Vec2,
  end : Vec2,
  time : Double,
  easing? : Easing = Linear,
) -> Vec2 {
  start.lerp(end, ease(easing, time))
}

///|
pub fn animate_color(
  start : Color,
  end : Color,
  time : Double,
  easing? : Easing = Linear,
) -> Color {
  start.mix(end, ease(easing, time))
}

///|
pub(all) struct Keyframe[T] {
  time : Double
  value : T
} derive(Debug)

///|
pub fn[T] keyframe(time : Double, value : T) -> Keyframe[T] {
  { time, value }
}

///|
pub fn[T] sample_keyframes(
  frames : Array[Keyframe[T]],
  time : Double,
  blend : (T, T, Double) -> T,
) -> T? {
  if frames.length() == 0 {
    None
  } else if time <= frames[0].time {
    Some(frames[0].value)
  } else {
    let last = frames[frames.length() - 1]
    if time >= last.time {
      Some(last.value)
    } else {
      let mut result = None
      for i in 0..<(frames.length() - 1) {
        let left = frames[i]
        let right = frames[i + 1]
        if time >= left.time && time <= right.time {
          let amount = (time - left.time) / (right.time - left.time)
          result = Some(blend(left.value, right.value, amount))
        }
      }
      result
    }
  }
}

///|
pub fn frames_for_double(
  frames : Array[Keyframe[Double]],
  time : Double,
) -> Double? {
  sample_keyframes(frames, time, fn(left, right, amount) {
    left + (right - left) * amount
  })
}