# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, Gdk, Gio, Gtk

THEMES = {
    "meshcore": {
        "name": "MeshCore Dark",
        "foreground": (1.0, 1.0, 1.0, 0.9),
        "background": (0.067, 0.094, 0.153),
        "accent": (0.231, 0.510, 0.965),
        "is_dark": True,
    },
    "meshcore-light": {
        "name": "MeshCore Light",
        "foreground": (0.067, 0.094, 0.153, 0.85),
        "background": (0.945, 0.961, 0.976),
        "accent": (0.231, 0.510, 0.965),
        "is_dark": False,
    },
    "spring": {
        "name": "Spring",
        "foreground": (0.250, 0.368, 0.501),
        "background": (0.972, 1.0, 0.949),
        "accent": (0.2, 0.65, 0.4),
        "is_dark": False,
    },
    "midnight": {
        "name": "Midnight",
        "foreground": (1.0, 1.0, 1.0, 0.6),
        "background": (0.137, 0.207, 0.250),
        "accent": (0.160, 0.470, 0.650),
        "is_dark": True,
    },
    "gruvbox": {
        "name": "Gruvbox",
        "foreground": (0.984, 0.945, 0.780, 0.8),
        "background": (0.156, 0.156, 0.156),
        "accent": (0.407, 0.615, 0.415),
        "is_dark": True,
    },
    "ocean": {
        "name": "Ocean",
        "foreground": (0.741, 0.901, 0.984),
        "background": (0.117, 0.145, 0.160),
        "accent": (0.3, 0.7, 0.8),
        "is_dark": True,
    },
}

COLOR_SCHEME_MAP = {
    "system": Adw.ColorScheme.DEFAULT,
    "light": Adw.ColorScheme.FORCE_LIGHT,
    "dark": Adw.ColorScheme.FORCE_DARK,
}


class ThemeManager:
    _instance = None

    def __init__(self):
        self._recoloring_provider = Gtk.CssProvider()
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            self._recoloring_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
        )
        self._settings = Gio.Settings(schema_id="page.codeberg.sesivany.Meshy")

    @classmethod
    def get_default(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def apply_from_settings(self):
        mode = self._settings.get_string("color-scheme")
        if mode == "custom":
            theme_id = self._settings.get_string("custom-theme")
            self.apply_custom_theme(theme_id)
        else:
            self.apply_color_scheme(mode)

    def apply_color_scheme(self, mode):
        scheme = COLOR_SCHEME_MAP.get(mode, Adw.ColorScheme.DEFAULT)
        Adw.StyleManager.get_default().set_color_scheme(scheme)
        self._recoloring_provider.load_from_string("")

    def apply_custom_theme(self, theme_id):
        theme = THEMES.get(theme_id)
        if not theme:
            Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.DEFAULT)
            self._recoloring_provider.load_from_string("")
            return
        if theme["is_dark"]:
            Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_DARK)
        else:
            Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_LIGHT)
        css = self._generate_css(theme)
        self._recoloring_provider.load_from_string(css)

    def set_color_scheme(self, mode):
        self._settings.set_string("color-scheme", mode)
        if mode != "custom":
            self.apply_color_scheme(mode)

    def set_custom_theme(self, theme_id):
        self._settings.set_string("custom-theme", theme_id)
        self._settings.set_string("color-scheme", "custom")
        self.apply_custom_theme(theme_id)

    @staticmethod
    def _rgba_str(components):
        if len(components) == 4:
            r, g, b, a = components
            return f"rgba({int(r*255)},{int(g*255)},{int(b*255)},{a:.2f})"
        r, g, b = components[:3]
        return f"rgb({int(r*255)},{int(g*255)},{int(b*255)})"

    @staticmethod
    def _rgba_opaque_str(components):
        r, g, b = components[:3]
        return f"rgb({int(r*255)},{int(g*255)},{int(b*255)})"

    def _generate_css(self, theme):
        fg = theme["foreground"]
        bg = theme["background"]
        accent = theme["accent"]
        is_dark = theme["is_dark"]

        alt = "white" if is_dark else "black"
        mix_level = 0.07 if is_dark else 0.25

        fg_str = self._rgba_str(fg)
        bg_str = self._rgba_opaque_str(bg)

        lines = []
        lines.append(f"@define-color window_bg_color {bg_str};")
        lines.append(f"@define-color window_fg_color {fg_str};")
        lines.append(f"@define-color headerbar_bg_color mix({bg_str},white,{mix_level});")
        lines.append(f"@define-color headerbar_fg_color {fg_str};")
        lines.append(f"@define-color popover_bg_color mix({bg_str},white,{mix_level});")
        lines.append(f"@define-color popover_fg_color {fg_str};")
        lines.append(
            f'@define-color view_bg_color mix({bg_str},{"black" if is_dark else "white"},{0.1 if is_dark else 0.3});'
        )
        lines.append(f"@define-color view_fg_color {fg_str};")
        lines.append(f"@define-color card_fg_color {fg_str};")
        lines.append(f"@define-color headerbar_border_color {fg_str};")
        lines.append(f"@define-color dialog_fg_color {fg_str};")
        lines.append("@define-color dark_fill_bg_color @headerbar_bg_color;")

        if is_dark:
            lines.append(f"@define-color dialog_bg_color mix({bg_str},white,0.07);")
            lines.append("@define-color card_bg_color alpha(white,0.08);")
        else:
            lines.append(f"@define-color dialog_bg_color {bg_str};")
            lines.append("@define-color card_bg_color alpha(white,0.8);")

        lines.append(f"@define-color sidebar_bg_color mix({bg_str},{alt},0.05);")
        lines.append(f"@define-color sidebar_backdrop_color mix({bg_str},{alt},0.1);")
        lines.append(f"@define-color sidebar_fg_color mix({fg_str},{alt},0.05);")
        lines.append(f"@define-color sidebar_shade_color mix({bg_str},black,0.1);")
        lines.append(f"@define-color sidebar_border_color mix({bg_str},black,0.1);")

        lines.append(f"@define-color secondary_sidebar_bg_color mix({bg_str},{alt},0.02);")
        lines.append(f"@define-color secondary_sidebar_backdrop_color mix({bg_str},{alt},0.05);")
        lines.append(f"@define-color secondary_sidebar_fg_color mix({fg_str},{alt},0.02);")
        lines.append(f"@define-color secondary_sidebar_shade_color mix({bg_str},black,0.1);")
        lines.append(f"@define-color secondary_sidebar_border_color mix({bg_str},black,0.1);")

        if accent:
            accent_str = self._rgba_opaque_str(accent)
            lines.append(f"@define-color accent_color {accent_str};")
            lines.append(f"@define-color accent_bg_color {accent_str};")
            lines.append("@define-color accent_fg_color white;")

        return "\n".join(lines)
