"""OpenLL reference implementation.

SPDX-License-Identifier: MIT
Copyright (c) 2026 The 100 Apps Project

This file reproduces the regime calculation used by Meridian. Input must be a
chronological pandas DataFrame with open, high, low, and close columns.
"""

from __future__ import annotations

import pandas as pd

try:
    import pandas_ta as ta
except ImportError:
    ta = None


EMA_FAST = 30
EMA_SLOW = 60
ATR_PERIOD = 60
ATR_MULTIPLIER = 0.3


def _ema(close: pd.Series, period: int) -> pd.Series:
    if ta is not None:
        return ta.ema(close, length=period)
    return close.ewm(span=period, adjust=False).mean()


def _atr(
    high: pd.Series,
    low: pd.Series,
    close: pd.Series,
    period: int,
) -> pd.Series:
    if ta is not None:
        return ta.atr(high, low, close, length=period)

    previous_close = close.shift(1)
    true_range = pd.concat(
        [
            (high - low).abs(),
            (high - previous_close).abs(),
            (low - previous_close).abs(),
        ],
        axis=1,
    ).max(axis=1)
    return true_range.rolling(window=period, min_periods=period).mean()


def compute_openll(prices: pd.DataFrame) -> pd.DataFrame:
    """Return OpenLL values and regime IDs: BULL=1, NEUTRAL=0, BEAR=-1."""
    required_columns = {"open", "high", "low", "close"}
    missing = required_columns.difference(prices.columns)
    if missing:
        raise ValueError(f"Missing required columns: {', '.join(sorted(missing))}")
    if len(prices) < max(EMA_FAST, EMA_SLOW, ATR_PERIOD):
        raise ValueError("OpenLL requires at least 60 chronological OHLC bars")

    result = prices.loc[:, ["open", "high", "low", "close"]].copy()
    result["ema_fast"] = _ema(result["close"], EMA_FAST)
    result["ema_slow"] = _ema(result["close"], EMA_SLOW)
    result["atr"] = _atr(
        result["high"], result["low"], result["close"], ATR_PERIOD
    )
    result["ema_gap"] = result["ema_fast"] - result["ema_slow"]
    result["threshold"] = result["atr"] * ATR_MULTIPLIER
    result["upper_boundary"] = result["ema_slow"] + result["threshold"]
    result["lower_boundary"] = result["ema_slow"] - result["threshold"]

    result["regime_id"] = 0
    result.loc[result["ema_gap"] > result["threshold"], "regime_id"] = 1
    result.loc[result["ema_gap"] < -result["threshold"], "regime_id"] = -1
    result["regime"] = result["regime_id"].map(
        {1: "BULL", 0: "NEUTRAL", -1: "BEAR"}
    )
    return result.dropna()