## Copyright (C) 2026  Solaar Contributors https://pwr-solaar.github.io/Solaar/
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""Theme-aware loader for Solaar's per-key UI icons.

Loads SVG icons from ``share/solaar/icons/`` and recolors them at load
time to match the active GTK theme's text foreground, by substituting
``currentColor`` in the SVG before passing it to GdkPixbuf. GTK's stock
symbolic loader is bypassed because it only recolors specific palette
fill stand-ins and ignores ``stroke="currentColor"``.
"""

from __future__ import annotations

import logging

from pathlib import Path

import gi

gi.require_version("Gtk", "3.0")
from gi.repository import GdkPixbuf  # NOQA: E402
from gi.repository import Gio  # NOQA: E402
from gi.repository import Gtk  # NOQA: E402

logger = logging.getLogger(__name__)

ICON_PIXEL_SIZE = 22

_search_path_added = False


def ensure_icon_path() -> None:
    """Register share/solaar/icons with the default GtkIconTheme so our
    custom symbolic tool icons resolve by name. Idempotent."""
    global _search_path_added
    if _search_path_added:
        return
    theme = Gtk.IconTheme.get_default()
    existing = set(theme.get_search_path() or [])
    # _icons.py: lib/solaar/ui/perkey/_icons.py -> parents[4] = repo root
    candidates = [
        Path(__file__).resolve().parents[4] / "share" / "solaar" / "icons",
    ]
    for c in candidates:
        if c.is_dir() and str(c) not in existing:
            theme.append_search_path(str(c))
    _search_path_added = True


def themed_icon_image(icon_name: str, style_widget: Gtk.Widget) -> Gtk.Image | None:
    """Load a Solaar tool icon and recolor it to match the given widget's
    text foreground color, so the icons follow the active GTK theme
    (light / dark / custom). Returns None if the icon can't be loaded.
    """
    ensure_icon_path()
    theme = Gtk.IconTheme.get_default()
    icon_info = theme.lookup_icon(icon_name, ICON_PIXEL_SIZE, Gtk.IconLookupFlags.FORCE_SIZE)
    if icon_info is None:
        return None
    path = icon_info.get_filename()
    if not path:
        return None
    fg = style_widget.get_style_context().get_color(Gtk.StateFlags.NORMAL)
    color = f"#{int(fg.red * 255):02x}{int(fg.green * 255):02x}{int(fg.blue * 255):02x}"
    try:
        with open(path, "r", encoding="utf-8") as f:
            svg = f.read()
        svg = svg.replace("currentColor", color)
        stream = Gio.MemoryInputStream.new_from_data(svg.encode("utf-8"))
        pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, ICON_PIXEL_SIZE, ICON_PIXEL_SIZE, True)
        return Gtk.Image.new_from_pixbuf(pixbuf)
    except Exception as e:
        logger.debug("recolor failed for %s: %s", icon_name, e)
        return None


def _fg_color_key(widget: Gtk.Widget) -> tuple[float, float, float]:
    fg = widget.get_style_context().get_color(Gtk.StateFlags.NORMAL)
    return (round(fg.red, 3), round(fg.green, 3), round(fg.blue, 3))


def attach_themed_icon(button: Gtk.Container, icon_name: str) -> int | None:
    """Add a themed icon to `button` and re-render it whenever the active
    GTK theme changes the button's foreground color. Returns the
    style-updated signal handler ID, or None if the icon couldn't be
    loaded (in which case the button is left unchanged so the caller can
    fall back to a text label).

    Listening to the button's own ``style-updated`` signal — instead of
    ``Gtk.Settings notify::gtk-theme-name`` — means we read the
    foreground color *after* GTK has re-resolved CSS for the new theme.
    Subscribing to the Settings notify fires too early; it returns the
    stale (pre-switch) color and produces icons that all settle on the
    previous theme's tone. We guard the rebuild with a per-button color
    key so unrelated style updates (hover, focus, active) don't trigger
    needless re-renders.
    """
    image = themed_icon_image(icon_name, button)
    if image is None:
        return None
    button.add(image)
    image.show()
    state = {"color_key": _fg_color_key(button)}

    def _refresh(_widget) -> None:
        new_key = _fg_color_key(button)
        if new_key == state["color_key"]:
            return
        state["color_key"] = new_key
        new_image = themed_icon_image(icon_name, button)
        if new_image is None:
            return
        old = button.get_child()
        if old is not None:
            button.remove(old)
        button.add(new_image)
        new_image.show()

    return button.connect("style-updated", _refresh)
