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

pub struct AroonResult {
    pub up: Vec<f64>,
    pub down: Vec<f64>,
}

pub struct AdxAllResult {
    pub adx: Vec<f64>,
    pub plus_di: Vec<f64>,
    pub minus_di: Vec<f64>,
}

fn ema_nan_safe(data: &[f64], period: usize) -> Vec<f64> {
    let n = data.len();
    let mut result = vec![f64::NAN; n];
    if period == 0 || n < period {
        return result;
    }
    let k = 2.0 / (period as f64 + 1.0);
    let mut seed_sum = 0.0;
    let mut seed_count = 0usize;
    let mut first_valid = None;
    for i in 0..n {
        if !data[i].is_nan() {
            seed_sum += data[i];
            seed_count += 1;
            if seed_count == period {
                first_valid = Some(i);
                result[i] = seed_sum / period as f64;
                break;
            }
        }
    }
    if let Some(start) = first_valid {
        for i in (start + 1)..n {
            if !data[i].is_nan() {
                result[i] = data[i] * k + result[i - 1] * (1.0 - k);
            }
        }
    }
    result
}

pub fn cci(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("CCI period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::cci(high, low, close, period))
}

pub fn willr(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Williams %R period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::willr(high, low, close, period))
}

pub fn sar(high: &[f64], low: &[f64], acceleration: f64, maximum: f64) -> Result<Vec<f64>> {
    if acceleration <= 0.0 || maximum <= 0.0 {
        return Err(RaptorError::invalid_parameter(
            "SAR acceleration and maximum must be > 0",
        ));
    }
    Ok(ferro_ta_core::overlap::sar(high, low, acceleration, maximum))
}

pub fn plus_di(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("+DI period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::plus_di(high, low, close, period))
}

pub fn minus_di(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("-DI period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::minus_di(high, low, close, period))
}

pub fn adx_all(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<AdxAllResult> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("ADX 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(AdxAllResult { adx, plus_di, minus_di })
}

pub fn adxr(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("ADXR period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::adxr(high, low, close, period))
}

pub fn roc(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("ROC period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::roc(close, period))
}

pub fn mfi(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    volume: &[f64],
    period: usize,
) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("MFI period must be > 0"));
    }
    Ok(ferro_ta_core::volume::mfi(high, low, close, volume, period))
}

pub fn wma(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("WMA period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::wma(close, period))
}

pub fn dema(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("DEMA period must be > 0"));
    }
    let n = close.len();
    let mut result = vec![f64::NAN; n];
    let ema1 = ferro_ta_core::overlap::ema(close, period);
    let ema2 = ema_nan_safe(&ema1, period);
    for i in 0..n {
        if !ema1[i].is_nan() && !ema2[i].is_nan() {
            result[i] = 2.0 * ema1[i] - ema2[i];
        }
    }
    Ok(result)
}

pub fn tema(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("TEMA period must be > 0"));
    }
    let n = close.len();
    let mut result = vec![f64::NAN; n];
    let ema1 = ferro_ta_core::overlap::ema(close, period);
    let ema2 = ema_nan_safe(&ema1, period);
    let ema3 = ema_nan_safe(&ema2, period);
    for i in 0..n {
        if !ema1[i].is_nan() && !ema2[i].is_nan() && !ema3[i].is_nan() {
            result[i] = 3.0 * ema1[i] - 3.0 * ema2[i] + ema3[i];
        }
    }
    Ok(result)
}

pub fn kama(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("KAMA period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::kama(close, period))
}

pub fn stochrsi(
    close: &[f64],
    timeperiod: usize,
    fastk_period: usize,
    fastd_period: usize,
) -> Result<(Vec<f64>, Vec<f64>)> {
    if timeperiod == 0 {
        return Err(RaptorError::invalid_parameter(
            "StochRSI timeperiod must be > 0",
        ));
    }
    Ok(ferro_ta_core::momentum::stochrsi(
        close,
        timeperiod,
        fastk_period,
        fastd_period,
    ))
}

pub fn aroon(high: &[f64], low: &[f64], period: usize) -> Result<AroonResult> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Aroon period must be > 0"));
    }
    let (down, up) = ferro_ta_core::momentum::aroon(high, low, period);
    Ok(AroonResult { up, down })
}

pub fn trix(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("TRIX period must be > 0"));
    }
    let n = close.len();
    let mut result = vec![f64::NAN; n];
    let ema1 = ferro_ta_core::overlap::ema(close, period);
    let ema2 = ema_nan_safe(&ema1, period);
    let ema3 = ema_nan_safe(&ema2, period);
    for i in 1..n {
        let prev = ema3[i - 1];
        if !ema3[i].is_nan() && !prev.is_nan() && prev != 0.0 {
            result[i] = (ema3[i] - prev) / prev * 100.0;
        }
    }
    Ok(result)
}

pub fn natr(high: &[f64], low: &[f64], close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("NATR period must be > 0"));
    }
    Ok(ferro_ta_core::volatility::natr(high, low, close, period))
}

pub fn trange(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::volatility::trange(high, low, close))
}

pub fn stddev(real: &[f64], period: usize, nbdev: f64) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("StdDev period must be > 0"));
    }
    Ok(ferro_ta_core::statistic::stddev(real, period, nbdev))
}

pub fn var(real: &[f64], period: usize, nbdev: f64) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("VAR period must be > 0"));
    }
    Ok(ferro_ta_core::statistic::var(real, period, nbdev))
}

pub fn linearreg(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "LinearReg period must be > 0",
        ));
    }
    Ok(ferro_ta_core::statistic::linearreg(close, period))
}

pub fn linearreg_slope(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "LinearReg Slope period must be > 0",
        ));
    }
    Ok(ferro_ta_core::statistic::linearreg_slope(close, period))
}

pub fn beta(real0: &[f64], real1: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Beta period must be > 0"));
    }
    Ok(ferro_ta_core::statistic::beta(real0, real1, period))
}

pub fn correl(real0: &[f64], real1: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Correl period must be > 0"));
    }
    Ok(ferro_ta_core::statistic::correl(real0, real1, period))
}

pub fn ad(high: &[f64], low: &[f64], close: &[f64], volume: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::volume::ad(high, low, close, volume))
}

pub fn adosc(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    volume: &[f64],
    fastperiod: usize,
    slowperiod: usize,
) -> Result<Vec<f64>> {
    if fastperiod == 0 || slowperiod == 0 {
        return Err(RaptorError::invalid_parameter(
            "ADOSC fast/slow period must be > 0",
        ));
    }
    Ok(ferro_ta_core::volume::adosc(
        high, low, close, volume, fastperiod, slowperiod,
    ))
}

pub fn obv(close: &[f64], volume: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::volume::obv(close, volume))
}

pub fn mom(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Momentum period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::mom(close, period))
}

pub struct PpoResult {
    pub ppo_line: Vec<f64>,
    pub signal_line: Vec<f64>,
    pub histogram: Vec<f64>,
}

pub fn ppo(
    close: &[f64],
    fastperiod: usize,
    slowperiod: usize,
    signalperiod: usize,
) -> Result<PpoResult> {
    if fastperiod == 0 || slowperiod == 0 || signalperiod == 0 {
        return Err(RaptorError::invalid_parameter(
            "PPO fast/slow/signal period must be > 0",
        ));
    }
    let n = close.len();
    let fast_ema = ferro_ta_core::overlap::ema(close, fastperiod);
    let slow_ema = ferro_ta_core::overlap::ema(close, slowperiod);

    let mut ppo_line = vec![f64::NAN; n];
    for i in 0..n {
        if !fast_ema[i].is_nan() && !slow_ema[i].is_nan() && slow_ema[i] != 0.0 {
            ppo_line[i] = (fast_ema[i] - slow_ema[i]) / slow_ema[i] * 100.0;
        }
    }

    let signal_line = ema_nan_safe(&ppo_line, signalperiod);
    let mut histogram = vec![f64::NAN; n];
    for i in 0..n {
        if !ppo_line[i].is_nan() && !signal_line[i].is_nan() {
            histogram[i] = ppo_line[i] - signal_line[i];
        }
    }

    Ok(PpoResult { ppo_line, signal_line, histogram })
}

pub fn cmo(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("CMO period must be > 0"));
    }
    Ok(ferro_ta_core::momentum::cmo(close, period))
}

pub fn aroonosc(high: &[f64], low: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "Aroon Osc period must be > 0",
        ));
    }
    Ok(ferro_ta_core::momentum::aroonosc(high, low, period))
}

pub fn bop(
    open: &[f64],
    high: &[f64],
    low: &[f64],
    close: &[f64],
) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::momentum::bop(open, high, low, close))
}

pub fn ultosc(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    period1: usize,
    period2: usize,
    period3: usize,
) -> Result<Vec<f64>> {
    if period1 == 0 || period2 == 0 || period3 == 0 {
        return Err(RaptorError::invalid_parameter(
            "Ultimate Osc periods must be > 0",
        ));
    }
    Ok(ferro_ta_core::momentum::ultosc(high, low, close, period1, period2, period3))
}

pub fn typprice(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::price_transform::typprice(high, low, close))
}

pub fn medprice(high: &[f64], low: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::price_transform::medprice(high, low))
}

pub fn avgprice(
    open: &[f64],
    high: &[f64],
    low: &[f64],
    close: &[f64],
) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::price_transform::avgprice(open, high, low, close))
}

pub fn wclprice(high: &[f64], low: &[f64], close: &[f64]) -> Result<Vec<f64>> {
    Ok(ferro_ta_core::price_transform::wclprice(high, low, close))
}

pub fn midpoint(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Midpoint period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::midpoint(close, period))
}

pub fn midprice(high: &[f64], low: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Midprice period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::midprice(high, low, period))
}

pub fn t3(close: &[f64], period: usize, vfactor: f64) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("T3 period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::t3(close, period, vfactor))
}

pub fn trima(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("TRIMA period must be > 0"));
    }
    Ok(ferro_ta_core::overlap::trima(close, period))
}

pub fn apo(close: &[f64], fastperiod: usize, slowperiod: usize) -> Result<Vec<f64>> {
    if fastperiod == 0 || slowperiod == 0 {
        return Err(RaptorError::invalid_parameter(
            "APO fast/slow period must be > 0",
        ));
    }
    Ok(ferro_ta_core::momentum::apo(close, fastperiod, slowperiod))
}

pub fn tsf(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("TSF period must be > 0"));
    }
    Ok(ferro_ta_core::statistic::tsf(close, period))
}

pub fn linearreg_angle(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "LinearReg Angle period must be > 0",
        ));
    }
    Ok(ferro_ta_core::statistic::linearreg_angle(close, period))
}

pub fn linearreg_intercept(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "LinearReg Intercept period must be > 0",
        ));
    }
    Ok(ferro_ta_core::statistic::linearreg_intercept(close, period))
}

// ============================================================================
// Extended indicators (from ferro_ta_core::extended)
// ============================================================================

/// Volume-Weighted Moving Average.
///
/// # Returns
/// Vector of VWMA values (NaN for warmup period).
pub fn vwma(close: &[f64], volume: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("VWMA period must be > 0"));
    }
    Ok(ferro_ta_core::extended::vwma(close, volume, period))
}

/// Donchian Channels result.
pub struct DonchianResult {
    pub upper: Vec<f64>,
    pub middle: Vec<f64>,
    pub lower: Vec<f64>,
}

/// Donchian Channels — rolling highest high / lowest low.
///
/// # Returns
/// `(upper, middle, lower)` arrays.
pub fn donchian(high: &[f64], low: &[f64], period: usize) -> Result<DonchianResult> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Donchian period must be > 0"));
    }
    let (upper, middle, lower) = ferro_ta_core::extended::donchian(high, low, period);
    Ok(DonchianResult { upper, middle, lower })
}

/// Choppiness Index — measures choppy vs trending market (0 = trend, 100 = chop).
///
/// # Returns
/// Vector of CI values (NaN for warmup period).
pub fn choppiness_index(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    period: usize,
) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "Choppiness period must be > 0",
        ));
    }
    Ok(ferro_ta_core::extended::choppiness_index(high, low, close, period))
}

/// Hull Moving Average.
///
/// `HMA(n) = WMA(2 * WMA(n/2) - WMA(n), sqrt(n))`.
pub fn hull_ma(close: &[f64], period: usize) -> Result<Vec<f64>> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter("Hull MA period must be > 0"));
    }
    Ok(ferro_ta_core::extended::hull_ma(close, period))
}

/// Chandelier Exit result — ATR-based trailing stops.
pub struct ChandelierResult {
    pub long_exit: Vec<f64>,
    pub short_exit: Vec<f64>,
}

/// Chandelier Exit — ATR-based trailing stop levels.
///
/// # Returns
/// `(long_exit, short_exit)` arrays.
pub fn chandelier_exit(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    period: usize,
    multiplier: f64,
) -> Result<ChandelierResult> {
    if period == 0 {
        return Err(RaptorError::invalid_parameter(
            "Chandelier period must be > 0",
        ));
    }
    if multiplier <= 0.0 {
        return Err(RaptorError::invalid_parameter(
            "Chandelier multiplier must be > 0",
        ));
    }
    let (long_exit, short_exit) =
        ferro_ta_core::extended::chandelier_exit(high, low, close, period, multiplier);
    Ok(ChandelierResult { long_exit, short_exit })
}

/// Ichimoku Cloud (Ichimoku Kinko Hyo) result.
pub struct IchimokuResult {
    pub tenkan: Vec<f64>,
    pub kijun: Vec<f64>,
    pub senkou_a: Vec<f64>,
    pub senkou_b: Vec<f64>,
    pub chikou: Vec<f64>,
}

/// Ichimoku Cloud — `(tenkan, kijun, senkou_a, senkou_b, chikou)`.
pub fn ichimoku(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    tenkan_period: usize,
    kijun_period: usize,
    senkou_b_period: usize,
    displacement: usize,
) -> Result<IchimokuResult> {
    if tenkan_period == 0 || kijun_period == 0 || senkou_b_period == 0 {
        return Err(RaptorError::invalid_parameter(
            "Ichimoku periods must be > 0",
        ));
    }
    let (tenkan, kijun, senkou_a, senkou_b, chikou) = ferro_ta_core::extended::ichimoku(
        high,
        low,
        close,
        tenkan_period,
        kijun_period,
        senkou_b_period,
        displacement,
    );
    Ok(IchimokuResult { tenkan, kijun, senkou_a, senkou_b, chikou })
}

/// Pivot Points result — supports classic / fibonacci / camarilla.
pub struct PivotPointsResult {
    pub pivot: Vec<f64>,
    pub r1: Vec<f64>,
    pub s1: Vec<f64>,
    pub r2: Vec<f64>,
    pub s2: Vec<f64>,
}

/// Pivot Points — classic, fibonacci, or camarilla methodology.
///
/// `method` must be one of: `"classic"`, `"fibonacci"`, `"camarilla"`.
/// Index 0 of each array is always NaN (no previous bar).
pub fn pivot_points(
    high: &[f64],
    low: &[f64],
    close: &[f64],
    method: &str,
) -> Result<PivotPointsResult> {
    let m = method.to_lowercase();
    if !matches!(m.as_str(), "classic" | "fibonacci" | "camarilla") {
        return Err(RaptorError::invalid_parameter(
            "Pivot method must be 'classic', 'fibonacci', or 'camarilla'",
        ));
    }
    let (pivot, r1, s1, r2, s2) =
        ferro_ta_core::extended::pivot_points(high, low, close, &m);
    Ok(PivotPointsResult { pivot, r1, s1, r2, s2 })
}

// ============================================================================
// Cycle (Hilbert Transform) indicators (from ferro_ta_core::cycle)
// ============================================================================

/// Hilbert Transform — Instantaneous Trendline.
pub fn ht_trendline(close: &[f64]) -> Result<Vec<f64>> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT Trendline requires at least 32 bars",
        ));
    }
    Ok(ferro_ta_core::cycle::ht_trendline(close))
}

/// Hilbert Transform — Dominant Cycle Period.
pub fn ht_dcperiod(close: &[f64]) -> Result<Vec<f64>> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT DCPeriod requires at least 32 bars",
        ));
    }
    Ok(ferro_ta_core::cycle::ht_dcperiod(close))
}

/// Hilbert Transform — Dominant Cycle Phase.
pub fn ht_dcphase(close: &[f64]) -> Result<Vec<f64>> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT DCPhase requires at least 32 bars",
        ));
    }
    Ok(ferro_ta_core::cycle::ht_dcphase(close))
}

/// Hilbert Transform — Phasor Components result.
pub struct HtPhasorResult {
    pub in_phase: Vec<f64>,
    pub quadrature: Vec<f64>,
}

/// Hilbert Transform — Phasor Components `(in_phase, quadrature)`.
pub fn ht_phasor(close: &[f64]) -> Result<HtPhasorResult> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT Phasor requires at least 32 bars",
        ));
    }
    let (in_phase, quadrature) = ferro_ta_core::cycle::ht_phasor(close);
    Ok(HtPhasorResult { in_phase, quadrature })
}

/// Hilbert Transform — Sine Wave result.
pub struct HtSineResult {
    pub sine: Vec<f64>,
    pub lead_sine: Vec<f64>,
}

/// Hilbert Transform — Sine Wave `(sine, lead_sine)`.
pub fn ht_sine(close: &[f64]) -> Result<HtSineResult> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT Sine requires at least 32 bars",
        ));
    }
    let (sine, lead_sine) = ferro_ta_core::cycle::ht_sine(close);
    Ok(HtSineResult { sine, lead_sine })
}

/// Hilbert Transform — Trend vs Cycle Mode.
///
/// Returns `Vec<i32>`: `1` = trend mode, `0` = cycle mode.
pub fn ht_trendmode(close: &[f64]) -> Result<Vec<i32>> {
    if close.len() < 32 {
        return Err(RaptorError::invalid_parameter(
            "HT TrendMode requires at least 32 bars",
        ));
    }
    Ok(ferro_ta_core::cycle::ht_trendmode(close))
}

// ============================================================================
// Market regime detection (from ferro_ta_core::regime)
// ============================================================================

/// Trend/range regime labels based on ADX threshold.
///
/// Returns `Vec<i8>`: `1` = trend, `0` = range, `-1` = warmup/NaN.
pub fn regime_adx(adx: &[f64], threshold: f64) -> Result<Vec<i8>> {
    Ok(ferro_ta_core::regime::regime_adx(adx, threshold))
}

/// Trend/range regime using ADX + ATR-ratio rule.
///
/// Returns `Vec<i8>`: `1` = trend, `0` = range, `-1` = NaN.
pub fn regime_combined(
    adx: &[f64],
    atr: &[f64],
    close: &[f64],
    adx_threshold: f64,
    atr_pct_threshold: f64,
) -> Result<Vec<i8>> {
    Ok(ferro_ta_core::regime::regime_combined(
        adx,
        atr,
        close,
        adx_threshold,
        atr_pct_threshold,
    ))
}

/// Detect structural breaks via CUSUM test.
///
/// Returns `Vec<i8>`: `1` at break bars, `0` elsewhere.
pub fn detect_breaks_cusum(
    series: &[f64],
    window: usize,
    threshold: f64,
    slack: f64,
) -> Result<Vec<i8>> {
    if window < 2 {
        return Err(RaptorError::invalid_parameter(
            "CUSUM window must be >= 2",
        ));
    }
    Ok(ferro_ta_core::regime::detect_breaks_cusum(
        series, window, threshold, slack,
    ))
}

/// Detect volatility regime breaks using rolling variance ratio.
///
/// `long_window` must be > `short_window`. Returns `Vec<i8>`:
/// `1` at break bars, `0` elsewhere.
pub fn rolling_variance_break(
    series: &[f64],
    short_window: usize,
    long_window: usize,
    threshold: f64,
) -> Result<Vec<i8>> {
    if short_window < 2 {
        return Err(RaptorError::invalid_parameter(
            "Variance break short_window must be >= 2",
        ));
    }
    if long_window <= short_window {
        return Err(RaptorError::invalid_parameter(
            "Variance break long_window must be > short_window",
        ));
    }
    Ok(ferro_ta_core::regime::rolling_variance_break(
        series,
        short_window,
        long_window,
        threshold,
    ))
}

// ============================================================================
// Portfolio / cross-series tools (from ferro_ta_core::portfolio)
// ============================================================================

/// Rolling beta — `cov(asset, benchmark) / var(benchmark)` over a sliding window.
pub fn rolling_beta(asset: &[f64], benchmark: &[f64], window: usize) -> Result<Vec<f64>> {
    if window < 2 {
        return Err(RaptorError::invalid_parameter(
            "Rolling beta window must be >= 2",
        ));
    }
    Ok(ferro_ta_core::portfolio::rolling_beta(asset, benchmark, window))
}

/// Drawdown series result.
pub struct DrawdownResult {
    /// Per-bar drawdown as a non-positive fraction of peak equity.
    pub series: Vec<f64>,
    /// Maximum drawdown over the full input range (non-positive).
    pub max_drawdown: f64,
}

/// Drawdown series — `(per_bar_drawdown, max_drawdown)` from an equity curve.
pub fn drawdown_series(equity: &[f64]) -> Result<DrawdownResult> {
    let (series, max_drawdown) = ferro_ta_core::portfolio::drawdown_series(equity);
    Ok(DrawdownResult { series, max_drawdown })
}

/// Rolling Z-Score over a window.
pub fn zscore_series(x: &[f64], window: usize) -> Result<Vec<f64>> {
    if window < 2 {
        return Err(RaptorError::invalid_parameter(
            "Z-score window must be >= 2",
        ));
    }
    Ok(ferro_ta_core::portfolio::zscore_series(x, window))
}

/// Relative strength — `asset - beta * benchmark` (excess return style).
pub fn relative_strength(asset_returns: &[f64], benchmark_returns: &[f64]) -> Result<Vec<f64>> {
    if asset_returns.len() != benchmark_returns.len() {
        return Err(RaptorError::invalid_parameter(
            "Relative strength inputs must have the same length",
        ));
    }
    Ok(ferro_ta_core::portfolio::relative_strength(asset_returns, benchmark_returns))
}

/// Spread between two series: `a - hedge * b`.
pub fn spread(a: &[f64], b: &[f64], hedge: f64) -> Result<Vec<f64>> {
    if a.len() != b.len() {
        return Err(RaptorError::invalid_parameter(
            "Spread inputs must have the same length",
        ));
    }
    Ok(ferro_ta_core::portfolio::spread(a, b, hedge))
}

/// Ratio between two series element-wise: `a / b`.
pub fn ratio(a: &[f64], b: &[f64]) -> Result<Vec<f64>> {
    if a.len() != b.len() {
        return Err(RaptorError::invalid_parameter(
            "Ratio inputs must have the same length",
        ));
    }
    Ok(ferro_ta_core::portfolio::ratio(a, b))
}