from __future__ import annotations

from typing import TYPE_CHECKING, Any, Protocol

if TYPE_CHECKING:
    from collections.abc import Sequence

    from typing_extensions import Self

    from narwhals._compliant.any_namespace import (
        CatNamespace,
        DateTimeNamespace,
        ListNamespace,
        StringNamespace,
        StructNamespace,
    )
    from narwhals._compliant.namespace import CompliantNamespace
    from narwhals._typing import NoDefault
    from narwhals._utils import Version
    from narwhals.typing import (
        ClosedInterval,
        FillNullStrategy,
        IntoDType,
        ModeKeepStrategy,
        RankMethod,
    )

__all__ = ["CompliantColumn"]


class CompliantColumn(Protocol):
    """Common parts of `Expr`, `Series`."""

    _version: Version

    def __add__(self, other: Self) -> Self: ...
    def __and__(self, other: Self) -> Self: ...
    def __eq__(self, other: Self) -> Self: ...  # type: ignore[override]
    def __floordiv__(self, other: Self) -> Self: ...
    def __ge__(self, other: Self) -> Self: ...
    def __gt__(self, other: Self) -> Self: ...
    def __invert__(self) -> Self: ...
    def __le__(self, other: Self) -> Self: ...
    def __lt__(self, other: Self) -> Self: ...
    def __mod__(self, other: Self) -> Self: ...
    def __mul__(self, other: Self) -> Self: ...
    def __ne__(self, other: Self) -> Self: ...  # type: ignore[override]
    def __or__(self, other: Self) -> Self: ...
    def __pow__(self, other: Self) -> Self: ...
    def __rfloordiv__(self, other: Self) -> Self: ...
    def __rmod__(self, other: Self) -> Self: ...
    def __rpow__(self, other: Self) -> Self: ...
    def __rsub__(self, other: Self) -> Self: ...
    def __rtruediv__(self, other: Self) -> Self: ...
    def __sub__(self, other: Self) -> Self: ...
    def __truediv__(self, other: Self) -> Self: ...

    def __narwhals_namespace__(self) -> CompliantNamespace[Any, Any]: ...

    def abs(self) -> Self: ...
    def alias(self, name: str) -> Self: ...
    def cast(self, dtype: IntoDType) -> Self: ...
    def clip(self, lower_bound: Self, upper_bound: Self) -> Self: ...
    def clip_lower(self, lower_bound: Self) -> Self: ...
    def clip_upper(self, upper_bound: Self) -> Self: ...
    def cum_count(self, *, reverse: bool) -> Self: ...
    def cum_max(self, *, reverse: bool) -> Self: ...
    def cum_min(self, *, reverse: bool) -> Self: ...
    def cum_prod(self, *, reverse: bool) -> Self: ...
    def cum_sum(self, *, reverse: bool) -> Self: ...
    def diff(self) -> Self: ...
    def drop_nulls(self) -> Self: ...
    def ewm_mean(
        self,
        *,
        com: float | None,
        span: float | None,
        half_life: float | None,
        alpha: float | None,
        adjust: bool,
        min_samples: int,
        ignore_nulls: bool,
    ) -> Self: ...
    def exp(self) -> Self: ...
    def sqrt(self) -> Self: ...
    def fill_nan(self, value: float | None) -> Self: ...
    def fill_null(
        self, value: Self | None, strategy: FillNullStrategy | None, limit: int | None
    ) -> Self: ...
    def is_between(
        self, lower_bound: Self, upper_bound: Self, closed: ClosedInterval
    ) -> Self:
        if closed == "left":
            return (self >= lower_bound) & (self < upper_bound)
        if closed == "right":
            return (self > lower_bound) & (self <= upper_bound)
        if closed == "none":
            return (self > lower_bound) & (self < upper_bound)
        return (self >= lower_bound) & (self <= upper_bound)

    def is_duplicated(self) -> Self:
        return ~self.is_unique()

    def is_finite(self) -> Self: ...
    def is_first_distinct(self) -> Self: ...
    def is_in(self, other: Any) -> Self: ...
    def is_last_distinct(self) -> Self: ...
    def is_nan(self) -> Self: ...
    def is_null(self) -> Self: ...
    def is_unique(self) -> Self: ...
    def log(self, base: float) -> Self: ...
    def mode(self, *, keep: ModeKeepStrategy) -> Self: ...
    def rank(self, method: RankMethod, *, descending: bool) -> Self: ...
    def replace_strict(
        self,
        default: Self | NoDefault,
        old: Sequence[Any],
        new: Sequence[Any],
        *,
        return_dtype: IntoDType | None,
    ) -> Self: ...
    def rolling_mean(
        self, window_size: int, *, min_samples: int, center: bool
    ) -> Self: ...
    def rolling_std(
        self, window_size: int, *, min_samples: int, center: bool, ddof: int
    ) -> Self: ...
    def rolling_sum(
        self, window_size: int, *, min_samples: int, center: bool
    ) -> Self: ...
    def rolling_var(
        self, window_size: int, *, min_samples: int, center: bool, ddof: int
    ) -> Self: ...
    def round(self, decimals: int) -> Self: ...
    def floor(self) -> Self: ...
    def ceil(self) -> Self: ...
    def shift(self, n: int) -> Self: ...
    def cos(self) -> Self: ...
    def sin(self) -> Self: ...
    def unique(self) -> Self: ...

    @property
    def str(self) -> StringNamespace[Self]: ...
    @property
    def dt(self) -> DateTimeNamespace[Self]: ...
    @property
    def cat(self) -> CatNamespace[Self]: ...
    @property
    def list(self) -> ListNamespace[Self]: ...
    @property
    def struct(self) -> StructNamespace[Self]: ...
