from _typeshed import MaybeNone
from collections.abc import Callable, Iterable
from typing import Any, Final, Generic, TypeVar, type_check_only
from typing_extensions import Self, TypeAlias, disjoint_base

__all__ = [
    "LUA_VERSION",
    "LUA_MAXINTEGER",
    "LUA_MININTEGER",
    "LuaRuntime",
    "LuaError",
    "LuaSyntaxError",
    "LuaMemoryError",
    "as_itemgetter",
    "as_attrgetter",
    "lua_type",
    "unpacks_lua_table",
    "unpacks_lua_table_method",
]

LUA_MAXINTEGER: Final[int]
LUA_MININTEGER: Final[int]
LUA_VERSION: Final[tuple[int, int]]

# cyfunction object
as_attrgetter: Callable[[object], object]
as_itemgetter: Callable[[object], object]

# cyfunction object
lua_type: Callable[[object], str | MaybeNone]

# cyfunction object as decorator
unpacks_lua_table: Callable[[Callable[..., Any]], Callable[..., Any]]
unpacks_lua_table_method: Callable[[Callable[..., Any]], Callable[..., Any]]

# inner classes

@type_check_only
class _LuaTable:
    def keys(self) -> Iterable[_LuaKey]: ...
    def values(self) -> Iterable[_LuaObject]: ...
    def items(self) -> Iterable[tuple[_LuaKey, _LuaObject]]: ...
    def __getitem__(self, key: _LuaKey) -> _LuaObject: ...
    def __setitem__(self, key: _LuaKey, value: _LuaObject) -> None: ...
    def __delitem__(self, key: _LuaKey) -> None: ...

# A Lua object can be a table or a primitive type. Because we have no way of
# knowing the actual type across languages, we simply use an Any for a Lua
# object.

# A previous version of this code had
#   _LuaObject: TypeAlias = _LuaTable | int | str | float | bool | None
# but that causes false type failures when running, e.g., `lua.globals()['foo']['bar']`
# (because `lua.globals()['foo']` is not known to be a nested table
_LuaKey: TypeAlias = str | int
_LuaObject: TypeAlias = Any

@type_check_only
class _LuaNoGC: ...

# classes

_bint = TypeVar("_bint", bool, int)

@disjoint_base
class FastRLock(Generic[_bint]):
    # @classmethod
    # def __init__(cls, /, *args: Any, **kwargs: Any) -> None: ...
    def acquire(self, blocking: _bint = True) -> _bint: ...
    def release(self) -> None: ...
    def __enter__(self) -> _bint: ...
    def __exit__(self, t: object, v: object, tb: object) -> None: ...

class LuaError(Exception): ...
class LuaSyntaxError(LuaError): ...
class LuaMemoryError(LuaError, MemoryError): ...

@disjoint_base
class LuaRuntime:
    lua_implementation: Final[str]
    lua_version: Final[tuple[int, int]]

    def __new__(cls, /, unpack_returned_tuples: bool) -> Self: ...
    # def add_pending_unref(self, ref: int) -> None: ...
    # def clean_up_pending_unrefs(self) -> int: ...
    def get_max_memory(self, total: bool = False) -> int | MaybeNone: ...
    def get_memory_used(self, total: bool = False) -> int | MaybeNone: ...
    # def reraise_on_exceptions(self) -> int: ...
    # def store_raised_exception(self, L: object, lua_error_msg: str) -> None: ...  # unannotated
    def eval(self, lua_code: str, *args: Any, name: str | None = None, mode: str | None = None) -> object: ...
    def execute(self, lua_code: str, *args: Any, name: str | None = None, mode: str | None = None) -> object: ...
    def compile(self, lua_code: str, name: str | None = None, mode: str | None = None) -> Callable[..., object]: ...
    def require(self, modulename: str) -> object: ...
    def globals(self) -> _LuaTable: ...
    def table(self, *items: Any, **kwargs: Any) -> _LuaTable: ...
    def table_from(self, *args: Any, recursive: bool = False) -> _LuaTable: ...
    def nogc(self) -> _LuaNoGC: ...
    def gccollect(self) -> None: ...
    def set_max_memory(self, max_memory: int, total: bool = False) -> None: ...
    def set_overflow_handler(self, overflow_handler: Callable[..., None]) -> None: ...
    # def register_py_object(self, cname: str, pyname: str, obj: object) -> int: ...
    # def init_python_lib(self, register_eval: bool, register_builtins: bool) -> int: ...
