import dataclasses
import enum
import typing

T = typing.TypeVar("T")
CommandFunction = typing.Callable[..., T]


class DataType(enum.Enum):
    """
    Enumeration of supported data types for command parameters.

    Attributes
    ----------
    FLOAT : DataType
        Floating-point number.
    STRING : DataType
        String.
    INTEGER : DataType
        Integer.
    BOOL : DataType
        Boolean.
    OSTRING : DataType
        Optional string.
    OINTEGER : DataType
        Optional integer.
    OFLOAT : DataType
        Optional float.
    OBOOL : DataType
        Optional boolean.
    """

    FLOAT = enum.auto()
    STRING = enum.auto()
    INTEGER = enum.auto()
    BOOL = enum.auto()
    OSTRING = enum.auto()
    OINTEGER = enum.auto()
    OFLOAT = enum.auto()
    OBOOL = enum.auto()

    @classmethod
    def from_str(
        cls,
        value: str,
    ):
        """
        Convert a string to a corresponding DataType.

        Parameters
        ----------
        value : str
            The string representation of the data type.

        Returns
        -------
        DataType
            The corresponding DataType member.

        Raises
        ------
        ValueError
            If the provided string does not correspond to any valid data type.
        """
        try:
            return cls[value]
        except KeyError:
            raise ValueError(f"Invalid data type: {value}")


@dataclasses.dataclass
class ChitraGuptParameter:
    """
    Represents a command parameter with its name, expected data type, and optionality.

    Parameters
    ----------
    name : str
        The parameter name.
    data_type : DataType
        The expected data type of the parameter.
    optional : bool, optional
        Whether the parameter is optional (default is False).
    """

    name: str
    data_type: DataType
    optional: bool = False

    def validate_value(
        self,
        value: typing.Any,
    ) -> None:
        """
        Validate that the provided value matches the expected data type.

        Parameters
        ----------
        value : Any
            The value to validate.

        Raises
        ------
        TypeError
            If the value is not of the expected type.
        """
        if self.optional and value is None:
            return

        type_mapping: typing.Dict[DataType, typing.Any] = {
            DataType.INTEGER: int,
            DataType.FLOAT: float,
            DataType.STRING: str,
            DataType.BOOL: bool,
            DataType.OINTEGER: (type(None), int),
            DataType.OFLOAT: (type(None), float),
            DataType.OSTRING: (type(None), str),
            DataType.OBOOL: (type(None), bool),
        }
        expected_type = type_mapping[self.data_type]
        if not isinstance(value, expected_type):
            raise TypeError(
                f"Argument '{self.name}' should be of type {self.data_type.name}"
            )


@dataclasses.dataclass
class ChitraguptCommand(typing.Generic[T]):
    """
    Represents a command registered with a plugin.

    Parameters
    ----------
    name : str
        The command name.
    parameters : List[ChitraGuptParameter]
        The list of expected parameters.
    function : CommandFunction[T]
        The function implementing the command.
    """

    name: str
    parameters: typing.List[ChitraGuptParameter]
    function: CommandFunction[T]
