"""
Swing pivot detection (V15 patch #13) — port fedele.

Used by structural SL placement: BUY trades anchor the SL behind a
recent swing LOW, SELL trades behind a swing HIGH. When no swing
qualifies, brain_tf / brain_mr fall back to ATR-based SL.

Pivot definition: 5-bar (2 left + 2 right neighbors are higher-low /
lower-high). "Strong" pivots additionally satisfy a wider neighbor
window scaled by atr_ratio (n_strong: 2 / 3 / 4).
"""

from __future__ import annotations

from typing import Any

import pandas as pd


def identify_swing_levels(
    df: pd.DataFrame,
    direction: str,
    lookback: int = 30,
    atr_ratio: float = 1.0,
    price_decimals: int = 2,
    entry_price: float | None = None,
) -> dict[str, Any]:
    """
    Find the most extreme swing point on the correct side of `entry_price`.

    Args:
        df: OHLCV (M5 typical).
        direction: "BUY" -> swing LOW; "SELL" -> swing HIGH.
        lookback: how many bars back from (n-3) to scan.
        atr_ratio: tunes the strength threshold (lower vol -> easier to qualify).
        price_decimals: rounding for swing_price.
        entry_price: filters swings on the wrong side (e.g. swing LOW must be
                     below entry for a BUY). When None no filter.

    Returns:
        dict with keys swing_price, swing_index, swing_found, swing_type,
        swing_strength, all_swings (truncated to first 5).
    """
    empty: dict[str, Any] = {
        "swing_price": None,
        "swing_index": None,
        "swing_found": False,
        "swing_type":  "SWING_LOW" if direction == "BUY" else "SWING_HIGH",
        "swing_strength": 0,
        "all_swings": [],
    }

    if df is None or len(df) < 12:
        return empty

    highs = df["high"].values
    lows = df["low"].values
    n = len(df)

    if atr_ratio < 1.0:
        n_strong = 2
    elif atr_ratio < 1.5:
        n_strong = 3
    else:
        n_strong = 4

    all_swings: list[dict] = []
    strong_swings: list[dict] = []
    scan_end = max(n - 3 - lookback, n_strong)

    for i in range(n - 3, scan_end, -1):
        bars_ago = (n - 1) - i
        if direction == "BUY":
            is_pivot_5 = (
                lows[i] < lows[i - 1] and lows[i] < lows[i - 2] and
                lows[i] < lows[i + 1] and lows[i] < lows[i + 2]
            )
            if is_pivot_5:
                price = round(float(lows[i]), price_decimals)
                is_strong = (
                    i >= n_strong and i + n_strong < n and
                    lows[i] < lows[i - n_strong] and
                    lows[i] < lows[i + n_strong]
                )
                sw = {"price": price, "bars_ago": bars_ago,
                      "type": "SWING_LOW", "strong": is_strong}
                all_swings.append(sw)
                if is_strong:
                    strong_swings.append(sw)
        else:
            is_pivot_5 = (
                highs[i] > highs[i - 1] and highs[i] > highs[i - 2] and
                highs[i] > highs[i + 1] and highs[i] > highs[i + 2]
            )
            if is_pivot_5:
                price = round(float(highs[i]), price_decimals)
                is_strong = (
                    i >= n_strong and i + n_strong < n and
                    highs[i] > highs[i - n_strong] and
                    highs[i] > highs[i + n_strong]
                )
                sw = {"price": price, "bars_ago": bars_ago,
                      "type": "SWING_HIGH", "strong": is_strong}
                all_swings.append(sw)
                if is_strong:
                    strong_swings.append(sw)

    if not all_swings:
        return empty

    if entry_price is not None:
        if direction == "BUY":
            all_swings    = [s for s in all_swings    if s["price"] < entry_price]
            strong_swings = [s for s in strong_swings if s["price"] < entry_price]
        else:
            all_swings    = [s for s in all_swings    if s["price"] > entry_price]
            strong_swings = [s for s in strong_swings if s["price"] > entry_price]

    if not all_swings:
        return empty

    candidate_pool = strong_swings if strong_swings else all_swings
    if direction == "BUY":
        chosen = min(candidate_pool, key=lambda x: x["price"])
    else:
        chosen = max(candidate_pool, key=lambda x: x["price"])

    return {
        "swing_price":    chosen["price"],
        "swing_index":    chosen["bars_ago"],
        "swing_found":    True,
        "swing_type":     chosen["type"],
        "swing_strength": n_strong if chosen.get("strong") else 1,
        "all_swings":     all_swings[:5],
    }
