"""steady_state.py — 안정상태(steady state) 자동검출.

방법:
- 초기 warm-up 구간(기본 5분) 제외.
- 슬라이딩 윈도우(기본 5분) 내 VO2, VCO2, RQ 의 변동계수(CV=SD/mean)를 계산.
- 세 지표 모두 임계(기본 10%) 이하인 가장 이른 연속 구간을 안정구간으로 추천.
- ACSM/관례상 VO2 CV < 10% 가 흔히 쓰이는 기준.

본 도구는 연구용·참고용이며 임상 의사결정에 직접 사용 금지.
"""

from __future__ import annotations

import numpy as np
import pandas as pd


def coefficient_of_variation(x):
    """CV = SD/mean (양수 평균 가정). 결측 무시."""
    x = np.asarray(x, dtype=float)
    x = x[~np.isnan(x)]
    if x.size == 0:
        return np.nan
    m = np.mean(x)
    if m == 0:
        return np.nan
    return float(np.std(x, ddof=1) / m) if x.size > 1 else 0.0


def rolling_cv(df, time_col="time_min", vo2_col="VO2_Lmin", vco2_col="VCO2_Lmin",
               window_min=5.0):
    """각 시점을 시작으로 하는 window_min 분 윈도우의 VO2/VCO2/RQ CV 계산.

    반환: time_col, cv_vo2, cv_vco2, cv_rq 컬럼을 가진 DataFrame.
    윈도우가 데이터 끝을 넘으면 해당 행 CV = NaN.
    """
    t = df[time_col].values.astype(float)
    vo2 = df[vo2_col].values.astype(float)
    vco2 = df[vco2_col].values.astype(float)
    with np.errstate(divide="ignore", invalid="ignore"):
        rqv = np.where(vo2 > 0, vco2 / vo2, np.nan)

    rows = []
    n = len(t)
    for i in range(n):
        t0 = t[i]
        mask = (t >= t0) & (t < t0 + window_min)
        if mask.sum() < 2 or (t0 + window_min) > (t[-1] + 1e-9):
            rows.append((t0, np.nan, np.nan, np.nan))
            continue
        rows.append((
            t0,
            coefficient_of_variation(vo2[mask]),
            coefficient_of_variation(vco2[mask]),
            coefficient_of_variation(rqv[mask]),
        ))
    return pd.DataFrame(rows, columns=[time_col, "cv_vo2", "cv_vco2", "cv_rq"])


def detect_steady_state(df, time_col="time_min", vo2_col="VO2_Lmin",
                        vco2_col="VCO2_Lmin", warmup_min=5.0,
                        window_min=5.0, cv_threshold=0.10):
    """안정구간 자동검출.

    반환 dict:
      found (bool), start_min, end_min, window_min, cv_threshold,
      cv_table (DataFrame), mask (안정구간 표시 bool ndarray on df rows)
    전략: warm-up 이후 가장 이른 윈도우 시작점에서 세 CV 모두 임계 이하인 곳을
          안정구간 시작으로 삼고, 그 윈도우(window_min) 구간을 추천.
    """
    cv_table = rolling_cv(df, time_col, vo2_col, vco2_col, window_min)
    t = df[time_col].values.astype(float)

    eligible = cv_table[cv_table[time_col] >= warmup_min].copy()
    ok = (
        (eligible["cv_vo2"] <= cv_threshold)
        & (eligible["cv_vco2"] <= cv_threshold)
        & (eligible["cv_rq"] <= cv_threshold)
    )
    eligible_ok = eligible[ok]

    mask = np.zeros(len(df), dtype=bool)
    if len(eligible_ok) == 0:
        return {
            "found": False,
            "start_min": None,
            "end_min": None,
            "window_min": window_min,
            "cv_threshold": cv_threshold,
            "cv_table": cv_table,
            "mask": mask,
        }

    start_min = float(eligible_ok[time_col].iloc[0])
    end_min = start_min + window_min
    mask = (t >= start_min) & (t < end_min)
    return {
        "found": True,
        "start_min": start_min,
        "end_min": end_min,
        "window_min": window_min,
        "cv_threshold": cv_threshold,
        "cv_table": cv_table,
        "mask": mask,
    }
