///|
/// Timeline marker for editors, captions, and export orchestration.
pub(all) struct TimelineMarker {
  name : String
  time_cs : Int
  frame_index : Int
  color : Rgba
} derive(Debug)

///|
pub fn TimelineMarker::new(
  name : String,
  time_cs : Int,
  color : Rgba,
) -> TimelineMarker {
  {
    name,
    time_cs: if time_cs < 0 {
      0
    } else {
      time_cs
    },
    frame_index: -1,
    color,
  }
}

///|
pub fn TimelineMarker::name(self : TimelineMarker) -> String {
  self.name
}

///|
pub fn TimelineMarker::time(self : TimelineMarker) -> Int {
  self.time_cs
}

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

///|
pub fn TimelineMarker::color(self : TimelineMarker) -> Rgba {
  self.color
}

///|
pub(all) struct AnimationTimeline {
  animation : Animation
  markers : Array[TimelineMarker]
} derive(Debug)

///|
pub fn AnimationTimeline::new(animation : Animation) -> AnimationTimeline {
  { animation, markers: [] }
}

///|
pub fn AnimationTimeline::animation(self : AnimationTimeline) -> Animation {
  self.animation
}

///|
pub fn AnimationTimeline::markers(
  self : AnimationTimeline,
) -> ArrayView[TimelineMarker] {
  self.markers[:]
}

///|
pub fn AnimationTimeline::mark(
  self : AnimationTimeline,
  marker : TimelineMarker,
) -> AnimationTimeline {
  let position = self.animation.position_at(marker.time())
  let frame_index = match position {
    None => -1
    Some(value) => value.index()
  }
  self.markers.push({ ..marker, frame_index, })
  self
}

///|
pub fn AnimationTimeline::mark_frame(
  self : AnimationTimeline,
  name : String,
  index : Int,
  color : Rgba,
) -> AnimationTimeline {
  let safe_index = if index < 0 {
    0
  } else if index >= self.animation.frame_count() {
    self.animation.frame_count() - 1
  } else {
    index
  }
  let time = if safe_index < 0 {
    0
  } else {
    self.animation.timeline_starts()[safe_index]
  }
  self.mark({ name, time_cs: time, frame_index: safe_index, color })
}

///|
pub fn AnimationTimeline::at_time(
  self : AnimationTimeline,
  time_cs : Int,
) -> Array[TimelineMarker] {
  let result : Array[TimelineMarker] = []
  for marker in self.markers {
    if marker.time() == time_cs {
      result.push(marker)
    }
  }
  result
}

///|
pub fn AnimationTimeline::between(
  self : AnimationTimeline,
  start_cs : Int,
  end_cs : Int,
) -> Array[TimelineMarker] {
  let low = if start_cs < end_cs { start_cs } else { end_cs }
  let high = if start_cs < end_cs { end_cs } else { start_cs }
  let result : Array[TimelineMarker] = []
  for marker in self.markers {
    if marker.time() >= low && marker.time() <= high {
      result.push(marker)
    }
  }
  result
}

///|
/// Move all markers by a time offset while clamping at zero.
pub fn AnimationTimeline::shift(
  self : AnimationTimeline,
  offset_cs : Int,
) -> AnimationTimeline {
  let markers : Array[TimelineMarker] = []
  for marker in self.markers {
    let time = if marker.time() + offset_cs < 0 {
      0
    } else {
      marker.time() + offset_cs
    }
    markers.push({ ..marker, time_cs: time })
  }
  { ..self, markers, }
}

///|
/// Scale all marker positions using the same integer tempo rule as retiming.
pub fn AnimationTimeline::scale_time(
  self : AnimationTimeline,
  per_mille : Int,
) -> AnimationTimeline {
  let safe = if per_mille < 0 { 0 } else { per_mille }
  let markers : Array[TimelineMarker] = []
  for marker in self.markers {
    markers.push({ ..marker, time_cs: marker.time() * safe / 1000 })
  }
  { ..self, markers, }
}

///|
/// Render a marker strip aligned with the animation timeline.
pub fn AnimationTimeline::marker_strip(
  self : AnimationTimeline,
  width : Int,
  height : Int,
) -> Canvas {
  let safe_width = if width < 1 { 1 } else { width }
  let safe_height = if height < 1 { 1 } else { height }
  let result = Canvas::new(
    width=safe_width,
    height=safe_height,
    fill=rgba(0, 0, 0, 0),
  )
  let duration = self.animation.duration_cs()
  for marker in self.markers {
    let x = if duration <= 0 {
      0
    } else {
      marker.time() * (safe_width - 1) / duration
    }
    ignore(
      result.draw_line(
        x0=x,
        y0=0,
        x1=x,
        y1=safe_height - 1,
        color=marker.color(),
      ),
    )
  }
  result
}

///|
/// Attach marker-derived delays to a sequence of frames.
pub fn AnimationTimeline::split_at_markers(
  self : AnimationTimeline,
) -> Animation {
  let result = self.animation
  if self.markers.length() == 0 {
    result
  } else {
    let starts = result.timeline_starts()
    for marker in self.markers {
      let mut nearest = 0
      for index, start in starts {
        if start <= marker.time() {
          nearest = index
        }
      }
      if nearest >= 0 && nearest < result.frames.length() {
        let frame = result.frames[nearest]
        let remaining = if marker.time() > starts[nearest] {
          marker.time() - starts[nearest]
        } else {
          0
        }
        result.frames[nearest] = frame.with_delay(delay_cs=remaining)
      }
    }
    result
  }
}

///|
/// Return marker names in timeline order.
pub fn AnimationTimeline::names(self : AnimationTimeline) -> Array[String] {
  let result : Array[String] = []
  for marker in self.markers {
    result.push(marker.name())
  }
  result
}

///|
/// Remove duplicate markers at the same time and name.
pub fn AnimationTimeline::deduplicate(
  self : AnimationTimeline,
) -> AnimationTimeline {
  let result : Array[TimelineMarker] = []
  for marker in self.markers {
    let mut present = false
    for existing in result {
      if existing.time() == marker.time() && existing.name() == marker.name() {
        present = true
      }
    }
    if !present {
      result.push(marker)
    }
  }
  { ..self, markers: result }
}