from typing import List


class ValidationError(RuntimeError):
    pass


def assert_true(condition, msg=''):
    if not condition:
        raise ValidationError(msg)


class Setting():
    def __init__(self, *, label: str = None):
        self.label = label

    def validate(self, value):
        pass

    def _serialize(self):
        raise RuntimeError("Setting must implement _serialize()")


class StringSetting(Setting):
    """
    A text input field.

    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def _serialize(self):
        return dict(type='string', label=self.label)

    def validate(self, value):
        assert_true(isinstance(value, str), "Must be a string")


class NumberSetting(Setting):
    """
    A numeric input field.

    """

    def __init__(self, *, min_value=None, max_value=None, **kwargs):
        """
        :param min_value: A minimum numeric value (optional).
        :param min_value: A maximum numeric value (optional).
        """
        super().__init__(**kwargs)

        if min_value is not None and max_value is not None:
            assert_true(min_value <= max_value,
                        "min_value cannot be greater than max_value")

        self.min_value = min_value
        self.max_value = max_value

    def _serialize(self):
        data = dict(type='number', label=self.label)

        if self.min_value is not None:
            data['min_value'] = self.min_value

        if self.max_value is not None:
            data['max_value'] = self.max_value

        return data

    def validate(self, value):
        assert_true(isinstance(
            value, int) or isinstance(value, float), "Must be an int or float")
        assert_true(self.min_value is None or value >=
                    self.min_value, f'Number less than {self.min_value}')
        assert_true(self.max_value is None or value <= self.max_value,
                    f'Number greater than {self.max_value}')


class ChoicesSetting(Setting):
    """
    A multiple-choice input field.

    """

    def __init__(self, choices: List[str], **kwargs):
        """
        :param choices: A list of choices for the user to choose from.
        """
        super().__init__(**kwargs)
        for choice in choices:
            assert_true(isinstance(choice, str), "Choice must be a string")

        self.choices = choices

    def _serialize(self):
        return dict(type='choices', label=self.label, choices=self.choices)

    def validate(self, value):
        assert_true(isinstance(value, str), "Must be a string")
        assert_true(value in self.choices, 'Invalid choice')
