// Moving averages: SMA, EMA, WMA, HMA, DEMA, TEMA and the shared
// `moving_average` dispatcher used by APO/PPO.

///|
/// Number of leading NaN values produced by `sma` for the given period.
pub fn sma_lookback(period : Int32) -> Int32 {
  period - 1
}

///|
/// Simple moving average.
///
/// Output has the same length as `data`; positions before the first full
/// window are NaN, and a NaN anywhere inside a window makes that position
/// NaN.
pub fn sma(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let n = data.length()
  let pd = period.to_double()
  let out : Array[Double] = []
  for i in 0.. Array[Double] {
  let n = data.length()
  let out : Array[Double] = []
  let fv = if first_valid < 0 { 0 } else { first_valid }
  let warm = fv + period - 1
  if warm >= n {
    for _ in 0.. Int32 {
  period - 1
}

///|
/// Exponential moving average with smoothing factor `2 / (period + 1)`,
/// seeded by the SMA of the first `period` values (TA-Lib convention).
pub fn ema(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  ema_from(data, period, 0)
}

///|
/// Number of leading NaN values produced by `wma` for the given period.
pub fn wma_lookback(period : Int32) -> Int32 {
  period - 1
}

///|
/// Linearly weighted moving average: the newest bar of the window gets
/// weight `period`, the oldest gets weight 1.
pub fn wma(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let n = data.length()
  let denom = (period * (period + 1) / 2).to_double()
  let out : Array[Double] = []
  for i in 0.. Int32 {
  period + @base.isqrt(period) - 2
}

///|
/// Hull moving average: `WMA(2 * WMA(n/2) - WMA(n), sqrt(n))`, smoothing the
/// lag of the weighted average against the price series.
pub fn hma(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let half = if period / 2 < 1 { 1 } else { period / 2 }
  let sq = @base.isqrt(period)
  let w1 = wma(data, half)
  let w2 = wma(data, period)
  let n = data.length()
  let diff : Array[Double] = []
  for i in 0.. Int32 {
  2 * (period - 1)
}

///|
/// Double exponential moving average: `2 * EMA - EMA(EMA)`.
pub fn dema(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let e1 = ema_from(data, period, 0)
  let e2 = ema_from(e1, period, period - 1)
  let n = data.length()
  let out : Array[Double] = []
  for i in 0.. Int32 {
  3 * (period - 1)
}

///|
/// Triple exponential moving average:
/// `3 * EMA - 3 * EMA(EMA) + EMA(EMA(EMA))`.
pub fn tema(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let e1 = ema_from(data, period, 0)
  let e2 = ema_from(e1, period, period - 1)
  let e3 = ema_from(e2, period, 2 * (period - 1))
  let n = data.length()
  let out : Array[Double] = []
  for i in 0.. Array[Double] raise @base.TaError {
  match ma_type {
    @base.Sma => sma(data, period)
    @base.Ema => ema(data, period)
  }
}

///|
/// Number of leading NaN values produced by `midpoint` for the given period.
pub fn midpoint_lookback(period : Int32) -> Int32 {
  period - 1
}

///|
/// Midpoint over a rolling window: `(max + min) / 2` of the last `period`
/// values. A NaN inside the window yields NaN.
pub fn midpoint(
  data : Array[Double],
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_series(data, period)
  let n = data.length()
  let out : Array[Double] = []
  for i in 0.. best_max {
          best_max = v
        }
        if v < best_min {
          best_min = v
        }
      }
    }
    if valid {
      out.push((best_max + best_min) / 2.0)
    } else {
      out.push(@base.nan())
    }
  }
  out
}