"""
Defines themes for figures.
"""

import copy
from collections.abc import Callable

import armonia
import plotly.graph_objects as go
import plotly.io as pio
from polychromos.color import HSLColor
from polychromos.palette import HSLColorSequence


class Theme:
    """
    Encapsulates a theme for figures.
    """

    def __init__(
        self,
        colors: dict[str, HSLColor],
    ) -> None:
        self._armonia_theme = armonia.Theme()
        self.__setup_colors(colors)

    def __setup_colors(
        self,
        colors: dict[str, HSLColor],
    ) -> None:
        for name, color in colors.items():
            self.armonia_theme.set_color(name, color)

        def lightness_variation_based_on_background(
            base_color_name: str,
            background_color_name: str,
            delta: float,
        ) -> Callable[[armonia.Theme], HSLColor]:
            def variation_function(
                theme: armonia.Theme,
            ) -> HSLColor:
                base_color: HSLColor = theme.get_color(base_color_name)
                background_color: HSLColor = theme.get_color(background_color_name)
                amount: float = background_color.lightness - base_color.lightness
                return base_color.delta(delta * 0.02, 0.0, delta * amount)

            return variation_function

        if "background" in colors:
            self.armonia_theme.set_computed_color(
                "background_inverted",
                armonia.colorfunctions.invert("background"),
            )
            self.armonia_theme.set_computed_color(
                "background_inverted_mild",
                armonia.colorfunctions.softer("background_inverted", "background", 0.3),
            )
            self.armonia_theme.set_computed_color(
                "background_inverted_milder",
                armonia.colorfunctions.softer("background_inverted", "background", 0.6),
            )
            self.armonia_theme.set_computed_color(
                "background_inverted_faint",
                armonia.colorfunctions.softer("background_inverted", "background", 0.9),
            )
        for base_color_prefix in ["primary", "secondary", "accent"]:
            if f"{base_color_prefix}_color_normal" in colors:
                self.armonia_theme.set_computed_color(
                    f"{base_color_prefix}_color_softer",
                    lightness_variation_based_on_background(f"{base_color_prefix}_color_normal", "background", 0.4),
                )
                self.armonia_theme.set_computed_color(
                    f"{base_color_prefix}_color_stronger",
                    lightness_variation_based_on_background(f"{base_color_prefix}_color_normal", "background", -0.4),
                )

    @property
    def armonia_theme(self) -> armonia.Theme:
        """
        Gets the underlying armonia theme.

        :return: The armonia theme.
        :rtype: armonia.Theme
        """
        return self._armonia_theme

    def get_color(
        self,
        name_or_value: str,
    ) -> HSLColor:
        """
        Gets a color from the theme by name or hex value.

        :param name_or_value: The name or value of the color.
        :type name_or_value: str
        :raises ValueError: When the color is not in hex value nor found in the theme or web colors.
        :return: The color mapped to the name or value given.
        :rtype: HSLColor
        """
        return self.armonia_theme.get_color(name_or_value)

    def get_all_colors(self) -> list[tuple[str, HSLColor]]:
        """
        Gets all colors defined in the theme.

        :return: A list of tuples with color names and their corresponding colors.
        :rtype: list[tuple[str, HSLColor]]
        """
        return [(c.name, c.color) for c in self.armonia_theme.get_all_colors(sort_by="name")]

    def get_default_color_sequence(self) -> HSLColorSequence:
        """
        Gets the default color sequence for the theme.

        :return: A color sequence to be used as default.
        :rtype: HSLColorSequence
        """
        return [
            self.armonia_theme.get_color("primary_color_normal"),
        ]

    def plotly_template(self) -> go.layout.Template:
        """
        Gets the plotly template associated with the theme.

        :return: The plotly template.
        :rtype: go.layout.Template
        """
        bg_color: str = self.armonia_theme.get_color("background").to_css_hsl(legacy=True)
        bg_inverted_color: str = self.armonia_theme.get_color("background_inverted").to_css_hsl(legacy=True)
        bg_inverted_mild_color: str = self.armonia_theme.get_color("background_inverted_mild").to_css_hsl(legacy=True)
        bg_inverted_milder_color: str = self.armonia_theme.get_color("background_inverted_milder").to_css_hsl(legacy=True)
        bg_inverted_faint_color: str = self.armonia_theme.get_color("background_inverted_faint").to_css_hsl(legacy=True)
        template: go.layout.Template = copy.deepcopy(pio.templates["plotly_white"])
        template.layout.paper_bgcolor = bg_color
        template.layout.plot_bgcolor = bg_color
        template.layout.font.color = bg_inverted_color
        template.layout.xaxis.gridcolor = bg_inverted_faint_color
        template.layout.xaxis.linecolor = bg_inverted_milder_color
        template.layout.xaxis.zerolinecolor = bg_inverted_mild_color
        template.layout.xaxis.tickfont.color = bg_inverted_mild_color
        template.layout.yaxis.gridcolor = bg_inverted_faint_color
        template.layout.yaxis.linecolor = bg_inverted_milder_color
        template.layout.yaxis.zerolinecolor = bg_inverted_mild_color
        template.layout.yaxis.tickfont.color = bg_inverted_mild_color
        template.layout.shapedefaults.line.color = bg_inverted_faint_color
        template.layout.geo.countrycolor = bg_inverted_faint_color
        template.layout.geo.lakecolor = bg_inverted_faint_color
        template.layout.geo.oceancolor = bg_inverted_faint_color
        template.layout.geo.landcolor = bg_color
        template.layout.geo.coastlinecolor = bg_inverted_color
        template.layout.geo.showcoastlines = True
        template.data["bar"][0].marker.line.color = bg_inverted_faint_color
        template.data["barpolar"][0].marker.line.color = bg_inverted_faint_color
        template.data["choropleth"][0].marker.line.color = bg_inverted_color
        template.data["choropleth"][0].marker.line.width = 0.5
        return template


__all__ = [
    "Theme",
]
