"""
Chart options definitions.
"""

from abc import ABC, abstractmethod
from typing import Any

from orama.enums import GeoProjection, LineInterpolation, LinePlacement, LineSortOrder, SortOrder


class ChartOption(ABC):
    """
    Abstract base class for chart options.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        icon: str | None = None,
    ) -> None:
        self._name = name
        self._description = description
        self._map_to = map_to
        self._icon = icon

    @abstractmethod
    def validate(
        self,
        value: Any,
    ) -> None:
        """
        Validates a value for the option.

        :param value: The value to validate.
        :type value: Any
        """

    def describe(self) -> dict[str, Any]:
        """
        Provides a descriptive structure of the option.

        :return: A dictionary describing the option.
        :rtype: dict[str, Any]
        """
        return {
            "type": self.__class__,
            "name": self._name,
            "description": self._description,
            "map_to": self._map_to,
            "icon": self._icon,
        }


class BooleanOption(ChartOption):
    """
    Represents a boolean option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: bool = False,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: Name of the option.
        :type name: str
        :param description: Description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default value for the option. Defaults to ``False``.
        :type default_value: bool, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, bool):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a boolean")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = bool
        return base_description


class FloatOption(ChartOption):
    """
    Represents a float option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: float | None = 0.0,
        min_value: float | None = None,
        max_value: float | None = None,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default value for the option. Defaults to ``0.0``.
        :type default_value: float | None, optional
        :param min_value: The minimum value of the option, if any. Defaults to ``None``.
        :type min_value: float | None, optional
        :param max_value: The maximum value of the option, if any. Defaults to ``None``.
        :type max_value: float | None, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value
        self._min_value = min_value
        self._max_value = max_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, (float, int)):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a float")
        if self._min_value is not None and value < self._min_value:
            raise ValueError(f"Value {str(value)} for option {self._name} is less than minimum {self._min_value}")
        if self._max_value is not None and value > self._max_value:
            raise ValueError(f"Value {str(value)} for option {self._name} is greater than maximum {self._max_value}")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = float
        if self._min_value is not None:
            base_description["min_value"] = self._min_value
        if self._max_value is not None:
            base_description["max_value"] = self._max_value
        return base_description


class IntOption(ChartOption):
    """
    Represents an integer option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: int | None = 0,
        min_value: int | None = None,
        max_value: int | None = None,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default value for the option. Defaults to ``0``.
        :type default_value: int | None, optional
        :param min_value: The minimum value of the option, if any. Defaults to ``None``.
        :type min_value: int | None, optional
        :param max_value: The maximum value of the option, if any. Defaults to ``None``.
        :type max_value: int | None, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value
        self._min_value = min_value
        self._max_value = max_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, int) or isinstance(value, bool):
            raise ValueError(f"Value {str(value)} for option {self._name} is not an integer")
        if self._min_value is not None and value < self._min_value:
            raise ValueError(f"Value {str(value)} for option {self._name} is less than minimum {self._min_value}")
        if self._max_value is not None and value > self._max_value:
            raise ValueError(f"Value {str(value)} for option {self._name} is greater than maximum {self._max_value}")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = int
        if self._min_value is not None:
            base_description["min_value"] = self._min_value
        if self._max_value is not None:
            base_description["max_value"] = self._max_value
        return base_description


class StringOption(ChartOption):
    """
    Represents a string option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: str | None = None,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: Name of the option.
        :type name: str
        :param description: Description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default value for the option. Defaults to ``None``.
        :type default_value: str | None, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, str):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a string")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = str
        return base_description


class SortOrderOption(ChartOption):
    """
    Represents a sort order option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: SortOrder = SortOrder.NONE,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default sorting order. Defaults to ``SortOrder.NONE``.
        :type default_value: SortOrder, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, SortOrder):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a sorting order")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = SortOrder
        return base_description


class LinePlacementOption(ChartOption):
    """
    Represents a line placement mode option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: LinePlacement = LinePlacement.OVERLAPPING,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default placement mode. Defaults to ``LinePlacement.OVERLAPPING``.
        :type default_value: LinePlacement, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, LinePlacement):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a LinePlacement")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = LinePlacement
        return base_description


class LineInterpolationOption(ChartOption):
    """
    Represents a line interpolation mode option for charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: LineInterpolation = LineInterpolation.LINEAR,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default interpolation mode. Defaults to ``LineInterpolation.LINEAR``.
        :type default_value: LineInterpolation, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, LineInterpolation):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a LineInterpolation")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = LineInterpolation
        return base_description


class LineSortOrderOption(ChartOption):
    """
    Represents a sort order option for line chart series/categories.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: LineSortOrder = LineSortOrder.NONE,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default sort order. Defaults to ``LineSortOrder.NONE``.
        :type default_value: LineSortOrder, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, LineSortOrder):
            raise ValueError(f"Value {value!r} for option {self._name} is not a LineSortOrder")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = LineSortOrder
        return base_description


class GeoProjectionOption(ChartOption):
    """
    Represents a geo projection option for choropleth map charts.
    """

    def __init__(
        self,
        name: str,
        description: str,
        map_to: str,
        default_value: GeoProjection = GeoProjection.NATURAL_EARTH,
        icon: str | None = None,
    ) -> None:
        """
        Constructor method.

        :param name: The name of the option.
        :type name: str
        :param description: The description of the option.
        :type description: str
        :param map_to: The internal parameter to map the variable to.
        :type map_to: str
        :param default_value: The default geo projection. Defaults to ``GeoProjection.NATURAL_EARTH``.
        :type default_value: GeoProjection, optional
        :param icon: An optional SVG icon string. Defaults to ``None``.
        :type icon: str | None, optional
        """
        super().__init__(
            name=name,
            description=description,
            map_to=map_to,
            icon=icon,
        )
        self._default_value = default_value

    def validate(
        self,
        value: Any,
    ) -> None:
        if not isinstance(value, GeoProjection):
            raise ValueError(f"Value {str(value)} for option {self._name} is not a GeoProjection")

    def describe(self) -> dict[str, Any]:
        base_description = super().describe()
        base_description["default_value"] = self._default_value
        base_description["value_type"] = GeoProjection
        return base_description


ChartOptionsMap = dict[str, ChartOption]


__all__ = [
    "ChartOption",
    "BooleanOption",
    "FloatOption",
    "IntOption",
    "StringOption",
    "SortOrderOption",
    "LinePlacementOption",
    "LineInterpolationOption",
    "LineSortOrderOption",
    "GeoProjectionOption",
    "ChartOptionsMap",
]
