///|
/// Typed, deterministic failures. No partial results are returned.
pub(all) enum RqaError {
  Invalid(String)
  Limit(String)
  Numeric(String)
} derive(Debug, Eq)

///|
/// Distance between finite state vectors.
pub(all) enum Metric {
  Chebyshev
  Manhattan
  Euclidean
} derive(Debug, Eq)

///|
/// Hard implementation ceilings; caller budgets can only tighten them.
fn finite(x : Double) -> Bool {
  !x.is_nan() && !x.is_inf()
}

///|
fn valid_number(x : Double) -> Bool {
  finite(x) && x.abs() <= 1.0e100
}

///|
/// Owns a row-major snapshot; mutation of inputs cannot change analysis.
pub struct Trajectory {
  samples : Int32
  dimensions : Int32
  values : Array[Double]
} derive(Debug, Eq)

///|
pub fn trajectory(rows : Array[Array[Double]]) -> Result[Trajectory, RqaError] {
  if rows.length() == 0 || rows.length() > 4096 {
    return Err(Limit("trajectory samples must be 1..4096"))
  }
  let d = rows[0].length()
  if d == 0 || d > 64 {
    return Err(Limit("dimensions must be 1..64"))
  }
  let values : Array[Double] = []
  for row in rows {
    if row.length() != d {
      return Err(Invalid("ragged trajectory"))
    }
    for x in row {
      if !valid_number(x) {
        return Err(Numeric("coordinate must be finite and abs <= 1e100"))
      }
      values.push(x)
    }
  }
  Ok({ samples: rows.length(), dimensions: d, values, })
}

///|
pub fn Trajectory::len(self : Trajectory) -> Int32 {
  self.samples
}

///|
pub fn Trajectory::dimension(self : Trajectory) -> Int32 {
  self.dimensions
}

///|
pub fn Trajectory::rows(self : Trajectory) -> Array[Array[Double]] {
  Array::makei(self.samples, fn(i) {
    Array::makei(self.dimensions, fn(k) { self.values[i * self.dimensions + k] })
  })
}

///|
pub fn Trajectory::point(
  self : Trajectory,
  i : Int32,
) -> Result[Array[Double], RqaError] {
  if i < 0 || i >= self.samples {
    return Err(Invalid("sample index"))
  }
  Ok(
    Array::makei(self.dimensions, fn(k) { self.values[i * self.dimensions + k] }),
  )
}

///|
pub extend RqaError with @debug.Debug::{to_repr}

///|
pub extend RqaError with Eq::{equal, not_equal}

///|
pub extend Metric with @debug.Debug::{to_repr}

///|
pub extend Metric with Eq::{equal, not_equal}

///|
pub extend Trajectory with @debug.Debug::{to_repr}

///|
pub extend Trajectory with Eq::{equal, not_equal}