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

from collections.abc import Callable
from typing import Any, NamedTuple, Protocol

from ..tokenizing import Cursor


class MemoKey(NamedTuple):
    pos: int
    ruleinfo: RuleInfo


class RuleResult(NamedTuple):
    node: Any
    newpos: int


class RuleLike(Protocol):
    is_leftrec: bool = False
    is_memoizable: bool = False
    is_name: bool = False

    def __call__(self, *args: Any, **kwargs: Any) -> Any:
        pass


class RuleInfo(NamedTuple):
    name: str
    instance: Any
    func: Callable[..., Any]
    is_lrec: bool
    is_memo: bool
    is_name: bool
    params: tuple[Any, ...]
    kwparams: dict[str, Any]

    @staticmethod
    def new(instance: Any, func: Callable, params=None, kwparams=None) -> RuleInfo:
        return RuleInfo(
            name=getattr(func, '__name__', '<?>'),
            instance=instance,
            func=func,
            is_lrec=getattr(func, 'is_leftrec', False),
            is_memo=getattr(func, 'is_memoizable', True),
            is_name=getattr(func, 'is_name', False),
            params=params or (),
            kwparams=kwparams or {},
        )

    @staticmethod
    def bind(ri: RuleInfo, instance: Any) -> RuleInfo:
        return ri._replace(instance=instance)

    def is_token_rule(self):
        return self.name.lstrip('_')[:1].isupper()

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        if isinstance(other, RuleInfo):
            return self.name == other.name
        return False

    def __ne__(self, other):
        return not self.__eq__(other)


class ParseInfo(NamedTuple):
    cursor: Cursor
    rule: str
    pos: int
    endpos: int
    line: int
    endline: int
    alerts: list[Alert] = []  # noqa: RUF012

    @property
    def tokenizer(self) -> Cursor:
        # NOTE:
        #   info.tokenizer provided for bakwards compatibility
        #   self.cursor.tokenizer:Tokenizer is opaque, so useless
        return self.cursor

    def text_lines(self) -> list[str]:
        return self.cursor.get_lines(self.line, self.endline)

    def line_index(self):
        return self.cursor.line_index(self.line, self.endline)


class CommentInfo(NamedTuple):
    inline: list
    eol: list

    @staticmethod
    def new_comment():
        return CommentInfo([], [])


class Alert(NamedTuple):
    level: int = 1
    message: str = ''
