///|
/// A timestamped 2D trajectory waypoint coordinate.
pub struct TrajectoryPoint {
  x : Double
  y : Double
  t : Double
  track_id : Int
} derive(Eq, Debug, Default)

///|
/// Creates a new timestamped trajectory waypoint.
pub fn TrajectoryPoint::new(
  x : Double,
  y : Double,
  t : Double,
  track_id : Int,
) -> TrajectoryPoint {
  TrajectoryPoint::{ x, y, t, track_id, }
}

///|
/// A directed 2D trajectory segment connecting two consecutive waypoints in spacetime.
pub struct TrajectorySegment {
  p0_x : Double
  p0_y : Double
  p0_t : Double
  p1_x : Double
  p1_y : Double
  p1_t : Double
  track_id : Int
  segment_id : Int
} derive(Eq, Debug, Default)

///|
/// Creates a new directed trajectory segment with track and segment IDs.
pub fn TrajectorySegment::new(
  p0_x : Double,
  p0_y : Double,
  p0_t : Double,
  p1_x : Double,
  p1_y : Double,
  p1_t : Double,
  track_id : Int,
  segment_id : Int,
) -> TrajectorySegment {
  TrajectorySegment::{
    p0_x,
    p0_y,
    p0_t,
    p1_x,
    p1_y,
    p1_t,
    track_id,
    segment_id,
  }
}

///|
/// Computes 2D spatial bounding box enclosing the trajectory segment.
pub fn TrajectorySegment::bounding_rect(self : TrajectorySegment) -> Rect {
  let min_x = if self.p0_x < self.p1_x { self.p0_x } else { self.p1_x }
  let max_x = if self.p0_x > self.p1_x { self.p0_x } else { self.p1_x }
  let min_y = if self.p0_y < self.p1_y { self.p0_y } else { self.p1_y }
  let max_y = if self.p0_y > self.p1_y { self.p0_y } else { self.p1_y }
  Rect::new(min_x, min_y, max_x, max_y)
}

///|
/// Computes temporal duration (|p1_t - p0_t|).
pub fn TrajectorySegment::duration(self : TrajectorySegment) -> Double {
  let dt = self.p1_t - self.p0_t
  if dt < 0.0 {
    -dt
  } else {
    dt
  }
}

///|
/// Computes Euclidean 2D spatial path length.
pub fn TrajectorySegment::length(self : TrajectorySegment) -> Double {
  let dx = self.p1_x - self.p0_x
  let dy = self.p1_y - self.p0_y
  (dx * dx + dy * dy).sqrt()
}

///|
/// Computes average kinematic speed (length / duration).
pub fn TrajectorySegment::speed(self : TrajectorySegment) -> Double {
  let dur = self.duration()
  if dur <= 1.0e-9 {
    0.0
  } else {
    self.length() / dur
  }
}

///|
/// Linearly interpolates (x, y) spatial coordinates at timestamp t.
pub fn TrajectorySegment::interpolate_position(
  self : TrajectorySegment,
  t : Double,
) -> (Double, Double)? {
  let t_start = if self.p0_t < self.p1_t { self.p0_t } else { self.p1_t }
  let t_end = if self.p0_t > self.p1_t { self.p0_t } else { self.p1_t }
  if t < t_start || t > t_end {
    return None
  }
  let span = self.p1_t - self.p0_t
  if span.abs() <= 1.0e-9 {
    return Some((self.p0_x, self.p0_y))
  }
  let alpha = (t - self.p0_t) / span
  let ix = self.p0_x + alpha * (self.p1_x - self.p0_x)
  let iy = self.p0_y + alpha * (self.p1_y - self.p0_y)
  Some((ix, iy))
}

///|
/// Spatiotemporal sliding-window trajectory segment index.
pub struct TrajectoryWindowIndex {
  priv inner : @trajectory.TrajectoryWindowIndex
} derive(Eq, Debug, Default)

///|
/// Builds a TrajectoryWindowIndex from an array of TrajectorySegments.
pub fn TrajectoryWindowIndex::build(
  segments : Array[TrajectorySegment],
  node_capacity? : Int = 16,
) -> TrajectoryWindowIndex {
  let t_segs : Array[@trajectory.TrajectorySegment] = []
  for i = 0; i < segments.length(); i = i + 1 {
    let s = segments[i]
    t_segs.push(
      @trajectory.TrajectorySegment::new(
        s.p0_x,
        s.p0_y,
        s.p0_t,
        s.p1_x,
        s.p1_y,
        s.p1_t,
        s.track_id,
        s.segment_id,
      ),
    )
  }
  let inner = @trajectory.TrajectoryWindowIndex::build(t_segs, node_capacity~)
  TrajectoryWindowIndex::{ inner, }
}

///|
/// Converts consecutive trajectory waypoints into directed segments, joining points with matching track_id.
pub fn TrajectoryWindowIndex::segments_from_points(
  points : Array[TrajectoryPoint],
) -> Array[TrajectorySegment] {
  let t_pts : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < points.length(); i = i + 1 {
    let p = points[i]
    t_pts.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let t_segs = @trajectory.TrajectoryWindowIndex::segments_from_points(t_pts)
  let res : Array[TrajectorySegment] = []
  for i = 0; i < t_segs.length(); i = i + 1 {
    let s = t_segs[i]
    res.push(
      TrajectorySegment::new(
        s.p0_x,
        s.p0_y,
        s.p0_t,
        s.p1_x,
        s.p1_y,
        s.p1_t,
        s.track_id,
        s.segment_id,
      ),
    )
  }
  res
}

///|
/// Queries all trajectory segment IDs whose spatial bounding box intersects search_window
/// and whose temporal lifespan overlaps [t_min, t_max].
pub fn TrajectoryWindowIndex::query_window(
  self : TrajectoryWindowIndex,
  spatial_window : Rect,
  t_min : Double,
  t_max : Double,
) -> Array[Int] {
  self.inner.query_window(spatial_window, t_min, t_max)
}

///|
/// Queries unique track IDs that intersected search_window during temporal interval [t_min, t_max].
pub fn TrajectoryWindowIndex::query_tracks_in_window(
  self : TrajectoryWindowIndex,
  spatial_window : Rect,
  t_min : Double,
  t_max : Double,
) -> Array[Int] {
  self.inner.query_tracks_in_window(spatial_window, t_min, t_max)
}

///|
/// Returns total number of indexed trajectory segments.
pub fn TrajectoryWindowIndex::num_segments(self : TrajectoryWindowIndex) -> Int {
  self.inner.num_segments()
}

///|
/// Metrics and decimated point collection resulting from trajectory simplification.
pub struct TrajectorySimplificationResult {
  points : Array[TrajectoryPoint]
  original_count : Int
  simplified_count : Int
  compression_ratio : Double
  max_error : Double
} derive(Eq, Debug, Default)

///|
/// Simplifies a 2D trajectory waypoint sequence using Douglas-Peucker algorithm with tolerance epsilon.
pub fn simplify_trajectory_2d(
  points : Array[TrajectoryPoint],
  epsilon : Double,
) -> TrajectorySimplificationResult {
  let t_pts : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < points.length(); i = i + 1 {
    let p = points[i]
    t_pts.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let res = @trajectory.simplify_trajectory_2d(t_pts, epsilon)
  let out_pts : Array[TrajectoryPoint] = []
  for i = 0; i < res.points.length(); i = i + 1 {
    let p = res.points[i]
    out_pts.push(TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  TrajectorySimplificationResult::{
    points: out_pts,
    original_count: res.original_count,
    simplified_count: res.simplified_count,
    compression_ratio: res.compression_ratio,
    max_error: res.max_error,
  }
}

///|
/// Simplifies a spatiotemporal trajectory using Synchronized Euclidean Distance (SED) tolerance epsilon.
pub fn simplify_trajectory_spatiotemporal(
  points : Array[TrajectoryPoint],
  epsilon : Double,
) -> TrajectorySimplificationResult {
  let t_pts : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < points.length(); i = i + 1 {
    let p = points[i]
    t_pts.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let res = @trajectory.simplify_trajectory_spatiotemporal(t_pts, epsilon)
  let out_pts : Array[TrajectoryPoint] = []
  for i = 0; i < res.points.length(); i = i + 1 {
    let p = res.points[i]
    out_pts.push(TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  TrajectorySimplificationResult::{
    points: out_pts,
    original_count: res.original_count,
    simplified_count: res.simplified_count,
    compression_ratio: res.compression_ratio,
    max_error: res.max_error,
  }
}

///|
/// Online streaming trajectory compressor performing real-time decimation against error tolerance.
pub struct StreamingTrajectoryCompressor {
  priv inner : @trajectory.StreamingTrajectoryCompressor
} derive(Eq, Debug)

///|
/// Instantiates an online trajectory compressor with error bound epsilon and optional SED calculation.
pub fn StreamingTrajectoryCompressor::new(
  epsilon : Double,
  use_sed? : Bool = false,
) -> StreamingTrajectoryCompressor {
  let inner = @trajectory.StreamingTrajectoryCompressor::new(epsilon, use_sed~)
  StreamingTrajectoryCompressor::{ inner, }
}

///|
/// Ingests a new trajectory waypoint and returns any newly finalized anchor waypoints.
pub fn StreamingTrajectoryCompressor::push(
  self : StreamingTrajectoryCompressor,
  pt : TrajectoryPoint,
) -> Array[TrajectoryPoint] {
  let emitted = self.inner.push(
    @trajectory.TrajectoryPoint::new(pt.x, pt.y, pt.t, pt.track_id),
  )
  let res : Array[TrajectoryPoint] = []
  for i = 0; i < emitted.length(); i = i + 1 {
    let p = emitted[i]
    res.push(TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  res
}

///|
/// Flushes the remaining trajectory stream and returns all preserved decimated waypoints.
pub fn StreamingTrajectoryCompressor::finish(
  self : StreamingTrajectoryCompressor,
) -> Array[TrajectoryPoint] {
  let final_pts = self.inner.finish()
  let res : Array[TrajectoryPoint] = []
  for i = 0; i < final_pts.length(); i = i + 1 {
    let p = final_pts[i]
    res.push(TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  res
}

///|
/// Returns total number of incoming points received so far.
pub fn StreamingTrajectoryCompressor::total_received(
  self : StreamingTrajectoryCompressor,
) -> Int {
  self.inner.total_received()
}

///|
/// Returns total number of finalized waypoints emitted so far.
pub fn StreamingTrajectoryCompressor::total_emitted(
  self : StreamingTrajectoryCompressor,
) -> Int {
  self.inner.total_emitted()
}

///|
/// Computes point decimation percentage ((1 - emitted / received) * 100).
pub fn StreamingTrajectoryCompressor::compression_ratio(
  self : StreamingTrajectoryCompressor,
) -> Double {
  self.inner.compression_ratio()
}

///|
/// Computes Dynamic Time Warping (DTW) distance between two trajectories with optional Sakoe-Chiba window constraint.
pub fn trajectory_dtw_distance(
  a : Array[TrajectoryPoint],
  b : Array[TrajectoryPoint],
  window? : Int = -1,
) -> Double {
  let t_a : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < a.length(); i = i + 1 {
    let p = a[i]
    t_a.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let t_b : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < b.length(); i = i + 1 {
    let p = b[i]
    t_b.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  @trajectory.trajectory_dtw_distance(t_a, t_b, window~)
}

///|
/// Computes length-normalized DTW distance (total_dtw / (len_a + len_b)).
pub fn trajectory_dtw_normalized(
  a : Array[TrajectoryPoint],
  b : Array[TrajectoryPoint],
  window? : Int = -1,
) -> Double {
  let t_a : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < a.length(); i = i + 1 {
    let p = a[i]
    t_a.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let t_b : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < b.length(); i = i + 1 {
    let p = b[i]
    t_b.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  @trajectory.trajectory_dtw_normalized(t_a, t_b, window~)
}

///|
/// Computes Discrete Fréchet Distance between two polygonal trajectory curves.
pub fn trajectory_frechet_distance(
  a : Array[TrajectoryPoint],
  b : Array[TrajectoryPoint],
) -> Double {
  let t_a : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < a.length(); i = i + 1 {
    let p = a[i]
    t_a.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  let t_b : Array[@trajectory.TrajectoryPoint] = []
  for i = 0; i < b.length(); i = i + 1 {
    let p = b[i]
    t_b.push(@trajectory.TrajectoryPoint::new(p.x, p.y, p.t, p.track_id))
  }
  @trajectory.trajectory_frechet_distance(t_a, t_b)
}