///|
/// Unified evaluation context shared by render, audio, and event consumers.
pub struct EvaluationContext2 {
  time : Double
  frame : Int
  normalized : Double
  delta : Double
  direction : Int
} derive(Debug)

///|
pub fn evaluation_context2(
  time : Double,
  frame : Int,
  normalized : Double,
  delta : Double,
  direction : Int,
) -> EvaluationContext2 {
  {
    time,
    frame,
    normalized: clamp01(normalized),
    delta: delta.max(0.0),
    direction: if direction < 0 {
      -1
    } else {
      1
    },
  }
}

///|
pub fn EvaluationContext2::time(self : EvaluationContext2) -> Double {
  self.time
}

///|
pub fn EvaluationContext2::frame(self : EvaluationContext2) -> Int {
  self.frame
}

///|
pub fn EvaluationContext2::normalized(self : EvaluationContext2) -> Double {
  self.normalized
}

///|
pub fn EvaluationContext2::delta(self : EvaluationContext2) -> Double {
  self.delta
}

///|
pub fn EvaluationContext2::direction(self : EvaluationContext2) -> Int {
  self.direction
}

///|
pub struct TimelineEvaluation2 {
  context : EvaluationContext2
  active_nodes : Array[TimelineGraphNode]
  render_frame : RenderFrame2
  events : Array[MotionEvent2]
} derive(Debug)

///|
pub fn TimelineEvaluation2::context(
  self : TimelineEvaluation2,
) -> EvaluationContext2 {
  self.context
}

///|
pub fn TimelineEvaluation2::active_nodes(
  self : TimelineEvaluation2,
) -> Array[TimelineGraphNode] {
  self.active_nodes.copy()
}

///|
pub fn TimelineEvaluation2::render_frame(
  self : TimelineEvaluation2,
) -> RenderFrame2 {
  self.render_frame
}

///|
pub fn TimelineEvaluation2::events(
  self : TimelineEvaluation2,
) -> Array[MotionEvent2] {
  self.events.copy()
}

///|
pub struct TimelineEvaluator2 {
  graph : TimelineGraph
  render_plan : RenderPlan2
  event_queue : MotionEventQueue2
  mut previous_time : Double
} derive(Debug)

///|
pub fn timeline_evaluator2(
  graph : TimelineGraph,
  render_plan : RenderPlan2,
) -> TimelineEvaluator2 {
  { graph, render_plan, event_queue: motion_event_queue2(), previous_time: 0.0 }
}

///|
pub fn TimelineEvaluator2::graph(self : TimelineEvaluator2) -> TimelineGraph {
  self.graph
}

///|
pub fn TimelineEvaluator2::render_plan(
  self : TimelineEvaluator2,
) -> RenderPlan2 {
  self.render_plan
}

///|
pub fn TimelineEvaluator2::queue(
  self : TimelineEvaluator2,
) -> MotionEventQueue2 {
  self.event_queue
}

///|
pub fn TimelineEvaluator2::add_event(
  self : TimelineEvaluator2,
  event : MotionEvent2,
) -> Unit {
  self.event_queue.push(event)
}

///|
pub fn TimelineEvaluator2::seek(
  self : TimelineEvaluator2,
  time : Double,
) -> Unit {
  self.previous_time = time
  self.event_queue.reset(time)
}

///|
pub fn TimelineEvaluator2::evaluate(
  self : TimelineEvaluator2,
  time : Double,
  frame : Int,
) -> TimelineEvaluation2 {
  let delta = (time - self.previous_time).abs()
  let range = self.graph.time_range()
  let normalized = if range.1 == range.0 {
    0.0
  } else {
    (time - range.0) / (range.1 - range.0)
  }
  let direction = if time >= self.previous_time { 1 } else { -1 }
  let context = evaluation_context2(time, frame, normalized, delta, direction)
  let active_nodes = self.graph.active_at(time)
  let render_frame = self.render_plan.frame(time)
  let events = if direction > 0 {
    self.event_queue.poll_until(time)
  } else {
    self.event_queue.poll_window(time, self.previous_time)
  }
  self.previous_time = time
  { context, active_nodes, render_frame, events }
}

///|
pub fn TimelineEvaluator2::evaluate_schedule(
  self : TimelineEvaluator2,
  schedule : FrameSchedule2,
) -> Array[TimelineEvaluation2] {
  let result : Array[TimelineEvaluation2] = []
  for index in 0.. Int {
  self.graph.active_at(time).length()
}

///|
pub struct EvaluationStats2 {
  frames : Int
  active_nodes : Int
  render_passes : Int
  events : Int
  duration : Double
} derive(Debug)

///|
pub fn EvaluationStats2::frames(self : EvaluationStats2) -> Int {
  self.frames
}

///|
pub fn EvaluationStats2::active_nodes(self : EvaluationStats2) -> Int {
  self.active_nodes
}

///|
pub fn EvaluationStats2::render_passes(self : EvaluationStats2) -> Int {
  self.render_passes
}

///|
pub fn EvaluationStats2::events(self : EvaluationStats2) -> Int {
  self.events
}

///|
pub fn EvaluationStats2::duration(self : EvaluationStats2) -> Double {
  self.duration
}

///|
pub fn summarize_evaluations(
  values : Array[TimelineEvaluation2],
) -> EvaluationStats2 {
  let mut nodes = 0
  let mut passes = 0
  let mut events = 0
  let mut first = 0.0
  let mut last = 0.0
  for index in 0.. Array[Double] {
  let result : Array[Double] = []
  for value in values {
    result.push(value.context.normalized)
  }
  result
}

///|
pub fn evaluation_times(values : Array[TimelineEvaluation2]) -> Array[Double] {
  let result : Array[Double] = []
  for value in values {
    result.push(value.context.time)
  }
  result
}

///|
pub fn evaluate_scalar_track(
  track : ScalarTrack,
  schedule : FrameSchedule2,
) -> ScalarSeries2 {
  let times = schedule.timestamps()
  let values : Array[Double] = []
  for time in times {
    values.push(track.sample(time))
  }
  scalar_series2(times, values)
}

///|
pub fn evaluate_point2_track(
  track : Point2Track,
  schedule : FrameSchedule2,
) -> PointSeries2 {
  let times = schedule.timestamps()
  let values : Array[Point2] = []
  for time in times {
    values.push(track.sample(time))
  }
  point_series2(times, values)
}

///|
pub fn evaluate_color_track(
  track : RgbaTrack,
  schedule : FrameSchedule2,
) -> ColorSeries2 {
  let times = schedule.timestamps()
  let values : Array[Rgba] = []
  for time in times {
    values.push(track.sample(time))
  }
  color_series2(times, values)
}