import sys
from _typeshed import SupportsRichComparisonT
from collections.abc import Callable, Hashable, Iterable, Sequence, Sized
from decimal import Decimal
from fractions import Fraction
from typing import Literal, NamedTuple, Protocol, SupportsFloat, SupportsIndex, TypeAlias, TypeVar, type_check_only
from typing_extensions import Self

__all__ = [
    "StatisticsError",
    "covariance",
    "correlation",
    "fmean",
    "geometric_mean",
    "linear_regression",
    "mean",
    "harmonic_mean",
    "pstdev",
    "pvariance",
    "stdev",
    "variance",
    "median",
    "median_low",
    "median_high",
    "median_grouped",
    "mode",
    "multimode",
    "NormalDist",
    "quantiles",
]

if sys.version_info >= (3, 13):
    __all__ += ["kde", "kde_random"]

# Most functions in this module accept homogeneous collections of one of these types
_Number: TypeAlias = float | Decimal | Fraction
_NumberT = TypeVar("_NumberT", float, Decimal, Fraction)

# Used in mode, multimode
_HashableT = TypeVar("_HashableT", bound=Hashable)

# Used in NormalDist.samples and kde_random
_Seed: TypeAlias = int | float | str | bytes | bytearray  # noqa: Y041

# Used in linear_regression
_T_co = TypeVar("_T_co", covariant=True)

@type_check_only
class _SizedIterable(Iterable[_T_co], Sized, Protocol[_T_co]): ...

class StatisticsError(ValueError): ...

if sys.version_info >= (3, 11):
    def fmean(data: Iterable[SupportsFloat], weights: Iterable[SupportsFloat] | None = None) -> float: ...

else:
    def fmean(data: Iterable[SupportsFloat]) -> float: ...

def geometric_mean(data: Iterable[SupportsFloat]) -> float: ...
def mean(data: Iterable[_NumberT]) -> _NumberT: ...
def harmonic_mean(data: Iterable[_NumberT], weights: Iterable[_Number] | None = None) -> _NumberT: ...
def median(data: Iterable[_NumberT]) -> _NumberT: ...
def median_low(data: Iterable[SupportsRichComparisonT]) -> SupportsRichComparisonT: ...
def median_high(data: Iterable[SupportsRichComparisonT]) -> SupportsRichComparisonT: ...

if sys.version_info >= (3, 11):
    def median_grouped(data: Iterable[SupportsFloat], interval: SupportsFloat = 1.0) -> float: ...

else:
    def median_grouped(data: Iterable[_NumberT], interval: _NumberT | float = 1) -> _NumberT | float: ...

def mode(data: Iterable[_HashableT]) -> _HashableT: ...
def multimode(data: Iterable[_HashableT]) -> list[_HashableT]: ...
def pstdev(data: Iterable[_NumberT], mu: _NumberT | None = None) -> _NumberT: ...
def pvariance(data: Iterable[_NumberT], mu: _NumberT | None = None) -> _NumberT: ...
def quantiles(
    data: Iterable[_NumberT], *, n: int = 4, method: Literal["inclusive", "exclusive"] = "exclusive"
) -> list[_NumberT]: ...
def stdev(data: Iterable[_NumberT], xbar: _NumberT | None = None) -> _NumberT: ...
def variance(data: Iterable[_NumberT], xbar: _NumberT | None = None) -> _NumberT: ...

class NormalDist:
    __slots__ = {"_mu": "Arithmetic mean of a normal distribution", "_sigma": "Standard deviation of a normal distribution"}
    def __init__(self, mu: float = 0.0, sigma: float = 1.0) -> None: ...
    @property
    def mean(self) -> float: ...
    @property
    def median(self) -> float: ...
    @property
    def mode(self) -> float: ...
    @property
    def stdev(self) -> float: ...
    @property
    def variance(self) -> float: ...
    @classmethod
    def from_samples(cls, data: Iterable[SupportsFloat]) -> Self: ...
    def samples(self, n: SupportsIndex, *, seed: _Seed | None = None) -> list[float]: ...
    def pdf(self, x: float) -> float: ...
    def cdf(self, x: float) -> float: ...
    def inv_cdf(self, p: float) -> float: ...
    def overlap(self, other: NormalDist) -> float: ...
    def quantiles(self, n: int = 4) -> list[float]: ...
    def zscore(self, x: float) -> float: ...
    def __eq__(x1, x2: object) -> bool: ...
    def __add__(x1, x2: float | NormalDist) -> NormalDist: ...
    def __sub__(x1, x2: float | NormalDist) -> NormalDist: ...
    def __mul__(x1, x2: float) -> NormalDist: ...
    def __truediv__(x1, x2: float) -> NormalDist: ...
    def __pos__(x1) -> NormalDist: ...
    def __neg__(x1) -> NormalDist: ...
    __radd__ = __add__
    def __rsub__(x1, x2: float | NormalDist) -> NormalDist: ...
    __rmul__ = __mul__
    def __hash__(self) -> int: ...

if sys.version_info >= (3, 12):
    def correlation(
        x: Sequence[_Number], y: Sequence[_Number], /, *, method: Literal["linear", "ranked"] = "linear"
    ) -> float: ...

else:
    def correlation(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ...

def covariance(x: Sequence[_Number], y: Sequence[_Number], /) -> float: ...

class LinearRegression(NamedTuple):
    slope: float
    intercept: float

if sys.version_info >= (3, 11):
    def linear_regression(
        regressor: _SizedIterable[_Number], dependent_variable: _SizedIterable[_Number], /, *, proportional: bool = False
    ) -> LinearRegression: ...

else:
    def linear_regression(
        regressor: _SizedIterable[_Number], dependent_variable: _SizedIterable[_Number], /
    ) -> LinearRegression: ...

if sys.version_info >= (3, 13):
    _Kernel: TypeAlias = Literal[
        "normal",
        "gauss",
        "logistic",
        "sigmoid",
        "rectangular",
        "uniform",
        "triangular",
        "parabolic",
        "epanechnikov",
        "quartic",
        "biweight",
        "triweight",
        "cosine",
    ]
    def kde(
        data: Sequence[float], h: float, kernel: _Kernel = "normal", *, cumulative: bool = False
    ) -> Callable[[float], float]: ...
    def kde_random(
        data: Sequence[float], h: float, kernel: _Kernel = "normal", *, seed: _Seed | None = None
    ) -> Callable[[], float]: ...
