import ctypes as ct
import re
from collections.abc import Callable, Iterable
from typing import Any, Final, Generic, Self, overload
from typing_extensions import TypeVar

import numpy as np
import numpy.typing as npt
from numpy.ctypeslib import c_intp

_PT_co = TypeVar("_PT_co", bound=int | None, default=None, covariant=True)

###

format_re: Final[re.Pattern[str]] = ...
sep_re: Final[re.Pattern[str]] = ...
space_re: Final[re.Pattern[str]] = ...

###

# TODO: Let the likes of `shape_as` and `strides_as` return `None`
# for 0D arrays once we've got shape-support

class _ctypes(Generic[_PT_co]):
    @overload
    def __init__(self: _ctypes[None], /, array: npt.NDArray[Any], ptr: None = None) -> None: ...
    @overload
    def __init__(self, /, array: npt.NDArray[Any], ptr: _PT_co) -> None: ...

    #
    @property
    def data(self) -> _PT_co: ...
    @property
    def shape(self) -> ct.Array[c_intp]: ...
    @property
    def strides(self) -> ct.Array[c_intp]: ...
    @property
    def _as_parameter_(self) -> ct.c_void_p: ...

    #
    def data_as[CastT: ct._CanCastTo](self, /, obj: type[CastT]) -> CastT: ...
    def shape_as[CT: ct._CData](self, /, obj: type[CT]) -> ct.Array[CT]: ...
    def strides_as[CT: ct._CData](self, /, obj: type[CT]) -> ct.Array[CT]: ...

class dummy_ctype[T_co]:
    _cls: type[T_co]

    def __init__(self, /, cls: type[T_co]) -> None: ...
    def __eq__(self, other: Self, /) -> bool: ...  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride]
    def __ne__(self, other: Self, /) -> bool: ...  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride]
    def __mul__(self, other: object, /) -> Self: ...
    def __call__(self, /, *other: object) -> T_co: ...

def array_ufunc_errmsg_formatter(dummy: object, ufunc: np.ufunc, method: str, *inputs: object, **kwargs: object) -> str: ...
def array_function_errmsg_formatter(public_api: Callable[..., object], types: Iterable[str]) -> str: ...
def npy_ctypes_check(cls: type) -> bool: ...
