///|
/// Represents a timestamped video frame.
pub(all) struct VideoFrame {
  frame_id : Int
  timestamp_seconds : Double
  image : Image
} derive(Debug)

///|
/// Creates a new VideoFrame.
pub fn VideoFrame::new(
  frame_id : Int,
  timestamp_seconds : Double,
  image : Image,
) -> VideoFrame {
  { frame_id, timestamp_seconds, image }
}

///|
/// Ring-buffer video frame stream with temporal smoothing and motion compensation.
pub(all) struct VideoStreamPipeline {
  capacity : Int
  frames : Array[VideoFrame]
  detector_params : DetectorParameters
  dictionary : MarkerDictionary
  tracker : MultiMarkerTracker
  camera : CameraIntrinsics?
  marker_length : Double
} derive(Debug)

///|
/// Creates a new VideoStreamPipeline.
pub fn VideoStreamPipeline::new(
  dictionary : MarkerDictionary,
  capacity? : Int = 30,
  camera? : CameraIntrinsics?,
  marker_length? : Double = 0.05,
) -> VideoStreamPipeline {
  let cam = match camera {
    Some(c) => c
    None => None
  }
  {
    capacity,
    frames: [],
    detector_params: DetectorParameters::default(),
    dictionary,
    tracker: MultiMarkerTracker::new(),
    camera: cam,
    marker_length,
  }
}

///|
/// Pushes a new frame into the stream and performs integrated detection and multi-target tracking.
pub fn VideoStreamPipeline::process_frame(
  self : VideoStreamPipeline,
  frame : VideoFrame,
) -> Array[MarkerTrack] {
  // Add to ring buffer
  self.frames.push(frame)
  if self.frames.length() > self.capacity {
    let _ = self.frames.remove(0)
  }

  // Detect markers
  let detections = detect_markers_with_params(
    frame.image,
    self.dictionary,
    self.detector_params,
    camera_matrix=self.camera,
    marker_length=self.marker_length,
  )

  // Compute dt from previous frame if available
  let dt = if self.frames.length() >= 2 {
    let prev = self.frames[self.frames.length() - 2]
    let diff = frame.timestamp_seconds - prev.timestamp_seconds
    if diff > 0.001 && diff < 1.0 {
      diff
    } else {
      0.033
    }
  } else {
    0.033
  }

  // Update multi-target Kalman / EKF tracker
  self.tracker.update(detections, dt~)
}