"""auc.py — 공통 trapezoidal AUC 유틸리티.

식후 지질 OFTT와 ¹³C 호기검사 모두에서 사용하는 사다리꼴 적분 함수를 모은다.
모든 함수는 순수 numpy 기반이며 NaN/결측 시점을 안전하게 처리한다.

면책: 본 모듈은 연구용·참고용 계산 유틸리티이며 임상 의사결정에 직접 사용 금지.
"""
from __future__ import annotations

from typing import Sequence, Tuple

import numpy as np


def _clean_xy(t: Sequence[float], y: Sequence[float]) -> Tuple[np.ndarray, np.ndarray]:
    """시간(t)·값(y)을 numpy로 변환, NaN 동반 제거, 시간 오름차순 정렬."""
    t = np.asarray(t, dtype=float)
    y = np.asarray(y, dtype=float)
    if t.shape != y.shape:
        raise ValueError("t와 y의 길이가 다릅니다.")
    mask = ~(np.isnan(t) | np.isnan(y))
    t, y = t[mask], y[mask]
    order = np.argsort(t)
    return t[order], y[order]


def total_auc(t: Sequence[float], y: Sequence[float]) -> float:
    """총 AUC (total area under curve), 사다리꼴 법칙.

    AUC = sum_i (t[i+1]-t[i]) * (y[i]+y[i+1]) / 2
    단위: y단위 × 시간단위 (예: mg/dL·h).
    """
    t, y = _clean_xy(t, y)
    if len(t) < 2:
        return float("nan")
    return float(np.trapz(y, t))


def incremental_auc(t: Sequence[float], y: Sequence[float],
                    baseline: float | None = None,
                    clip_negative: bool = False) -> float:
    """증분 AUC (iAUC, baseline-subtracted).

    baseline 미지정 시 첫 시점(t 최소) 값을 기저로 사용.
    clip_negative=True 이면 기저 미만(음수) 구간을 0으로 절단 (식후 지질에서
    기저 복귀 후 음수 기여 배제 시 사용). 기본은 음수 허용(net iAUC).
    """
    t, y = _clean_xy(t, y)
    if len(t) < 2:
        return float("nan")
    if baseline is None:
        baseline = float(y[0])
    delta = y - baseline
    if clip_negative:
        delta = np.clip(delta, 0.0, None)
    return float(np.trapz(delta, t))


def peak_and_tmax(t: Sequence[float], y: Sequence[float]) -> Tuple[float, float]:
    """peak 값과 Tmax(peak 도달 시간) 반환."""
    t, y = _clean_xy(t, y)
    if len(t) == 0:
        return float("nan"), float("nan")
    idx = int(np.argmax(y))
    return float(y[idx]), float(t[idx])


def cumulative_auc_to(t: Sequence[float], y: Sequence[float], t_end: float) -> float:
    """0..t_end 구간의 누적 AUC. t_end 가 마지막 측정 시점 사이에 들어오면
    선형 보간으로 끝점을 만들어 적분 (호기검사 cPDR 40/120분 등에서 사용)."""
    t, y = _clean_xy(t, y)
    if len(t) < 2:
        return float("nan")
    if t_end <= t[0]:
        return 0.0
    if t_end >= t[-1]:
        return float(np.trapz(y, t))
    # t_end 이하 시점 + 보간점
    keep = t <= t_end
    t_k = t[keep]
    y_k = y[keep]
    y_interp = float(np.interp(t_end, t, y))
    t_full = np.append(t_k, t_end)
    y_full = np.append(y_k, y_interp)
    return float(np.trapz(y_full, t_full))


def return_to_baseline_time(t: Sequence[float], y: Sequence[float],
                            baseline: float | None = None,
                            tol_frac: float = 0.10) -> float:
    """기저 복귀 시간(청소 시간) 추정.

    peak 이후 처음으로 baseline*(1+tol_frac) 이하로 떨어지는 시점을
    인접 두 시점 사이 선형 보간으로 구한다. 복귀하지 않으면 NaN.
    tol_frac: 기저 대비 허용 오차 분율 (기본 10%).
    """
    t, y = _clean_xy(t, y)
    if len(t) < 2:
        return float("nan")
    if baseline is None:
        baseline = float(y[0])
    threshold = baseline * (1.0 + tol_frac)
    peak_idx = int(np.argmax(y))
    for i in range(peak_idx, len(t) - 1):
        if y[i] > threshold >= y[i + 1]:
            # 선형 보간
            frac = (y[i] - threshold) / (y[i] - y[i + 1])
            return float(t[i] + frac * (t[i + 1] - t[i]))
    # peak 자체가 이미 threshold 이하인 경우
    if y[peak_idx] <= threshold:
        return float(t[peak_idx])
    return float("nan")
