"""ZigZag 스윙 피벗 탐지 (Ground Truth용, 미래 데이터 허용)."""

from __future__ import annotations

from dataclasses import dataclass

import pandas as pd


@dataclass(frozen=True)
class Pivot:
    """스윙 고점/저점."""

    bar_index: int
    side: str  # "low" | "high"
    price: float
    datetime: pd.Timestamp


def find_zigzag_pivots(
    df: pd.DataFrame,
    reversal_pct: float = 5.0,
) -> list[Pivot]:
    """ZigZag 알고리즘으로 교대 스윙 피벗을 찾는다.

    가격이 직전 극값 대비 `reversal_pct`% 이상 되돌림되면 피벗을 확정한다.
    Ground Truth 라벨링용이므로 전체 시계열(미래 포함)을 사용한다.

    Args:
        df: datetime, high, low, close 컬럼 DataFrame.
        reversal_pct: 피벗 확정 최소 되돌림 비율(%).

    Returns:
        시간순 피벗 리스트 (low/high 교대).
    """
    if len(df) < 3:
        return []

    threshold = reversal_pct / 100.0
    pivots: list[Pivot] = []

    direction: str | None = None
    extreme_idx = 0
    extreme_price = float(df.iloc[0]["close"])
    anchor_price = extreme_price

    for i in range(1, len(df)):
        high = float(df.iloc[i]["high"])
        low = float(df.iloc[i]["low"])

        if direction is None:
            if high >= anchor_price * (1 + threshold):
                direction = "up"
                extreme_idx = i
                extreme_price = high
            elif low <= anchor_price * (1 - threshold):
                direction = "down"
                extreme_idx = i
                extreme_price = low
            continue

        if direction == "up":
            if high >= extreme_price:
                extreme_price = high
                extreme_idx = i
            if low <= extreme_price * (1 - threshold):
                pivots.append(_make_pivot(df, extreme_idx, "high", extreme_price))
                direction = "down"
                extreme_idx = i
                extreme_price = low
                anchor_price = extreme_price
        else:
            if low <= extreme_price:
                extreme_price = low
                extreme_idx = i
            if high >= extreme_price * (1 + threshold):
                pivots.append(_make_pivot(df, extreme_idx, "low", extreme_price))
                direction = "up"
                extreme_idx = i
                extreme_price = high
                anchor_price = extreme_price

    # 마지막 미확정 극값도 피벗으로 추가 (방향에 따라)
    if direction == "up":
        pivots.append(_make_pivot(df, extreme_idx, "high", extreme_price))
    elif direction == "down":
        pivots.append(_make_pivot(df, extreme_idx, "low", extreme_price))

    return _normalize_alternating(pivots)


def _make_pivot(df: pd.DataFrame, idx: int, side: str, price: float) -> Pivot:
    """Pivot 객체를 생성한다."""
    return Pivot(
        bar_index=idx,
        side=side,
        price=price,
        datetime=pd.Timestamp(df.iloc[idx]["datetime"]),
    )


def _normalize_alternating(pivots: list[Pivot]) -> list[Pivot]:
    """인접 동일 side 피벗을 제거하고 low/high 교대를 맞춘다."""
    if not pivots:
        return []

    cleaned: list[Pivot] = [pivots[0]]
    for pivot in pivots[1:]:
        last = cleaned[-1]
        if pivot.side == last.side:
            # 같은 방향이면 더 극단적인 가격으로 갱신
            if pivot.side == "high" and pivot.price >= last.price:
                cleaned[-1] = pivot
            elif pivot.side == "low" and pivot.price <= last.price:
                cleaned[-1] = pivot
        else:
            cleaned.append(pivot)

    # 첫 피벗이 high면 앞 low가 누락된 경우 — GT에서는 허용
    return cleaned
