# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

import collections
from typing import TYPE_CHECKING, Dict, List, Tuple, ValuesView

from pylint.exceptions import UnknownMessageError
from pylint.message.message_definition import MessageDefinition
from pylint.message.message_id_store import MessageIdStore

if TYPE_CHECKING:
    from pylint.checkers import BaseChecker


class MessageDefinitionStore:

    """The messages store knows information about every possible message definition but has
    no particular state during analysis.
    """

    def __init__(self) -> None:
        self.message_id_store: MessageIdStore = MessageIdStore()
        # Primary registry for all active messages definitions.
        # It contains the 1:1 mapping from msgid to MessageDefinition.
        # Keys are msgid, values are MessageDefinition
        self._messages_definitions: Dict[str, MessageDefinition] = {}
        # MessageDefinition kept by category
        self._msgs_by_category: Dict[str, List[str]] = collections.defaultdict(list)

    @property
    def messages(self) -> ValuesView[MessageDefinition]:
        """The list of all active messages."""
        return self._messages_definitions.values()

    def register_messages_from_checker(self, checker: "BaseChecker") -> None:
        """Register all messages definitions from a checker."""
        checker.check_consistency()
        for message in checker.messages:
            self.register_message(message)

    def register_message(self, message: MessageDefinition) -> None:
        """Register a MessageDefinition with consistency in mind."""
        self.message_id_store.register_message_definition(
            message.msgid, message.symbol, message.old_names
        )
        self._messages_definitions[message.msgid] = message
        self._msgs_by_category[message.msgid[0]].append(message.msgid)

    def get_message_definitions(self, msgid_or_symbol: str) -> List[MessageDefinition]:
        """Returns the Message definition for either a numeric or symbolic id."""
        return [
            self._messages_definitions[m]
            for m in self.message_id_store.get_active_msgids(msgid_or_symbol)
        ]

    def get_msg_display_string(self, msgid_or_symbol: str) -> str:
        """Generates a user-consumable representation of a message."""
        message_definitions = self.get_message_definitions(msgid_or_symbol)
        if len(message_definitions) == 1:
            return repr(message_definitions[0].symbol)
        return repr([md.symbol for md in message_definitions])

    def help_message(self, msgids_or_symbols: List[str]) -> None:
        """Display help messages for the given message identifiers"""
        for msgids_or_symbol in msgids_or_symbols:
            try:
                for message_definition in self.get_message_definitions(
                    msgids_or_symbol
                ):
                    print(message_definition.format_help(checkerref=True))
                    print("")
            except UnknownMessageError as ex:
                print(ex)
                print("")
                continue

    def list_messages(self) -> None:
        """Output full messages list documentation in ReST format."""
        emittable, non_emittable = self.find_emittable_messages()
        print("Emittable messages with current interpreter:")
        for msg in emittable:
            print(msg.format_help(checkerref=False))
        print("\nNon-emittable messages with current interpreter:")
        for msg in non_emittable:
            print(msg.format_help(checkerref=False))
        print("")

    def find_emittable_messages(
        self,
    ) -> Tuple[List[MessageDefinition], List[MessageDefinition]]:
        """Finds all emittable and non-emittable messages"""
        messages = sorted(self._messages_definitions.values(), key=lambda m: m.msgid)
        emittable = []
        non_emittable = []
        for message in messages:
            if message.may_be_emitted():
                emittable.append(message)
            else:
                non_emittable.append(message)
        return emittable, non_emittable
