# Copyright© 2025-2026 Gesellschaft zur Förderung der angewandten Forschung e.V.
# acting on behalf of its Fraunhofer Institut für Graphische Datenverarbeitung.
# Licensed under the EUPL. See LICENSE.txt.

from types import EllipsisType
from typing import Any, Protocol, Sequence, Self, TypeVar
from collections.abc import Buffer

ArrayLike = Any
DType = Any
Device = Any
#ArrayNamespace = Any

NestedSequence = Sequence[Any]

"""
class Device(Protocol):
    pass

class DType(Protocol):
    def __eq__(self, other: Any) -> bool: ...

class ArrayLike(Protocol):

    Slice = int | slice | EllipsisType | None | Sequence[int | slice | EllipsisType | Self | None] | Self
    Shape = tuple[int | None, ...]

    @property
    def device(self) -> Any: ...

    @property
    def dtype(self) -> Any: ...

    @property
    def shape(self) -> Shape: ...

    @property
    def ndim(self) -> int: ...

    @property
    def mT(self) -> Self: ...

    @property
    def T(self) -> Self: ...

    def __add__(self, other: int | float | complex | Self, /) -> Self: ...
    def __abs__(self) -> Self: ...
    def __and__(self, other: int | bool | Self, /) -> Self: ...
    def __array_namespace__(self,  api_version: str | None = None) -> ArrayNamespace: ...
    def __bool__(self) -> bool: ...
    def __complex__(self) -> complex: ...
    def __dlpack__(self) -> Any: ...
    def __dlpack_device__(self) -> Any: ...
    def __eq__(self, other: int | float | complex | bool | Self, /) -> Self: ... # type: ignore
    def __float__(self) -> float: ...
    def __floordiv__(self, other: int | float | Self, /) -> Self: ...
    def __ge__(self, other: int | float | Self, /) -> Self: ...
    def __getitem__(self, key: Slice, /) -> Self: ...
    def __gt__(self, other: int | float | Self, /) -> Self: ...
    def __index__(self, *args, **kwargs) -> int: ...
    def __int__(self) -> int: ...
    def __invert__(self) -> Self: ...
    def __le__(self, other: int | float | Self, /) -> Self: ...
    def __lshift__(self, other: int | Self, /) -> Self: ...
    def __lt__(self, other: int | float | Self, /) -> Self: ...
    def __matmul__(self, other: Self, /) -> Self: ...
    def __mod__(self, other: int | float | Self) -> Self: ...
    def __mul__(self, other: int | float | complex | Self, /) -> Self: ...
    def __ne__(self, other: int | float | complex | bool | Self, /) -> Self: ... # type: ignore
    def __neg__(self) -> Self: ...
    def __or__(self, other: int | bool | Self) -> Self: ...
    def __pos__(self) -> Self: ...
    def __pow__(self, other: int | float | complex | Self, /) -> Self: ...
    def __rshift__(self, other: int | Self, /) -> Self: ...
    def __setitem__(self, key: Slice, value: int | float | complex | bool | Self, /) -> None: ...
    def __sub__(self, other: int | float | complex | Self, /) -> Self: ...
    def __truediv__(self, other: int | float | complex | Self, /) -> Self: ...
    def __xor__(self, other: int | bool | Self) -> Self: ...

ArrayBound = TypeVar("ArrayBound", bound=ArrayLike)
"""

class Finfo(Protocol):
    @property
    def bits(self) -> int: ...
    @property
    def eps(self) -> float: ...
    @property
    def max(self) -> float: ...
    @property
    def min(self) -> float: ...
    @property
    def smallest_normal(self) -> float: ...
    @property
    def dtype(self) -> DType: ...

class Iinfo(Protocol):
    @property
    def bits(self) -> int: ...
    @property
    def max(self) -> int: ...
    @property
    def min(self) -> int: ...
    @property
    def dtype(self) -> DType: ...

class ArrayNamespaceInfo(Protocol):
    def capabilities(self) -> dict[str, Any]: ...
    def default_device(self) -> Device: ...
    def default_dtype(self, *args, **kwargs) -> DType: ...
    def devices(self) -> list[Device]: ...
    def dtypes(self, *args, **kwargs) -> dict[str, DType]: ...

class LinearAlgebraExtensions[T: ArrayLike](Protocol):
    def cholesky(self, *args, **kwargs) -> T: ...
    def cross(self, *args, **kwargs) -> T: ...
    def det(self, *args, **kwargs) -> T: ...
    def diagonal(self, *args, **kwargs) -> T: ...
    def eigh(self, *args, **kwargs) -> tuple[T, T]: ...
    def eigvalsh(self, *args, **kwargs) -> T: ...
    def inv(self, *args, **kwargs) -> T: ...
    def matmul(self, *args, **kwargs) -> T: ...
    def matrix_norm(self, *args, **kwargs) -> T: ...
    def matrix_power(self, *args, **kwargs) -> T: ...
    def matrix_rank(self, *args, **kwargs) -> T: ...
    def matrix_transpose(self, *args, **kwargs) -> T: ...
    def outer(self, *args, **kwargs) -> T: ...
    def pinv(self, *args, **kwargs) -> T: ...
    def qr(self, *args, **kwargs) -> tuple[T, T]: ...
    def slogdet(self, *args, **kwargs) -> tuple[T, T]: ...
    def solve(self, *args, **kwargs) -> T: ...
    def svd(self, *args, **kwargs) -> tuple[T, T, T]: ...
    def svdvals(self, *args, **kwargs) -> T: ...
    def tensordot(self, *args, **kwargs) -> T: ...
    def trace(self, *args, **kwargs) -> T: ...
    def vecdot(self, *args, **kwargs) -> T: ...
    def vector_norm(self, *args, **kwargs) -> T: ...


class ArrayNamespace[T: ArrayLike](Protocol):

    # Constants
    @property
    def e(self) -> float: ...
    @property
    def pi(self) -> float: ...
    @property
    def inf(self) -> float: ...
    @property
    def nan(self) -> float: ...
    @property
    def newaxis(self) -> None: ...

    @property
    def __array_api_version__(self) -> str: ...

    @property
    def linalg(self) -> LinearAlgebraExtensions: ...

    # Creation functions
    def arange(
            self,
            start: int | float, /,
            stop: int | float | None = None,
            step: int | float = 1, *,
            dtype: DType | None = None,
            device: Device | None = None
            ) -> T: ...
    def asarray(
            self,
            obj: T | bool | int | float | complex | NestedSequence | Buffer, /, *,
            dtype: DType | None = None,
            device: Device | None = None,
            copy: bool | None = None
            ) -> T: ...
    def empty(
            self,
            shape: int | tuple[int, ...], *,
            dtype: DType | None = None,
            device: Device | None = None
            ) -> T: ...
    def empty_like(
            self,
            x: T, /, *,
            dtype: DType | None = None,
            device: Device | None = None
            ) -> T: ...
    def eye(self, *args, **kwargs) -> T: ...
    def from_dlpack(self, *args, **kwargs) -> T: ...
    def full(self, *args, **kwargs) -> T: ...
    def full_like(self, *args, **kwargs) -> T: ...
    def linspace(self, *args, **kwargs) -> T: ...
    def meshgrid(self, *args, **kwargs) -> list[T]: ...
    def ones(self, *args, **kwargs) -> T: ...
    def ones_like(self, *args, **kwargs) -> T: ...
    def tril(self, *args, **kwargs) -> T: ...
    def triu(self, *args, **kwargs) -> T: ...
    def zeros(self, *args, **kwargs) -> T: ...
    def zeros_like(self, *args, **kwargs) -> T: ...

    # Data Type Functions
    def astype(self, *args, **kwargs) -> T: ...
    def can_cast(self, *args, **kwargs) -> bool: ...
    def finfo(self, *args, **kwargs) -> Finfo: ...
    def iinfo(self, *args, **kwargs) -> Iinfo: ...
    def isdtype(self, *args, **kwargs) -> bool: ...
    def result_type(self, *args, **kwargs) -> DType: ...

    # Element-wise functions
    def abs(self, *args, **kwargs) -> T: ...
    def acos(self, *args, **kwargs) -> T: ...
    def acosh(self, *args, **kwargs) -> T: ...
    def add(self, *args, **kwargs) -> T: ...
    def asin(self, *args, **kwargs) -> T: ...
    def asinh(self, *args, **kwargs) -> T: ...
    def atan(self, *args, **kwargs) -> T: ...
    def atan2(self, *args, **kwargs) -> T: ...
    def atanh(self, *args, **kwargs) -> T: ...
    def bitwise_and(self, *args, **kwargs) -> T: ...
    def bitwise_left_shift(self, *args, **kwargs) -> T: ...
    def bitwise_invert(self, *args, **kwargs) -> T: ...
    def bitwise_or(self, *args, **kwargs) -> T: ...
    def bitwise_right_shift(self, *args, **kwargs) -> T: ...
    def bitwise_xor(self, *args, **kwargs) -> T: ...
    def ceil(self, *args, **kwargs) -> T: ...
    def clip(self, *args, **kwargs) -> T: ...
    def conj(self, *args, **kwargs) -> T: ...
    def copysign(self, *args, **kwargs) -> T: ...
    def cos(self, *args, **kwargs) -> T: ...
    def cosh(self, *args, **kwargs) -> T: ...
    def divide(self, *args, **kwargs) -> T: ...
    def equal(self, *args, **kwargs) -> T: ...
    def exp(self, *args, **kwargs) -> T: ...
    def expm1(self, *args, **kwargs) -> T: ...
    def floor(self, *args, **kwargs) -> T: ...
    def floor_divide(self, *args, **kwargs) -> T: ...
    def greater(self, *args, **kwargs) -> T: ...
    def greater_equal(self, *args, **kwargs) -> T: ...
    def hypot(self, *args, **kwargs) -> T: ...
    def imag(self, *args, **kwargs) -> T: ...
    def isfinite(self, *args, **kwargs) -> T: ...
    def isinf(self, *args, **kwargs) -> T: ...
    def isnan(self, *args, **kwargs) -> T: ...
    def less(self, *args, **kwargs) -> T: ...
    def less_equal(self, *args, **kwargs) -> T: ...
    def log(self, *args, **kwargs) -> T: ...
    def log1p(self, *args, **kwargs) -> T: ...
    def log2(self, *args, **kwargs) -> T: ...
    def log10(self, *args, **kwargs) -> T: ...
    def logaddexp(self, *args, **kwargs) -> T: ...
    def logical_and(self, *args, **kwargs) -> T: ...
    def logical_not(self, *args, **kwargs) -> T: ...
    def logical_or(self, *args, **kwargs) -> T: ...
    def logical_xor(self, *args, **kwargs) -> T: ...
    def maximum(self, *args, **kwargs) -> T: ...
    def minimum(self, *args, **kwargs) -> T: ...
    def multiply(self, *args, **kwargs) -> T: ...
    def negative(self, *args, **kwargs) -> T: ...
    def nextafter(self, *args, **kwargs) -> T: ...
    def not_equal(self, *args, **kwargs) -> T: ...
    def positive(self, *args, **kwargs) -> T: ...
    def pow(self, *args, **kwargs) -> T: ...
    def real(self, *args, **kwargs) -> T: ...
    def reciprocal(self, *args, **kwargs) -> T: ...
    def remainder(self, *args, **kwargs) -> T: ...
    def round(self, *args, **kwargs) -> T: ...
    def sign(self, *args, **kwargs) -> T: ...
    def signbit(self, *args, **kwargs) -> T: ...
    def sin(self, *args, **kwargs) -> T: ...
    def sinh(self, *args, **kwargs) -> T: ...
    def square(self, *args, **kwargs) -> T: ...
    def sqrt(self, *args, **kwargs) -> T: ...
    def subtract(self, *args, **kwargs) -> T: ...
    def tan(self, *args, **kwargs) -> T: ...
    def tanh(self, *args, **kwargs) -> T: ...
    def trunc(self, *args, **kwargs) -> T: ...

    # Indexing Functions
    def take(self, *args, **kwargs) -> T: ...
    def take_along_axis(self, *args, **kwargs) -> T: ...

    # Inspection
    def __array_namespace_info__(self) -> ArrayNamespaceInfo: ...

    # Linear Algebra Functions
    def matmul(self, *args, **kwargs) -> T: ...
    def matrix_transpose(self, *args, **kwargs) -> T: ...
    def tensordot(self, *args, **kwargs) -> T: ...
    def vecdot(self, *args, **kwargs) -> T: ...

    # Manipulation Functions
    def broadcast_arrays(self, *args, **kwargs) -> list[T]: ...
    def broadcast_to(self, *args, **kwargs) -> T: ...
    def concat(self, *args, **kwargs) -> T: ...
    def expand_dims(self, *args, **kwargs) -> T: ...
    def flip(self, *args, **kwargs) -> T: ...
    def moveaxis(self, *args, **kwargs) -> T: ...
    def permute_dims(self, *args, **kwargs) -> T: ...
    def repeat(self, *args, **kwargs) -> T: ...
    def reshape(self, *args, **kwargs) -> T: ...
    def roll(self, *args, **kwargs) -> T: ...
    def squeeze(self, *args, **kwargs) -> T: ...
    def stack(self, arrays: tuple[T, ...] | list[T], /, *, axis: int = 0) -> T: ...
    def tile(self, *args, **kwargs) -> T: ...
    def unstack(self, *args, **kwargs) -> tuple[T, ...]: ...

    # Searching Functions
    def argmax(self, *args, **kwargs) -> T: ...
    def argmin(self, *args, **kwargs) -> T: ...
    def count_nonzero(self, *args, **kwargs) -> T: ...
    def nonzero(self, *args, **kwargs) -> tuple[T, ...]: ...
    def searchsorted(self, *args, **kwargs) -> T: ...
    def where(self, *args, **kwargs) -> T: ...

    # Set Functions
    def unique_all(self, *args, **kwargs) -> tuple[T, T, T, T]: ...
    def unique_counts(self, *args, **kwargs) -> tuple[T, T]: ...
    def unique_inverse(self, *args, **kwargs) -> tuple[T, T]: ...
    def unique_values(self, *args, **kwargs) -> T: ...

    # Sorting Functions
    def argsort(self, *args, **kwargs) -> T: ...
    def sort(self, *args, **kwargs) -> T: ...

    # Statistical Functions
    def cumulativ_prod(self, *args, **kwargs) -> T: ...
    def cumulativ_sum(self, *args, **kwargs) -> T: ...
    def max(self, *args, **kwargs) -> T: ...
    def mean(self, *args, **kwargs) -> T: ...
    def min(self, *args, **kwargs) -> T: ...
    def prod(self, *args, **kwargs) -> T: ...
    def std(self, *args, **kwargs) -> T: ...
    def sum(self, *args, **kwargs) -> T: ...
    def var(self, *args, **kwargs) -> T: ...

    # Utility Functions
    def all(self, *args, **kwargs) -> bool: ...
    def any(self, *args, **kwargs) -> bool: ...
    def diff(self, *args, **kwargs) -> T: ...
