# Copyright (c) 2017-2026 Juancarlo Añez (apalala@gmail.com)
# SPDX-License-Identifier: BSD-4-Clause
from __future__ import annotations

import inspect
from collections.abc import Callable
from contextlib import AbstractContextManager
from functools import cache
from typing import Any, Protocol, runtime_checkable

from ..exceptions import FailedParse
from ..input import Cursor, Text
from .ast import AST
from .infos import RuleInfo
from .state import ParseState, ParseStateStack


type Func = Callable[[Ctx], Any]


@cache
def is_func(func: Callable) -> bool:
    declared = inspect.signature(func).parameters
    if len(declared) == 1:
        p, *_ = declared.values()
        return p.annotation == 'Ctx'
    # if len(declared) == 2:
    #     p1, p2, *_ = declared.values()
    #     return p1.name == 'self' and p2.annotation == 'Ctx'
    return False


@runtime_checkable
class CanParse(Protocol):
    def parse(
        self,
        text: str | Text,
        /,
        *,
        start: str | None = None,
        config: Any = None,
        asmodel: bool = False,
        **settings: Any,
    ) -> Any: ...


@runtime_checkable
class Ctx(CanParse, Protocol):
    states: ParseStateStack

    @property
    def ast(self) -> AST: ...

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

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

    @property
    def state(self) -> ParseState: ...
    def statescope(self, *, merge: bool = True) -> Any: ...

    @property
    def cursor(self) -> Cursor: ...
    @property
    def callstack(self) -> list[RuleInfo]: ...
    def heartbeat(self) -> bool: ...
    def newexcept(
        self,
        msg: Any,
        excls: type[FailedParse] = FailedParse,
    ) -> FailedParse: ...

    def call(self, ri: RuleInfo) -> Any: ...
    def find_rule(self, name: str) -> Func: ...

    def alert(self, message: str, level: int) -> None: ...
    def choice(self) -> Any: ...
    def constant(self, literal: Any) -> Any: ...
    def cut(self) -> None: ...
    def define(self, keys: list[str], addkeys: list[str] | None = None) -> None: ...
    def dot(self) -> Any: ...
    def eofcheck(self) -> None: ...
    def eolcheck(self) -> None: ...
    def empty(self) -> list: ...
    def fail(self): ...
    def gatheropt(self) -> Any: ...
    def gatherplus(self) -> Any: ...
    def group(self) -> Any: ...
    def groupexp(self, exp: Func) -> Any: ...
    def skipgroup(self) -> Any: ...
    def if_(self) -> Any: ...
    def ifnot_(self) -> Any: ...
    def joinopt(self) -> Any: ...
    def joinplus(self) -> Any: ...
    def joinleft(self) -> Any: ...
    def joinright(self) -> Any: ...
    def loopopt(self) -> Any: ...
    def loopplus(self) -> Any: ...
    def nameset(self, name: str) -> Any: ...
    def nameadd(self, name: str) -> Any: ...
    def result(self) -> Any: ...
    def resultadd(self) -> Any: ...
    def option(self) -> Any: ...
    def optional(self) -> Any: ...
    def token(self, token: str) -> str: ...
    def pattern(self, pattern: str) -> Any: ...
    def matchname(self) -> str | None: ...
    def matchint(self) -> int | None: ...
    def matchuint(self) -> int | None: ...
    def matchfloat(self) -> float | None: ...
    def matchbool(self) -> float | None: ...
    def skipto(self) -> Any: ...
    def skip_to(self, exp: Func) -> Any: ...
    def void(self) -> Any: ...

    def closure(
        self,
        exp: Func,
        sep: Func | None = None,
        omitsep: bool = False,
    ) -> Any: ...
    def positive_closure(
        self,
        exp: Func,
        sep: Func | None = None,
        omitsep: bool = False,
    ) -> Any: ...
    def join(self, exp: Func, sep: Func) -> Any: ...
    def positive_join(self, exp: Func, sep: Func) -> Any: ...
    def left_join(self, exp: Func, sep: Func) -> Any: ...
    def right_join(self, exp: Func, sep: Func) -> Any: ...
    def gather(self, exp: Func, sep: Func) -> Any: ...
    def positive_gather(self, exp: Func, sep: Func) -> Any: ...
    def isolate(self, exp: Func) -> Any: ...
    def expcall(self, exp: Func) -> Any: ...

    def bound(
        self,
        text: str | Text,
        /,
        *,
        start: str | None = None,
        config: Any = None,
        asmodel: bool = False,
        **settings: Any,
    ) -> AbstractContextManager[Ctx]: ...
