"""Pure-python technical indicators (no numpy dependency)."""


def sma(values: list[float], period: int) -> float | None:
    if len(values) < period:
        return None
    return sum(values[-period:]) / period


def ema_series(values: list[float], period: int) -> list[float]:
    if not values:
        return []
    k = 2 / (period + 1)
    out = [values[0]]
    for v in values[1:]:
        out.append(v * k + out[-1] * (1 - k))
    return out


def ema(values: list[float], period: int) -> float | None:
    if len(values) < period:
        return None
    return ema_series(values, period)[-1]


def rsi(closes: list[float], period: int = 14) -> float | None:
    if len(closes) < period + 1:
        return None
    gains, losses = [], []
    for i in range(1, len(closes)):
        diff = closes[i] - closes[i - 1]
        gains.append(max(diff, 0))
        losses.append(max(-diff, 0))
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / 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 round(100 - 100 / (1 + rs), 2)


def macd(closes: list[float]) -> tuple[float, float, float] | None:
    """Returns (macd_line, signal_line, histogram)."""
    if len(closes) < 35:
        return None
    fast = ema_series(closes, 12)
    slow = ema_series(closes, 26)
    macd_line = [f - s for f, s in zip(fast, slow)]
    signal = ema_series(macd_line, 9)
    return round(macd_line[-1], 4), round(signal[-1], 4), round(macd_line[-1] - signal[-1], 4)


def atr(bars: list[dict], period: int = 14) -> float | None:
    if len(bars) < period + 1:
        return None
    trs = []
    for i in range(1, len(bars)):
        h, l, pc = bars[i]["high"], bars[i]["low"], bars[i - 1]["close"]
        trs.append(max(h - l, abs(h - pc), abs(l - pc)))
    a = sum(trs[:period]) / period
    for tr in trs[period:]:
        a = (a * (period - 1) + tr) / period
    return round(a, 4)


def bollinger(closes: list[float], period: int = 20, mult: float = 2.0):
    """Returns (upper, mid, lower, %B) or None."""
    if len(closes) < period:
        return None
    window = closes[-period:]
    mid = sum(window) / period
    var = sum((c - mid) ** 2 for c in window) / period
    sd = var ** 0.5
    upper, lower = mid + mult * sd, mid - mult * sd
    pct_b = (closes[-1] - lower) / (upper - lower) if upper != lower else 0.5
    return round(upper, 2), round(mid, 2), round(lower, 2), round(pct_b, 3)
