import typing

if typing.TYPE_CHECKING:
    from .visitor import CxxVisitor  # pragma: nocover

from .errors import CxxParseError
from .lexer import LexToken, Location
from .types import ClassDecl, NamespaceDecl, Value


class ParsedTypeModifiers(typing.NamedTuple):
    vars: typing.Dict[str, LexToken]  # only found on variables
    both: typing.Dict[str, LexToken]  # found on either variables or functions
    meths: typing.Dict[str, LexToken]  # only found on methods
    #: For C++20 ``explicit(<expr>)``: the constant expression inside the
    #: parens (omitting the parens themselves). ``None`` if absent or if
    #: ``explicit`` was used as a bare keyword.
    explicit_value: typing.Optional[Value] = None
    #: ``friend`` if encountered while parsing declaration specifiers.
    friend: typing.Optional[LexToken] = None

    def validate(
        self, *, var_ok: bool, meth_ok: bool, msg: str, friend_ok: bool = False
    ) -> None:
        # Almost there! Do any checks the caller asked for
        if not var_ok and self.vars:
            for tok in self.vars.values():
                raise CxxParseError(f"{msg}: unexpected '{tok.value}'")

        if not meth_ok and self.meths:
            for tok in self.meths.values():
                raise CxxParseError(f"{msg}: unexpected '{tok.value}'")

        if not meth_ok and not var_ok and self.both:
            for tok in self.both.values():
                raise CxxParseError(f"{msg}: unexpected '{tok.value}'")

        if not friend_ok and self.friend is not None:
            raise CxxParseError(f"{msg}: unexpected '{self.friend.value}'")


#: custom user data for this state type
T = typing.TypeVar("T")

#: type of custom user data for a parent state
PT = typing.TypeVar("PT")


class BaseState(typing.Generic[T, PT]):
    #: Uninitialized user data available for use by visitor implementations. You
    #: should set this in a ``*_start`` method.
    user_data: T

    #: parent state
    parent: typing.Optional["State"]

    #: Approximate location that the parsed element was found at
    location: Location

    #: internal detail used by parser
    _prior_visitor: "CxxVisitor"

    def __init__(self, parent: typing.Optional["State"], location: Location) -> None:
        self.parent = parent
        self.location = location

    def _finish(self, visitor: "CxxVisitor") -> None:
        pass


class ExternBlockState(BaseState[T, PT]):
    parent: "NonClassBlockState"

    #: The linkage for this extern block
    linkage: str

    def __init__(
        self, parent: "NonClassBlockState", location: Location, linkage: str
    ) -> None:
        super().__init__(parent, location)
        self.linkage = linkage

    def _finish(self, visitor: "CxxVisitor") -> None:
        visitor.on_extern_block_end(self)


class NamespaceBlockState(BaseState[T, PT]):
    parent: "NonClassBlockState"

    #: The incremental namespace for this block
    namespace: NamespaceDecl

    def __init__(
        self,
        parent: typing.Optional["NonClassBlockState"],
        location: Location,
        namespace: NamespaceDecl,
    ) -> None:
        super().__init__(parent, location)
        self.namespace = namespace

    def _finish(self, visitor: "CxxVisitor") -> None:
        visitor.on_namespace_end(self)


class ClassBlockState(BaseState[T, PT]):
    parent: "State"

    #: class decl block being processed
    class_decl: ClassDecl

    #: Current access level for items encountered
    access: str

    #: Currently parsing as a typedef
    typedef: bool

    #: modifiers to apply to following variables
    mods: ParsedTypeModifiers

    def __init__(
        self,
        parent: typing.Optional["State"],
        location: Location,
        class_decl: ClassDecl,
        access: str,
        typedef: bool,
        mods: ParsedTypeModifiers,
    ) -> None:
        super().__init__(parent, location)
        self.class_decl = class_decl
        self.access = access
        self.typedef = typedef
        self.mods = mods

    def _set_access(self, access: str) -> None:
        self.access = access

    def _finish(self, visitor: "CxxVisitor") -> None:
        visitor.on_class_end(self)


State = typing.Union[
    NamespaceBlockState[T, PT], ExternBlockState[T, PT], ClassBlockState[T, PT]
]
NonClassBlockState = typing.Union[ExternBlockState[T, PT], NamespaceBlockState[T, PT]]
