"""Percentile-rank helpers underlying every score (spec §13)."""
from __future__ import annotations

from typing import Sequence

import numpy as np


def percentile_rank(value: float, peers: Sequence[float], lower_is_better: bool = False) -> float:
    """
    Percentage of `peers` that `value` beats (0-100). Ties are handled by counting them as
    "half beaten" (mean rank), the standard approach for percentile scoring so a value tied
    with the whole peer set lands at 50, not 0 or 100.
    """
    if not peers:
        return 50.0
    arr = np.asarray(peers, dtype=float)
    less = float(np.sum(arr < value))
    equal = float(np.sum(arr == value))
    n = len(arr)
    raw = (less + 0.5 * equal) / n * 100.0
    return 100.0 - raw if lower_is_better else raw


MIN_PEER_GROUP_SIZE = 8


def peer_values_with_fallback(
    industry_bucket_values: Sequence[float],
    industry_values: Sequence[float],
    sector_values: Sequence[float],
    min_size: int = MIN_PEER_GROUP_SIZE,
) -> tuple[Sequence[float], str]:
    """
    Widening fallback: industry+cap-bucket peer group -> whole industry -> whole sector.
    Returns (peer_values_used, confidence_tier) where confidence_tier documents which level
    was actually used, since scoring.py downgrades confidence on each widening step.
    """
    if len(industry_bucket_values) >= min_size:
        return industry_bucket_values, "industry_and_cap_bucket"
    if len(industry_values) >= min_size:
        return industry_values, "industry_wide"
    if len(sector_values) >= min_size:
        return sector_values, "sector_wide"
    # Even the sector is thin — use whatever we have rather than fail outright.
    return sector_values or industry_values or industry_bucket_values, "insufficient_peer_group"
