//! Strength indicators: ADX.

use crate::core::error::RaptorError;
use crate::core::Result;

/// Average Directional Index (ADX).
///
/// # Arguments
/// * `high` - High prices
/// * `low` - Low prices
/// * `close` - Close prices
/// * `period` - Lookback period (default: 14)
///
/// # Returns
/// Vector of ADX values (0-100 scale, NaN for warmup period)
pub fn adx(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    let n = close.len();
    if n != high.len() || n != low.len() {
        return Err(RaptorError::length_mismatch(n, high.len()));
    }
    if period == 0 {
        return Err(RaptorError::invalid_parameter("ADX period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::adx(high, low, close, period))
}

/// Directional Index result including +DI, -DI, and ADX.
#[derive(Debug, Clone)]
pub struct DirectionalIndexResult {
    /// +DI values.
    pub plus_di: Vec<f64>,
    /// -DI values.
    pub minus_di: Vec<f64>,
    /// ADX values.
    pub adx: Vec<f64>,
}

/// Full Directional Movement System (DI+, DI-, ADX).
///
/// # Arguments
/// * `high` - High prices
/// * `low` - Low prices
/// * `close` - Close prices
/// * `period` - Lookback period (default: 14)
///
/// # Returns
/// DirectionalIndexResult with +DI, -DI, and ADX
pub fn directional_movement(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    period: usize,
) -> Result<DirectionalIndexResult> {
    let n = close.len();
    if n != high.len() || n != low.len() {
        return Err(RaptorError::length_mismatch(n, high.len()));
    }
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Period must be > 0"));
    }
    let (_pdm_s, _mdm_s, plus_di, minus_di, _dx, adx) =
        ferro_ta_core::momentum::adx_all(high, low, close, period);
    Ok(DirectionalIndexResult { plus_di, minus_di, adx })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_adx() {
        // Generate some trending data
        let n = 50;
        let high: Vec<f64> = (0..n).map(|i| 100.0 + i as f64 + 2.0).collect();
        let low: Vec<f64> = (0..n).map(|i| 100.0 + i as f64 - 2.0).collect();
        let close: Vec<f64> = (0..n).map(|i| 100.0 + i as f64).collect();

        let result = adx(&high, &low, &close, 14).unwrap();

        // ADX should be valid from index 27 (2 * period - 1)
        assert!(result[26].is_nan());
        assert!(!result[27].is_nan());

        // ADX should be positive and <= 100
        assert!(result[27] >= 0.0 && result[27] <= 100.0);
    }

    #[test]
    fn test_directional_movement() {
        let n = 50;
        let high: Vec<f64> = (0..n).map(|i| 100.0 + i as f64 + 2.0).collect();
        let low: Vec<f64> = (0..n).map(|i| 100.0 + i as f64 - 2.0).collect();
        let close: Vec<f64> = (0..n).map(|i| 100.0 + i as f64).collect();

        let result = directional_movement(&high, &low, &close, 14).unwrap();

        // Check DI values are valid
        assert!(!result.plus_di[20].is_nan());
        assert!(!result.minus_di[20].is_nan());

        // In an uptrend, +DI should be greater than -DI
        assert!(result.plus_di[40] > result.minus_di[40]);
    }
}