import re
import sys
from typing import (
    Any,
    Callable,
    ClassVar,
    Dict,
    Generic,
    Iterable,
    List,
    Match,
    MutableMapping,
    Optional,
    Pattern,
    Set,
    Tuple,
    Type,
    TypeVar,
    Union,
    cast,
)

if sys.version_info >= (3, 11):
    from typing import Self
else:
    from typing_extensions import Self

_LINE_END = re.compile(r"\n|$")


class BlockState:
    """The state to save block parser's cursor and tokens."""

    src: str
    tokens: List[Dict[str, Any]]
    cursor: int
    cursor_max: int
    list_tight: bool
    parent: Any
    env: MutableMapping[str, Any]
    lazy_line_starts: Set[int]

    def __init__(self, parent: Optional[Any] = None) -> None:
        self.src = ""
        self.tokens = []

        # current cursor position
        self.cursor = 0
        self.cursor_max = 0

        # for list and block quote chain
        self.list_tight = True
        self.parent = parent
        self.lazy_line_starts = set()

        # for saving def references
        if parent:
            self.env = parent.env
        else:
            self.env = {"ref_links": {}}

    def child_state(self, src: str, lazy_line_starts: Optional[Set[int]] = None) -> "BlockState":
        child = self.__class__(self)
        child.process(src)
        if lazy_line_starts:
            child.lazy_line_starts = lazy_line_starts
        return child

    def process(self, src: str) -> None:
        self.src = src
        self.cursor_max = len(src)

    def find_line_end(self) -> int:
        return self.find_line_end_at(self.cursor)

    def find_line_end_at(self, pos: int) -> int:
        m = _LINE_END.search(self.src, pos)
        assert m is not None
        return m.end()

    def get_text(self, end_pos: int) -> str:
        return self.src[self.cursor : end_pos]

    def get_line(self, start_pos: int) -> str:
        return self.src[start_pos : self.find_line_end_at(start_pos)]

    def last_token(self) -> Any:
        if self.tokens:
            return self.tokens[-1]

    def prepend_token(self, token: Dict[str, Any]) -> None:
        """Insert token before the last token."""
        self.tokens.insert(len(self.tokens) - 1, token)

    def append_token(self, token: Dict[str, Any]) -> None:
        """Add token to the end of token list."""
        self.tokens.append(token)

    def add_paragraph(self, text: str) -> None:
        last_token = self.last_token()
        if last_token and last_token["type"] == "paragraph":
            last_token["text"] += text
        else:
            self.tokens.append({"type": "paragraph", "text": text})

    def append_paragraph(self) -> Optional[int]:
        last_token = self.last_token()
        if last_token and last_token["type"] == "paragraph":
            pos = self.find_line_end()
            last_token["text"] += self.get_text(pos)
            return pos
        return None

    def depth(self) -> int:
        d = 0
        parent = self.parent
        while parent:
            d += 1
            parent = parent.parent
        return d


class InlineState:
    """The state to save inline parser's tokens."""

    def __init__(self, env: MutableMapping[str, Any]):
        self.env = env
        self.src = ""
        self.tokens: List[Dict[str, Any]] = []
        self.in_image = False
        self.image_depth = 0
        self.in_link = False
        self.no_close_bracket_before: int = 0  # high-water mark for DoS mitigation
        self.no_link_before: int = 0  # high-water mark for failed balanced link candidates
        self.no_image_before: int = 0  # high-water mark for failed image candidates
        self.link_brackets: Dict[int, Tuple[str, Dict[int, int]]] = {}
        self.link_ranges: Dict[int, Tuple[str, List[int], List[int]]] = {}
        self.formatting_no_end: Dict[Tuple[int, str], Tuple[str, int]] = {}

    def prepend_token(self, token: Dict[str, Any]) -> None:
        """Insert token before the last token."""
        self.tokens.insert(len(self.tokens) - 1, token)

    def append_token(self, token: Dict[str, Any]) -> None:
        """Add token to the end of token list."""
        self.tokens.append(token)

    def copy(self) -> "InlineState":
        """Create a copy of current state."""
        state = self.__class__(self.env)
        state.in_image = self.in_image
        state.image_depth = self.image_depth
        state.in_link = self.in_link
        state.link_brackets = self.link_brackets
        state.link_ranges = self.link_ranges
        state.formatting_no_end = self.formatting_no_end
        return state


ST = TypeVar("ST", InlineState, BlockState)


class Parser(Generic[ST]):
    sc_flag: "re._FlagsType" = re.M
    state_cls: Type[ST]

    SPECIFICATION: ClassVar[Dict[str, str]] = {}
    DEFAULT_RULES: ClassVar[Iterable[str]] = []

    def __init__(self) -> None:
        self.specification = self.SPECIFICATION.copy()
        self.rules = list(self.DEFAULT_RULES)
        self._methods: Dict[
            str,
            Callable[[Match[str], ST], Optional[int]],
        ] = {}

        self.__sc: Dict[str, Pattern[str]] = {}

    def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
        if rules is None:
            key = "$"
            rules = self.rules
        else:
            key = "|".join(rules)

        sc = self.__sc.get(key)
        if sc:
            return sc

        regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
        sc = re.compile(regex, self.sc_flag)
        self.__sc[key] = sc
        return sc

    def register(
        self,
        name: str,
        pattern: Union[str, None],
        func: Callable[[Self, Match[str], ST], Optional[int]],
        before: Optional[str] = None,
    ) -> None:
        """Register a new rule to parse the token. This method is usually used to
        create a new plugin.

        :param name: name of the new grammar
        :param pattern: regex pattern in string
        :param func: the parsing function
        :param before: insert this rule before a built-in rule
        """
        self._methods[name] = lambda m, state: func(self, m, state)
        self.__sc.clear()
        if pattern:
            self.specification[name] = pattern
        if name not in self.rules:
            self.insert_rule(self.rules, name, before=before)

    def register_rule(self, name: str, pattern: str, func: Any) -> None:
        raise DeprecationWarning("This plugin is not compatible with mistune v3.")

    @staticmethod
    def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
        if before:
            try:
                index = rules.index(before)
                rules.insert(index, name)
            except ValueError:
                rules.append(name)
        else:
            rules.append(name)

    def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
        lastgroup = m.lastgroup
        assert lastgroup
        func = self._methods[lastgroup]
        return func(m, state)


class BaseRenderer(object):
    NAME: ClassVar[str] = "base"

    def __init__(self) -> None:
        self.__methods: Dict[str, Callable[..., str]] = {}

    def register(self, name: str, method: Callable[..., str]) -> None:
        """Register a render method for the named token. For example::

        def render_wiki(renderer, key, title):
            return f'<a href="/wiki/{key}">{title}</a>'

        renderer.register('wiki', render_wiki)
        """
        # bind self into renderer method
        self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)

    def _get_method(self, name: str) -> Callable[..., str]:
        try:
            return cast(Callable[..., str], object.__getattribute__(self, name))
        except AttributeError:
            method = self.__methods.get(name)
            if not method:
                raise AttributeError('No renderer "{!r}"'.format(name))
            return method

    def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
        func = self._get_method(token["type"])
        return func(token, state)

    def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
        for tok in tokens:
            yield self.render_token(tok, state)

    def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        return "".join(self.iter_tokens(tokens, state))

    def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
        return self.render_tokens(tokens, state)
