///|
/// Robust trend decomposition for longitudinal HRV series.

///|
/// Trend decomposition components.
pub(all) struct TrendDecomposition {
  original : Array[Double]
  baseline : Array[Double]
  residual : Array[Double]
  slope : Double
  intercept : Double
  turning_points : Int
  explained_variance : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Estimate a rolling baseline with a centered median window.
pub fn trend_baseline(
  values : Array[Double],
  window_size : Int,
) -> Array[Double] {
  rolling_median(values, if window_size < 1 { 1 } else { window_size })
}

///|
/// Count direction changes in a residual sequence.
pub fn count_turning_points(values : Array[Double]) -> Int {
  if values.length() <= 2 {
    return 0
  }
  let mut count = 0
  let mut previous = values[1] - values[0]
  for i in 2.. Double {
  if values.length() == 0 {
    return 0.0
  }
  let mean = mean_value(values)
  let mut total = 0.0
  let mut residual = 0.0
  for i in 0.. TrendDecomposition {
  let baseline = trend_baseline(values, window_size)
  let residual = []
  let n = if values.length() < baseline.length() {
    values.length()
  } else {
    baseline.length()
  }
  for i in 0.. Array[Double] {
  if values.length() == 0 {
    return []
  }
  let weight = alpha.clamp(min=0.0, max=1.0)
  let result = [values[0]]
  let mut current = values[0]
  for i in 1.. Array[Int] {
  let result = []
  let scale = median_absolute_deviation(residual) * 1.4826
  let limit = if threshold < 0.0 { 0.0 } else { threshold }
  let bound = if scale < 1.0 { limit } else { scale * limit }
  for i in 0.. bound {
      result.push(i)
    }
  }
  result
}

///|
/// Return the signed change between the endpoints of a series.
pub fn endpoint_change(values : Array[Double]) -> Double {
  if values.length() <= 1 {
    0.0
  } else {
    values[values.length() - 1] - values[0]
  }
}

///|
/// Calculate a trailing slope for every complete window.
pub fn rolling_trend_slopes(
  values : Array[Double],
  window_size : Int,
) -> Array[Double] {
  if window_size <= 1 || values.length() < window_size {
    return []
  }
  let result = []
  for start in 0..<(values.length() - window_size + 1) {
    let window = []
    for i in start..<(start + window_size) {
      window.push(values[i])
    }
    result.push(fit_linear_trend(window).slope)
  }
  result
}

///|
/// Return a trend feature vector.
pub fn trend_feature_vector(
  decomposition : TrendDecomposition,
) -> Array[Double] {
  [
    decomposition.slope,
    decomposition.intercept,
    decomposition.turning_points.to_double(),
    decomposition.explained_variance,
    endpoint_change(decomposition.original),
    standard_deviation(decomposition.residual),
    mean_value(decomposition.baseline),
  ]
}

///|
/// Return whether the decomposition has aligned finite arrays.
pub fn trend_decomposition_is_usable(
  decomposition : TrendDecomposition,
) -> Bool {
  decomposition.original.length() == decomposition.baseline.length() &&
  decomposition.baseline.length() == decomposition.residual.length()
}