import collections.abc
import dataclasses
import re

import typing_extensions as typing

from ..data_types.string import String
from ..fdl import Deserializer, Serializer
from .constraint import Constraint

if typing.TYPE_CHECKING:
    from ..data_types import BasicType, List


@dataclasses.dataclass
class Pattern(Constraint[String]):
    """A constraint that enforces a specific regular expression pattern for a `String` value."""

    value: str
    """The regular expression pattern that the string value must match."""

    @typing.override
    async def validate(self, value: String) -> bool:
        if not isinstance(value, String):
            msg = f"Expected value of type 'String', received '{type(value).__name__}'."
            raise TypeError(msg)

        if not re.fullmatch(self.value, value.value):
            msg = f"Value '{value.value}' does not match the pattern: '{self.value}'."
            raise ValueError(msg)

        return True

    @typing.override
    def serialize(self, serializer: Serializer) -> None:
        serializer.write_str("Pattern", self.value)

    @typing.override
    @classmethod
    def deserialize(
        cls, deserializer: Deserializer, context: typing.Optional[dict] = None
    ) -> collections.abc.Generator[None, typing.Any, typing.Self]:
        data_type: typing.Union[None, type["BasicType"], type["List"]] = (context or {}).get("data_type", None)

        if data_type is None:
            msg = "Missing 'data_type' in context."
            raise ValueError(msg)

        if not issubclass(data_type, String):
            msg = f"Expected constraint's data type to be 'String', received '{data_type.__name__}'."
            raise ValueError(msg)

        yield from deserializer.read_start_element(name="Pattern")
        value = yield from deserializer.read_str()
        yield from deserializer.read_end_element(name="Pattern")

        return cls(value.value)
