"""Tap tempo estimation helpers."""

from statistics import mean, median


def _validate_numeric_sequence(values: list[float], label: str) -> list[float]:
    if len(values) == 0:
        raise ValueError(f"{label} cannot be empty.")

    cleaned: list[float] = []
    for value in values:
        try:
            numeric_value = float(value)
        except (TypeError, ValueError) as exc:
            raise ValueError(f"{label} must contain only numbers.") from exc
        cleaned.append(numeric_value)

    return cleaned


def _intervals_from_timestamps(timestamps: list[float]) -> list[float]:
    cleaned_timestamps = _validate_numeric_sequence(timestamps, "timestamps")

    if len(cleaned_timestamps) < 2:
        raise ValueError("At least two timestamps are required.")

    intervals: list[float] = []
    previous = cleaned_timestamps[0]
    for current in cleaned_timestamps[1:]:
        interval = current - previous
        if interval <= 0:
            raise ValueError("timestamps must be strictly increasing.")
        intervals.append(interval)
        previous = current

    return intervals


def _bpm_from_intervals(intervals: list[float], strategy: str = "median") -> float:
    cleaned_intervals = _validate_numeric_sequence(intervals, "intervals")
    if len(cleaned_intervals) == 0:
        raise ValueError("At least one interval is required.")

    for interval in cleaned_intervals:
        if interval <= 0:
            raise ValueError("intervals must be greater than 0.")

    normalized_strategy = strategy.lower()
    if normalized_strategy == "median":
        representative_interval = median(cleaned_intervals)
    elif normalized_strategy == "mean":
        representative_interval = mean(cleaned_intervals)
    else:
        raise ValueError("strategy must be either 'median' or 'mean'.")

    bpm = 60000.0 / representative_interval
    return round(bpm, 2)


def estimate_bpm_from_taps(timestamps: list[float], strategy: str = "median") -> float:
    """Estimate BPM from a sequence of tap timestamps in milliseconds."""

    intervals = _intervals_from_timestamps(timestamps)
    return _bpm_from_intervals(intervals, strategy=strategy)

