"""
Pure NumPy/pandas technical indicators.
All functions accept lists or arrays and return float or array.
"""
import numpy as np
from typing import List


def ema(values: List[float], period: int) -> np.ndarray:
    arr = np.array(values, dtype=float)
    k = 2.0 / (period + 1)
    result = np.zeros_like(arr)
    result[0] = arr[0]
    for i in range(1, len(arr)):
        result[i] = arr[i] * k + result[i - 1] * (1 - k)
    return result


def atr(highs: List[float], lows: List[float], closes: List[float], period: int = 14) -> float:
    h = np.array(highs, dtype=float)
    l = np.array(lows, dtype=float)
    c = np.array(closes, dtype=float)
    tr = np.maximum(h[1:] - l[1:], np.maximum(abs(h[1:] - c[:-1]), abs(l[1:] - c[:-1])))
    if len(tr) < period:
        return float(np.mean(tr))
    # Wilder's smoothing
    atr_val = float(np.mean(tr[:period]))
    for i in range(period, len(tr)):
        atr_val = (atr_val * (period - 1) + tr[i]) / period
    return atr_val


def rsi(closes: List[float], period: int = 14) -> float:
    c = np.array(closes, dtype=float)
    deltas = np.diff(c)
    gains = np.where(deltas > 0, deltas, 0.0)
    losses = np.where(deltas < 0, -deltas, 0.0)
    if len(gains) < period:
        return 50.0
    avg_gain = np.mean(gains[:period])
    avg_loss = np.mean(losses[:period])
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
    if avg_loss == 0:
        return 100.0
    rs = avg_gain / avg_loss
    return 100.0 - (100.0 / (1.0 + rs))


def adx(highs: List[float], lows: List[float], closes: List[float], period: int = 14) -> float:
    h = np.array(highs, dtype=float)
    l = np.array(lows, dtype=float)
    c = np.array(closes, dtype=float)
    n = len(c)
    if n < period + 1:
        return 0.0

    dm_plus = np.where((h[1:] - h[:-1]) > (l[:-1] - l[1:]),
                       np.maximum(h[1:] - h[:-1], 0), 0.0)
    dm_minus = np.where((l[:-1] - l[1:]) > (h[1:] - h[:-1]),
                        np.maximum(l[:-1] - l[1:], 0), 0.0)
    tr = np.maximum(h[1:] - l[1:], np.maximum(abs(h[1:] - c[:-1]), abs(l[1:] - c[:-1])))

    # Wilder's smooth over period
    def wilder_smooth(arr, p):
        s = np.sum(arr[:p])
        result = [s]
        for i in range(p, len(arr)):
            s = s - s / p + arr[i]
            result.append(s)
        return np.array(result)

    atr_s = wilder_smooth(tr, period)
    dmp_s = wilder_smooth(dm_plus, period)
    dmm_s = wilder_smooth(dm_minus, period)

    with np.errstate(divide="ignore", invalid="ignore"):
        di_plus = np.where(atr_s > 0, 100 * dmp_s / atr_s, 0.0)
        di_minus = np.where(atr_s > 0, 100 * dmm_s / atr_s, 0.0)
        dx = np.where((di_plus + di_minus) > 0,
                      100 * abs(di_plus - di_minus) / (di_plus + di_minus), 0.0)

    if len(dx) < period:
        return float(np.mean(dx))
    adx_val = float(np.mean(dx[:period]))
    for i in range(period, len(dx)):
        adx_val = (adx_val * (period - 1) + dx[i]) / period
    return adx_val


def volume_ratio(volumes: List[float], period: int = 20) -> float:
    """Current volume vs rolling average."""
    v = np.array(volumes, dtype=float)
    if len(v) < 2:
        return 1.0
    avg = np.mean(v[-period - 1:-1]) if len(v) > period else np.mean(v[:-1])
    if avg == 0:
        return 1.0
    return float(v[-1] / avg)


def find_swing_levels(highs: List[float], lows: List[float], lookback: int = 20) -> dict:
    """Find recent swing high/low for structure-based SL."""
    h = np.array(highs[-lookback:], dtype=float)
    l = np.array(lows[-lookback:], dtype=float)
    return {
        "swing_high": float(np.max(h)),
        "swing_low": float(np.min(l)),
    }
