from abc import ABCMeta
from dataclasses import dataclass, replace
from typing import Callable, Set, Union

from tdm.abstract.datamodel import AbstractDomain, AbstractFact, AbstractNode, AbstractNodeMention, Identifiable
from tdm.abstract.json_schema import generate_model
from .value import AtomValueFact


@dataclass(frozen=True)
class _MentionFact(AbstractFact, metaclass=ABCMeta):
    """
    Auxiliary class for `MentionFact` to fix dataclass fields order.

    Attributes
    ----------
    mention:
        The part of the node that contains value fact mention.
    value:
        The value fact mentioned in document node.
    """
    mention: AbstractNodeMention
    value: AtomValueFact

    @classmethod
    def constant_fields(cls) -> Set[str]:
        return {'mention', 'value'}


@generate_model(label='mention')
@dataclass(frozen=True)
class MentionFact(Identifiable, _MentionFact):
    """
    Represents fact that some `AtomValueFact` is mentioned in the document node.

    """

    def replace_with_domain(self, domain: AbstractDomain) -> 'MentionFact':
        value = self.value.replace_with_domain(domain)
        if value is self.value:
            return self
        return replace(self, value=value)

    @staticmethod
    def node_filter(node: Union[AbstractNode, str]) -> Callable[['MentionFact'], bool]:
        """
        Create a filter function to filter `MentionFact` instances based on the document node.

        :param node: The document node or its identifier to filter by.
        :return: A filter function for `MentionFact` instances.
        """
        node_id = node.id if isinstance(node, AbstractNode) else node

        def _filter(fact: MentionFact) -> bool:
            return fact.mention.node_id == node_id

        return _filter

    @staticmethod
    def value_filter(filter_: Callable[[AtomValueFact], bool]) -> Callable[['MentionFact'], bool]:
        """
        Create a filter function to filter `MentionFact` instances based on the value.

        :param filter_: A filter function for mentioned `AtomValueFact`.
        :return: A filter function for `MentionFact` instances.
        """

        def _filter(fact: MentionFact) -> bool:
            return filter_(fact.value)

        return _filter
