"""
Extended Indicators — Popular indicators not in the TA-Lib standard set.

All indicator logic is implemented in Rust (PyO3) for maximum performance.
This module provides the public Python API with:
- Input validation
- ``_to_f64`` conversion
- pandas/polars-compatible return values (numpy arrays)

Functions
---------
VWAP               — Volume Weighted Average Price (cumulative or rolling)
SUPERTREND         — ATR-based trend-following signal
ICHIMOKU           — Ichimoku Cloud
DONCHIAN           — Donchian Channels
PIVOT_POINTS       — Classic / Fibonacci / Camarilla pivot levels
KELTNER_CHANNELS   — EMA ± ATR bands
HULL_MA            — Hull Moving Average (WMA-based)
CHANDELIER_EXIT    — ATR-based stop-loss / exit levels
VWMA               — Volume Weighted Moving Average
CHOPPINESS_INDEX   — Market choppiness / trending strength index

Rust backend
------------
All computations delegate to Rust functions in the ``_ferro_ta`` extension::

    from ferro_ta._ferro_ta import supertrend, donchian, vwap, ...
"""

from __future__ import annotations

import numpy as np
from numpy.typing import ArrayLike

# ---------------------------------------------------------------------------
# Import Rust implementations
# ---------------------------------------------------------------------------
from ferro_ta._ferro_ta import (
    chandelier_exit as _rust_chandelier_exit,
)
from ferro_ta._ferro_ta import (
    choppiness_index as _rust_choppiness_index,
)
from ferro_ta._ferro_ta import (
    donchian as _rust_donchian,
)
from ferro_ta._ferro_ta import (
    hull_ma as _rust_hull_ma,
)
from ferro_ta._ferro_ta import (
    ichimoku as _rust_ichimoku,
)
from ferro_ta._ferro_ta import (
    keltner_channels as _rust_keltner_channels,
)
from ferro_ta._ferro_ta import (
    pivot_points as _rust_pivot_points,
)
from ferro_ta._ferro_ta import (
    supertrend as _rust_supertrend,
)
from ferro_ta._ferro_ta import (
    vwap as _rust_vwap,
)
from ferro_ta._ferro_ta import (
    vwma as _rust_vwma,
)
from ferro_ta._utils import _to_f64
from ferro_ta.core.exceptions import FerroTAValueError, _normalize_rust_error


def VWAP(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    volume: ArrayLike,
    timeperiod: int = 0,
) -> np.ndarray:
    """Volume Weighted Average Price.

    Parameters
    ----------
    high : array-like
        Sequence of high prices.
    low : array-like
        Sequence of low prices.
    close : array-like
        Sequence of closing prices.
    volume : array-like
        Sequence of volumes.
    timeperiod : int, optional
        Rolling window length. ``0`` (default) computes a cumulative VWAP
        from bar 0 (session VWAP). Any value ``>= 1`` uses a rolling window
        of that length; the first ``timeperiod - 1`` values are ``NaN``.

    Returns
    -------
    numpy.ndarray
        Array of VWAP values.

    Notes
    -----
    Typical price is used: ``(high + low + close) / 3``.
    Implemented in Rust for maximum performance.
    """
    if timeperiod < 0:
        raise FerroTAValueError("timeperiod must be >= 0 for VWAP")
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    v = _to_f64(volume)
    try:
        return np.asarray(_rust_vwap(h, lo, c, v, timeperiod))
    except ValueError as e:
        _normalize_rust_error(e)


def SUPERTREND(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    timeperiod: int = 7,
    multiplier: float = 3.0,
) -> tuple[np.ndarray, np.ndarray]:
    """Supertrend indicator.

    An ATR-based trend-following indicator. Returns the Supertrend line and a
    direction array.

    Parameters
    ----------
    high : array-like
        Sequence of high prices.
    low : array-like
        Sequence of low prices.
    close : array-like
        Sequence of closing prices.
    timeperiod : int, optional
        ATR period (default 7).
    multiplier : float, optional
        ATR multiplier for band width (default 3.0).

    Returns
    -------
    supertrend : numpy.ndarray
        The Supertrend line values. ``NaN`` during the warmup period.
    direction : numpy.ndarray
        ``1`` = uptrend (price above Supertrend), ``-1`` = downtrend.
        ``0`` during warmup.

    Notes
    -----
    Implemented in Rust — the sequential band-adjustment loop that was
    previously a Python bottleneck now runs at native speed.

    Examples
    --------
    >>> import numpy as np
    >>> from ferro_ta import SUPERTREND
    >>> h = np.array([10.0, 11.0, 12.0, 11.0, 10.0, 9.0, 8.0, 9.0, 10.0, 11.0,
    ...               12.0, 13.0, 14.0, 13.0, 12.0])
    >>> l = h - 1.0
    >>> c = (h + l) / 2.0
    >>> st, dir_ = SUPERTREND(h, l, c)
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        st, d = _rust_supertrend(h, lo, c, timeperiod, multiplier)
    except ValueError as e:
        _normalize_rust_error(e)
    return np.asarray(st), np.asarray(d)


def ICHIMOKU(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    tenkan_period: int = 9,
    kijun_period: int = 26,
    senkou_b_period: int = 52,
    displacement: int = 26,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Ichimoku Cloud (Ichimoku Kinko Hyo).

    Parameters
    ----------
    high : array-like
    low : array-like
    close : array-like
    tenkan_period : int, default 9
        Conversion line (Tenkan-sen) period.
    kijun_period : int, default 26
        Base line (Kijun-sen) period.
    senkou_b_period : int, default 52
        Leading Span B period.
    displacement : int, default 26
        Displacement / cloud offset for Senkou A & B.

    Returns
    -------
    tenkan, kijun, senkou_a, senkou_b, chikou : numpy.ndarray
        Each is a 1-D float64 array of the same length as the inputs.

    Notes
    -----
    Implemented in Rust with O(n) monotonic deque for all rolling windows.
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        t, k, sa, sb, ch = _rust_ichimoku(
            h, lo, c, tenkan_period, kijun_period, senkou_b_period, displacement
        )
    except ValueError as e:
        _normalize_rust_error(e)
    return (
        np.asarray(t),
        np.asarray(k),
        np.asarray(sa),
        np.asarray(sb),
        np.asarray(ch),
    )


def DONCHIAN(
    high: ArrayLike,
    low: ArrayLike,
    timeperiod: int = 20,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Donchian Channels — rolling highest high / lowest low.

    Parameters
    ----------
    high : array-like
    low : array-like
    timeperiod : int, default 20

    Returns
    -------
    upper, middle, lower : numpy.ndarray
        Rolling highest high, midpoint, and lowest low.

    Notes
    -----
    Implemented in Rust with O(n) monotonic deque (no Python loop).
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    try:
        upper, middle, lower = _rust_donchian(h, lo, timeperiod)
    except ValueError as e:
        _normalize_rust_error(e)
    return np.asarray(upper), np.asarray(middle), np.asarray(lower)


def PIVOT_POINTS(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    method: str = "classic",
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Pivot Points — support / resistance levels.

    Computes pivot points for each bar using the *previous bar's* H/L/C.
    The first bar output is NaN.

    Parameters
    ----------
    high : array-like
    low : array-like
    close : array-like
    method : {'classic', 'fibonacci', 'camarilla'}, default 'classic'

    Returns
    -------
    pivot, r1, s1, r2, s2 : numpy.ndarray

    Notes
    -----
    **Classic**: P=(H+L+C)/3; R1=2P−L; S1=2P−H; R2=P+(H−L); S2=P−(H−L)

    **Fibonacci**: P=(H+L+C)/3; R1=P+0.382*(H−L); S1=P−0.382*(H−L);
    R2=P+0.618*(H−L); S2=P−0.618*(H−L)

    **Camarilla**: P=(H+L+C)/3; R1=C+1.1*(H−L)/12; S1=C−1.1*(H−L)/12;
    R2=C+1.1*(H−L)/6; S2=C−1.1*(H−L)/6
    """
    valid_methods = {"classic", "fibonacci", "camarilla"}
    if method.lower() not in valid_methods:
        raise FerroTAValueError(
            f"Unknown pivot method '{method}'. Use 'classic', 'fibonacci', or 'camarilla'."
        )
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        pivot, r1, s1, r2, s2 = _rust_pivot_points(h, lo, c, method)
    except ValueError as e:
        _normalize_rust_error(e)
    return (
        np.asarray(pivot),
        np.asarray(r1),
        np.asarray(s1),
        np.asarray(r2),
        np.asarray(s2),
    )


def KELTNER_CHANNELS(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    timeperiod: int = 20,
    atr_period: int = 10,
    multiplier: float = 2.0,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Keltner Channels — EMA ± (multiplier × ATR).

    Parameters
    ----------
    high : array-like
    low : array-like
    close : array-like
    timeperiod : int, default 20
        EMA period for the middle band.
    atr_period : int, default 10
        ATR period for band width.
    multiplier : float, default 2.0
        ATR multiplier.

    Returns
    -------
    upper, middle, lower : numpy.ndarray

    Notes
    -----
    Implemented in Rust — EMA and ATR computed inline without Python calls.
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        upper, middle, lower = _rust_keltner_channels(
            h, lo, c, timeperiod, atr_period, multiplier
        )
    except ValueError as e:
        _normalize_rust_error(e)
    return np.asarray(upper), np.asarray(middle), np.asarray(lower)


def HULL_MA(
    close: ArrayLike,
    timeperiod: int = 16,
) -> np.ndarray:
    """Hull Moving Average (HMA).

    A fast-responding moving average that reduces lag.

    Parameters
    ----------
    close : array-like
    timeperiod : int, default 16

    Returns
    -------
    numpy.ndarray

    Notes
    -----
    Formula: ``HMA(n) = WMA(2 * WMA(n/2) - WMA(n), sqrt(n))``

    Implemented in Rust — all WMA computations are in-process.
    """
    c = _to_f64(close)
    try:
        return np.asarray(_rust_hull_ma(c, timeperiod))
    except ValueError as e:
        _normalize_rust_error(e)


def CHANDELIER_EXIT(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    timeperiod: int = 22,
    multiplier: float = 3.0,
) -> tuple[np.ndarray, np.ndarray]:
    """Chandelier Exit — ATR-based trailing stop levels.

    Parameters
    ----------
    high : array-like
    low : array-like
    close : array-like
    timeperiod : int, default 22
        Lookback period for highest high / lowest low and ATR.
    multiplier : float, default 3.0
        ATR multiplier.

    Returns
    -------
    long_exit, short_exit : numpy.ndarray

    Notes
    -----
    Implemented in Rust with O(n) monotonic deque for rolling max/min.
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        long_exit, short_exit = _rust_chandelier_exit(h, lo, c, timeperiod, multiplier)
    except ValueError as e:
        _normalize_rust_error(e)
    return np.asarray(long_exit), np.asarray(short_exit)


def VWMA(
    close: ArrayLike,
    volume: ArrayLike,
    timeperiod: int = 20,
) -> np.ndarray:
    """Volume Weighted Moving Average.

    Parameters
    ----------
    close : array-like
    volume : array-like
    timeperiod : int, default 20

    Returns
    -------
    numpy.ndarray

    Notes
    -----
    ``VWMA = sum(close * volume, n) / sum(volume, n)``
    Implemented in Rust with O(n) prefix-sum approach.
    """
    c = _to_f64(close)
    v = _to_f64(volume)
    try:
        return np.asarray(_rust_vwma(c, v, timeperiod))
    except ValueError as e:
        _normalize_rust_error(e)


def CHOPPINESS_INDEX(
    high: ArrayLike,
    low: ArrayLike,
    close: ArrayLike,
    timeperiod: int = 14,
) -> np.ndarray:
    """Choppiness Index — measures market choppiness (range-bound vs trending).

    Parameters
    ----------
    high : array-like
    low : array-like
    close : array-like
    timeperiod : int, default 14

    Returns
    -------
    numpy.ndarray
        Values in ``[0, 100]``. Values near 100 indicate choppy/range-bound
        markets; values near 0 indicate strong trends.

    Notes
    -----
    ``CI = 100 * log10(sum(ATR(1), n) / (highest_high − lowest_low)) / log10(n)``

    Implemented in Rust with O(n) monotonic deques (no Python loop).
    """
    h = _to_f64(high)
    lo = _to_f64(low)
    c = _to_f64(close)
    try:
        return np.asarray(_rust_choppiness_index(h, lo, c, timeperiod))
    except ValueError as e:
        _normalize_rust_error(e)


__all__ = [
    "VWAP",
    "SUPERTREND",
    "ICHIMOKU",
    "DONCHIAN",
    "PIVOT_POINTS",
    "KELTNER_CHANNELS",
    "HULL_MA",
    "CHANDELIER_EXIT",
    "VWMA",
    "CHOPPINESS_INDEX",
]
