from typing import Any, Callable, Hashable, Literal, Protocol, Self, override

import numpy as np
import numpy.typing as npt
from pandas._typing import ArrayLike, AxisInt, FloatFormatType
from pandas.core.arrays import ExtensionArray
from pandas.core.indexes.api import Index
from pandas.core.internals.base import DataManager, SingleDataManager

class ArrayApplyFunc[R](Protocol):
    def __call__(self, arr: npt.NDArray[Any], **kwds: Any) -> R: ...

class BaseArrayManager(DataManager):
    """
    Core internal data structure to implement DataFrame and Series.

    Alternative to the BlockManager, storing a list of 1D arrays instead of
    Blocks.

    This is *not* a public API class

    Parameters
    ----------
    arrays : Sequence of arrays
    axes : Sequence of Index
    verify_integrity : bool, default True

    """

    __slots__: list[str] = ["_axes", "arrays"]

    arrays: list[np.ndarray | ExtensionArray]
    _axes: list[Index]

    def __init__(
        self, arrays: list[np.ndarray | ExtensionArray], axes: list[Index], verify_integrity: bool = True
    ) -> None: ...
    def make_empty(self, axes: list[Index] | None = None) -> Self: ...
    @property
    def items(self) -> Index: ...
    @property
    def axes(self) -> list[Index]: ...
    @property
    def shape_proper(self) -> tuple[int, ...]: ...
    @staticmethod
    def _normalize_axis(axis: AxisInt) -> int: ...
    def set_axis(self, axis: AxisInt, new_labels: Index) -> None: ...
    def get_dtypes(self) -> npt.NDArray[np.object_]: ...
    def add_references(self, mgr: BaseArrayManager) -> None: ...
    @override
    def __getstate__(self) -> tuple[list[npt.NDArray[Any] | ExtensionArray], list[Index]]: ...
    def __setstate__(self, state: tuple[list[npt.NDArray[Any] | ExtensionArray], list[Index]]) -> None: ...
    @override
    def __repr__(self) -> str: ...
    def apply(self, f: str | ArrayApplyFunc[ArrayLike], align_keys: list[str] | None = None, **kwargs: Any) -> Self: ...
    def apply_with_block(self, f: str, align_keys: list[str] | None = None, **kwargs: Any) -> Self: ...
    def setitem(self, indexer: Any, value: Any, warn: bool = True) -> Self: ...
    def diff(self, n: int) -> Self: ...
    def astype(self, dtype: npt.DTypeLike, copy: bool | None = False, errors: str = "raise") -> Self: ...
    def convert(self, copy: bool | None) -> Self: ...
    def get_values_for_csv(
        self,
        *,
        float_format: FloatFormatType,
        date_format: Any,
        decimal: str,
        na_rep: str = "nan",
        quoting: int | None = None,
    ) -> Self: ...
    @property
    def any_extension_types(self) -> bool: ...
    @property
    def is_view(self) -> bool: ...
    @property
    def is_single_block(self) -> bool: ...
    def _get_data_subset(self, predicate: Callable[[npt.NDArray[Any]], bool]) -> Self: ...
    def get_bool_data(self, copy: bool = False) -> Self: ...
    def get_numeric_data(self, copy: bool = False) -> Self: ...
    def copy(self, deep: bool | Literal["all"] | None = True) -> Self: ...
    def reindex_indexer(
        self,
        new_axis,
        indexer,
        axis: AxisInt,
        fill_value=None,
        allow_dups: bool = False,
        copy: bool | None = True,
        only_slice: bool = False,
        use_na_proxy: bool = False,
    ) -> Self: ...
    def _reindex_indexer(
        self,
        new_axis,
        indexer: npt.NDArray[np.intp] | None,
        axis: AxisInt,
        fill_value=None,
        allow_dups: bool = False,
        copy: bool | None = True,
        use_na_proxy: bool = False,
    ) -> Self: ...
    def take(self, indexer: npt.NDArray[np.intp], axis: AxisInt = 1, verify: bool = True) -> Self: ...
    def _make_na_array(self, fill_value=None, use_na_proxy: bool = False): ...
    def _equal_values(self, other) -> bool: ...

class ArrayManager(BaseArrayManager):
    @property
    def ndim(self) -> Literal[2]: ...
    def __init__(
        self, arrays: list[np.ndarray | ExtensionArray], axes: list[Index], verify_integrity: bool = True
    ) -> None: ...
    def _verify_integrity(self) -> None: ...
    def fast_xs(self, loc: int) -> SingleArrayManager: ...
    def get_slice(self, slobj: slice, axis: AxisInt = 0) -> ArrayManager: ...
    def iget(self, i: int) -> SingleArrayManager: ...
    def iget_values(self, i: int) -> ArrayLike: ...
    @property
    def column_arrays(self) -> list[ArrayLike]: ...
    def iset(self, loc: int | slice | np.ndarray, value: ArrayLike, inplace: bool = False, refs=None) -> None: ...
    def column_setitem(self, loc: int, idx: int | slice | np.ndarray, value, inplace_only: bool = False) -> None: ...
    def insert(self, loc: int, item: Hashable, value: ArrayLike, refs=None) -> None: ...
    def idelete(self, indexer) -> ArrayManager: ...
    def grouped_reduce(self, func: Callable) -> Self: ...
    def reduce(self, func: Callable) -> Self: ...
    def operate_blockwise(self, other: ArrayManager, array_op) -> ArrayManager: ...
    def quantile(
        self, *, qs: Index, transposed: bool = False, interpolation: QuantileInterpolation = "linear"
    ) -> ArrayManager: ...
    def unstack(self, unstacker, fill_value) -> ArrayManager: ...
    def as_array(self, dtype=None, copy: bool = False, na_value: object = lib.no_default) -> np.ndarray: ...
    @classmethod
    def concat_horizontal(cls, mgrs: list[Self], axes: list[Index]) -> Self: ...
    @classmethod
    def concat_vertical(cls, mgrs: list[Self], axes: list[Index]) -> Self: ...

class SingleArrayManager(BaseArrayManager, SingleDataManager):
    __slots__: list[str] = ["_axes", "arrays"]

    arrays: list[np.ndarray | ExtensionArray]
    _axes: list[Index]

    @property
    def ndim(self) -> Literal[1]: ...
    def __init__(
        self, arrays: list[np.ndarray | ExtensionArray], axes: list[Index], verify_integrity: bool = True
    ) -> None: ...
    def _verify_integrity(self) -> None: ...
    @staticmethod
    @override
    def _normalize_axis(axis: AxisInt) -> AxisInt: ...
    @override
    def make_empty(self, axes: list[Index] | None = None) -> Self: ...
    @classmethod
    def from_array(cls, array: npt.NDArray[Any] | ExtensionArray, index: Index) -> SingleArrayManager: ...
    @property
    @override
    def axes(self) -> list[Index]: ...
    @property
    def index(self) -> Index: ...
    @property
    def dtype(self): ...
    def external_values(self): ...
    def internal_values(self): ...
    def array_values(self): ...
    @property
    def _can_hold_na(self) -> bool: ...
    @property
    @override
    def is_single_block(self) -> bool: ...
    def fast_xs(self, loc: int) -> SingleArrayManager: ...
    def get_slice(self, slobj: slice, axis: AxisInt = 0) -> SingleArrayManager: ...
    def get_rows_with_mask(self, indexer: npt.NDArray[np.bool_]) -> SingleArrayManager: ...
    @override
    def apply(self, func: str | Callable[[Any], npt.NDArray[Any] | ExtensionArray], **kwargs: Any) -> Self: ...  # pyright: ignore[reportIncompatibleMethodOverride]
    @override
    def setitem(self, indexer: npt.NDArray[np.integer], value: Any, warn: bool = True) -> SingleArrayManager: ...
    def idelete(self, indexer: npt.NDArray[np.integer]) -> SingleArrayManager: ...
    @override
    def _get_data_subset(self, predicate: Callable[[npt.NDArray[Any]], bool]) -> SingleArrayManager: ...
    def set_values(self, values: ArrayLike) -> None: ...
    def to_2d_mgr(self, columns: Index) -> ArrayManager: ...
