// Average Directional Index with +DI / -DI (J. Welles Wilder).

///|
/// ADX decomposition: the ADX trend-strength line, the +DI and -DI
/// directional indicators, and the raw DX line that ADX smooths. All four
/// arrays have the input length.
pub struct AdxResult {
  adx : Array[Double]
  plus_di : Array[Double]
  minus_di : Array[Double]
  dx : Array[Double]
}

///|
fn dm_plus(candles : @base.Candles, i : Int32) -> Double {
  let up = candles.high[i] - candles.high[i - 1]
  let down = candles.low[i - 1] - candles.low[i]
  if @base.is_nan(up) || @base.is_nan(down) {
    return @base.nan()
  }
  if up > down && up > 0.0 {
    up
  } else {
    0.0
  }
}

///|
fn dm_minus(candles : @base.Candles, i : Int32) -> Double {
  let up = candles.high[i] - candles.high[i - 1]
  let down = candles.low[i - 1] - candles.low[i]
  if @base.is_nan(up) || @base.is_nan(down) {
    return @base.nan()
  }
  if down > up && down > 0.0 {
    down
  } else {
    0.0
  }
}

///|
/// Number of leading NaN values produced by `adx` for the given period:
/// `2 * period - 1` (the DI lines themselves are valid from `period`).
pub fn adx_lookback(period : Int32) -> Int32 {
  2 * period - 1
}

///|
/// Wilder-smoothed +DI / -DI lines, valid from index `period`. Shared core
/// of `adx` and `dx`.
fn wilder_di_lines(
  candles : @base.Candles,
  period : Int32,
) -> (Array[Double], Array[Double]) {
  let n = candles.close.length()
  let pd = period.to_double()
  let plus_di : Array[Double] = []
  let minus_di : Array[Double] = []
  for _ in 0.. period
  if seeded {
    for i in 1..<(period + 1) {
      let t = @base.true_range(candles, i)
      let p = dm_plus(candles, i)
      let m = dm_minus(candles, i)
      if @base.is_nan(t) || @base.is_nan(p) || @base.is_nan(m) {
        seeded = false
        break
      }
      s_tr += t
      s_p += p
      s_m += m
    }
  }
  if seeded {
    plus_di[period] = if s_tr == 0.0 { 0.0 } else { 100.0 * s_p / s_tr }
    minus_di[period] = if s_tr == 0.0 { 0.0 } else { 100.0 * s_m / s_tr }
    for i in (period + 1).. Array[Double] {
  let dx : Array[Double] = []
  for i in 0.. AdxResult raise @base.TaError {
  @base.validate_candles(candles)
  if period < 2 {
    raise @base.InvalidPeriod("ADX period must be at least 2, got \{period}")
  }
  let n = candles.close.length()
  if period > n {
    raise @base.NotEnoughData("period \{period} exceeds input length \{n}")
  }
  let pd = period.to_double()
  let (plus_di, minus_di) = wilder_di_lines(candles, period)
  let dx = dx_from_di(n, plus_di, minus_di)
  let adx_out : Array[Double] = []
  for _ in 0.. first {
    let mut sum = 0.0
    let mut ok = true
    for i in period..<(first + 1) {
      if @base.is_nan(dx[i]) {
        ok = false
        break
      }
      sum += dx[i]
    }
    if ok {
      let mut a = sum / pd
      adx_out[first] = a
      for i in (first + 1).. Int32 {
  period
}

///|
/// Directional Movement Index (DX): the raw `100 * |+DI - -DI| / (+DI + -DI)`
/// ratio that `adx` smooths, valid from index `period`.
pub fn dx(
  candles : @base.Candles,
  period : Int32,
) -> Array[Double] raise @base.TaError {
  @base.validate_candles(candles)
  if period < 2 {
    raise @base.InvalidPeriod("ADX period must be at least 2, got \{period}")
  }
  let n = candles.close.length()
  if period > n {
    raise @base.NotEnoughData("period \{period} exceeds input length \{n}")
  }
  let (plus_di, minus_di) = wilder_di_lines(candles, period)
  dx_from_di(n, plus_di, minus_di)
}