from collections.abc import (
    Hashable,
    Sequence,
)
import datetime as dt
from typing import (
    Literal,
    TypeAlias,
    overload,
    type_check_only,
)

import numpy as np
import pandas as pd
from pandas import Index
from pandas._stubs_only import (
    OrderableScalarT,
    OrderableT,
    OrderableTimesT,
)
from pandas.core.indexes.extension import ExtensionIndex

from pandas._libs.interval import (
    Interval as Interval,
    IntervalMixin,
)
from pandas._libs.tslibs.timedeltas import Timedelta
from pandas._typing import (
    DatetimeLike,
    DtypeArg,
    Frequency,
    IntervalClosedType,
    IntervalT,
    Label,
    MaskType,
    np_1darray_bool,
    np_ndarray_anyint,
    np_ndarray_bool,
    np_ndarray_dt,
    np_ndarray_int64,
    np_ndarray_intp,
    np_ndarray_td,
    npt,
)

from pandas.core.dtypes.dtypes import IntervalDtype as IntervalDtype

_EdgesInt: TypeAlias = (
    Sequence[int]
    | np_ndarray_int64
    | npt.NDArray[np.int32]
    | np_ndarray_intp
    | pd.Series[int]
    | Index[int]
)
_EdgesFloat: TypeAlias = (
    Sequence[float] | npt.NDArray[np.float64] | pd.Series[float] | Index[float]
)
_EdgesTimestamp: TypeAlias = (
    Sequence[DatetimeLike] | np_ndarray_dt | pd.Series[pd.Timestamp] | pd.DatetimeIndex
)
_EdgesTimedelta: TypeAlias = (
    Sequence[pd.Timedelta] | np_ndarray_td | pd.Series[pd.Timedelta] | pd.TimedeltaIndex
)
_TimestampLike: TypeAlias = pd.Timestamp | np.datetime64 | dt.datetime
_TimedeltaLike: TypeAlias = pd.Timedelta | np.timedelta64 | dt.timedelta

@type_check_only
class _LengthDescriptor:
    @overload
    def __get__(
        self,
        instance: IntervalIndex[Interval[OrderableScalarT]],
        owner: type[IntervalIndex],
    ) -> Index[OrderableScalarT]: ...
    @overload
    def __get__(
        self,
        instance: IntervalIndex[Interval[OrderableTimesT]],
        owner: type[IntervalIndex],
    ) -> Index[Timedelta]: ...

@type_check_only
class _MidDescriptor:
    @overload
    def __get__(
        self,
        instance: IntervalIndex[Interval[int]],
        owner: type[IntervalIndex],
    ) -> Index[float]: ...
    @overload
    def __get__(
        self,
        instance: IntervalIndex[Interval[OrderableT]],
        owner: type[IntervalIndex],
    ) -> Index[OrderableT]: ...

class IntervalIndex(ExtensionIndex[IntervalT, np.object_], IntervalMixin):
    closed: IntervalClosedType

    def __new__(
        cls,
        data: Sequence[IntervalT],
        closed: IntervalClosedType | None = None,
        dtype: IntervalDtype | None = None,
        copy: bool = False,
        name: Hashable = None,
        verify_integrity: bool = True,
    ) -> IntervalIndex[IntervalT]: ...
    @overload
    @classmethod
    def from_breaks(  # pyright: ignore[reportOverlappingOverload]
        cls,
        breaks: _EdgesInt,
        closed: IntervalClosedType | None = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[int]]:
        """
classmethod(function) -> method

Convert a function to be a class method.

A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:

  class C:
      @classmethod
      def f(cls, arg1, arg2, ...):
          ...

It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()).  The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.

Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
        """
        pass
    @overload
    @classmethod
    def from_breaks(
        cls,
        breaks: _EdgesFloat,
        closed: IntervalClosedType | None = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[float]]: ...
    @overload
    @classmethod
    def from_breaks(
        cls,
        breaks: _EdgesTimestamp,
        closed: IntervalClosedType | None = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[pd.Timestamp]]: ...
    @overload
    @classmethod
    def from_breaks(
        cls,
        breaks: _EdgesTimedelta,
        closed: IntervalClosedType | None = None,
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[pd.Timedelta]]: ...
    @overload
    @classmethod
    def from_arrays(  # pyright: ignore[reportOverlappingOverload]
        cls,
        left: _EdgesInt,
        right: _EdgesInt,
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[int]]:
        """
classmethod(function) -> method

Convert a function to be a class method.

A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:

  class C:
      @classmethod
      def f(cls, arg1, arg2, ...):
          ...

It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()).  The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.

Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
        """
        pass
    @overload
    @classmethod
    def from_arrays(
        cls,
        left: _EdgesFloat,
        right: _EdgesFloat,
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[float]]: ...
    @overload
    @classmethod
    def from_arrays(
        cls,
        left: _EdgesTimestamp,
        right: _EdgesTimestamp,
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[pd.Timestamp]]: ...
    @overload
    @classmethod
    def from_arrays(
        cls,
        left: _EdgesTimedelta,
        right: _EdgesTimedelta,
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[Interval[pd.Timedelta]]: ...
    @overload
    @classmethod
    def from_tuples(  # pyright: ignore[reportOverlappingOverload]
        cls,
        data: Sequence[tuple[int, int]],
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[pd.Interval[int]]:
        """
classmethod(function) -> method

Convert a function to be a class method.

A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:

  class C:
      @classmethod
      def f(cls, arg1, arg2, ...):
          ...

It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()).  The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.

Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
        """
        pass
    # Ignore misc here due to intentional overlap between int and float
    @overload
    @classmethod
    def from_tuples(
        cls,
        data: Sequence[tuple[float, float]],
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[pd.Interval[float]]: ...
    @overload
    @classmethod
    def from_tuples(
        cls,
        data: Sequence[
            tuple[pd.Timestamp, pd.Timestamp]
            | tuple[dt.datetime, dt.datetime]
            | tuple[np.datetime64, np.datetime64]
        ],
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[pd.Interval[pd.Timestamp]]: ...
    @overload
    @classmethod
    def from_tuples(
        cls,
        data: Sequence[
            tuple[pd.Timedelta, pd.Timedelta]
            | tuple[dt.timedelta, dt.timedelta]
            | tuple[np.timedelta64, np.timedelta64]
        ],
        closed: IntervalClosedType = "right",
        name: Hashable = None,
        copy: bool = False,
        dtype: IntervalDtype | None = None,
    ) -> IntervalIndex[pd.Interval[pd.Timedelta]]: ...
    def to_tuples(self, na_tuple: bool = True) -> pd.Index: ...
    @overload
    def __contains__(self, key: IntervalT) -> bool: ...  # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload]
    @overload
    def __contains__(self, key: object) -> Literal[False]: ...
    def astype(self, dtype: DtypeArg, copy: bool = True) -> IntervalIndex: ...  # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-override]  # ty: ignore[invalid-method-override]
    @property
    def inferred_type(self) -> str: ...
    def memory_usage(self, deep: bool = False) -> int: ...
    @property
    def is_overlapping(self) -> bool: ...
    def get_loc(self, key: Label) -> int | slice | np_1darray_bool: ...
    @property
    def left(self: IntervalIndex[Interval[OrderableT]]) -> Index[OrderableT]: ...
    @property
    def right(self: IntervalIndex[Interval[OrderableT]]) -> Index[OrderableT]: ...
    mid = _MidDescriptor()
    length = _LengthDescriptor()
    @overload  # type: ignore[override]
    def __getitem__(  # pyrefly: ignore[bad-override]
        self,
        idx: (
            slice
            | np_ndarray_anyint
            | Sequence[int]
            | Index
            | MaskType
            | np_ndarray_bool
        ),
    ) -> IntervalIndex[IntervalT]: ...
    @overload
    def __getitem__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, idx: int
    ) -> IntervalT: ...
    @overload  # type: ignore[override]
    def __gt__(
        self, other: IntervalT | IntervalIndex[IntervalT]
    ) -> np_1darray_bool: ...
    @overload
    def __gt__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: pd.Series[IntervalT]
    ) -> pd.Series[bool]: ...
    @overload  # type: ignore[override]
    def __ge__(
        self, other: IntervalT | IntervalIndex[IntervalT]
    ) -> np_1darray_bool: ...
    @overload
    def __ge__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: pd.Series[IntervalT]
    ) -> pd.Series[bool]: ...
    @overload  # type: ignore[override]
    def __le__(
        self, other: IntervalT | IntervalIndex[IntervalT]
    ) -> np_1darray_bool: ...
    @overload
    def __le__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: pd.Series[IntervalT]
    ) -> pd.Series[bool]: ...
    @overload  # type: ignore[override]
    def __lt__(
        self, other: IntervalT | IntervalIndex[IntervalT]
    ) -> np_1darray_bool: ...
    @overload
    def __lt__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: pd.Series[IntervalT]
    ) -> pd.Series[bool]: ...
    @overload  # type: ignore[override]
    def __eq__(self, other: IntervalT | IntervalIndex[IntervalT]) -> np_1darray_bool: ...  # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] # pyrefly: ignore[bad-override]
    @overload
    def __eq__(self, other: pd.Series[IntervalT]) -> pd.Series[bool]: ...  # type: ignore[overload-overlap]
    @overload
    def __eq__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: object
    ) -> Literal[False]: ...
    @overload  # type: ignore[override]
    def __ne__(self, other: IntervalT | IntervalIndex[IntervalT]) -> np_1darray_bool: ...  # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] # pyrefly: ignore[bad-override]
    @overload
    def __ne__(self, other: pd.Series[IntervalT]) -> pd.Series[bool]: ...  # type: ignore[overload-overlap]
    @overload
    def __ne__(  # pyright: ignore[reportIncompatibleMethodOverride] # ty: ignore[invalid-method-override]
        self, other: object
    ) -> Literal[True]: ...

# misc here because int and float overlap but interval has distinct types
# int gets hit first and so the correct type is returned
@overload
def interval_range(  # pyright: ignore[reportOverlappingOverload]
    start: int | None = None,
    end: int | None = None,
    periods: int | None = None,
    freq: int | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[int]]: ...
@overload
def interval_range(
    start: float | None = None,
    end: float | None = None,
    periods: int | None = None,
    freq: int | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[float]]: ...
@overload
def interval_range(
    start: _TimestampLike,
    end: _TimestampLike | None = None,
    periods: int | None = None,
    freq: Frequency | dt.timedelta | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timestamp]]: ...
@overload
def interval_range(
    start: None = None,
    *,
    end: _TimestampLike,
    periods: int | None = None,
    freq: Frequency | dt.timedelta | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timestamp]]: ...
@overload
def interval_range(
    start: None,
    end: _TimestampLike,
    periods: int,
    freq: Frequency | dt.timedelta,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timestamp]]: ...
@overload
def interval_range(
    start: _TimedeltaLike,
    end: _TimedeltaLike | None = None,
    periods: int | None = None,
    freq: Frequency | dt.timedelta | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timedelta]]: ...
@overload
def interval_range(
    start: None,
    end: _TimedeltaLike,
    periods: int,
    freq: Frequency | dt.timedelta,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timedelta]]: ...
@overload
def interval_range(
    start: None = None,
    *,
    end: _TimedeltaLike,
    periods: int | None = None,
    freq: Frequency | dt.timedelta | None = None,
    name: Hashable = None,
    closed: IntervalClosedType = "right",
) -> IntervalIndex[Interval[pd.Timedelta]]: ...
