from dataclasses import dataclass
from typing import Any, List, Optional, Type

# pylint: disable=unidiomatic-typecheck


@dataclass(frozen=True)
class Parameter:
    """Class for storing parameters. This dataclass is frozen so no attributes can be changed after initialization.

    Parameters
    ----------
    name : str
        Name of the parameter.
    type_ : type
        Type of the parameter.
    default : Any
        Default value of the parameter.
    choices : Optional[List[Any]]
        List of possible values for the parameter.
    """

    name: str
    type_: Type
    default: Any
    choices: Optional[List[Any]] = None

    def __post_init__(self):
        """Post initialization."""
        self.validate()

    def validate(self):
        """Validate the value of the parameter.

        Parameters
        ----------
        value : Any
            Value of the parameter.

        Raises
        ------
        TypeError
            If the value is not of the correct type.
        ValueError
            If the value is not in the list of possible values.
        """
        if not self.validate_type(self.default):
            raise TypeError(
                f"Default for '{self.name}' must be of type '{self.type_.__name__}'."
            )
        if self.choices is not None:
            if not all(self.validate_type(choice) for choice in self.choices):
                raise TypeError(
                    f"Choices must be of the same type as the parameter.\n"
                    f"'{self.name}': '{self.choices}' -> '{self.type_.__name__}'"
                )
        if self.choices is not None and self.default not in self.choices:
            raise ValueError(
                f"Parameter '{self.name}' must be one of type '{self.choices}'."
            )

    def validate_type(self, value: Any) -> bool:
        """Check if the value is of the correct type.

        Parameters
        ----------
        value : Any
            Value of the parameter.

        Returns
        -------
        bool
            True if the value is of the correct type, False otherwise.
        """
        # Temporary workaround until we drop Python 3.9 for:
        # Error: Subscripted generics cannot be used with class and instance checks
        #
        # isinstance for subscripted generics (e.g. List[str]) is only available
        # in Python 3.10, so this method does not check for the type of subscripted generics.
        # TODO: Though we should implement it.
        # See https://stackoverflow.com/questions/70825351/python-3-typeerror-subscripted-generics-cannot-be-used-with-class-and-instance # noqa: E501
        try:
            return isinstance(value, self.type_)
        except Exception as _:  # noqa: F841
            return True
