"""Wallpaper — Thumbnail-Grid, Klick setzt das Bild via waypaper (Backend swaybg).

RAM-KRITISCH: Eine frühere Version hat ~30 GB belegt, weil Bilder voll geladen
wurden. Deshalb gilt hier strikt:
  * Thumbnails NUR skaliert laden: `Pixbuf.new_from_file_at_scale(p, W, H, True)`.
    NIE `Pixbuf.new_from_file` oder `Gtk.Picture.set_filename` (laden voll!).
  * Aus dem skalierten Pixbuf eine `Gdk.Texture` machen, Pixbuf NICHT festhalten.
  * CAP: max. die ersten 60 Bilder rendern (sortiert), Rest als „+N more"-Hinweis.
  * Laden ASYNC in EINEM Worker-Thread: pro Bild Pixbuf im Thread skalieren,
    dann via GLib.idle_add die Karte bauen + einhängen — graduell, nichts blockt.

Diese Funktionalität lebt jetzt in `wallpaper_group()` und wird in die
Appearance-Seite eingefaltet. `build()` bleibt als dünner Wrapper bestehen.
"""
from __future__ import annotations

import threading

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import Adw, Gdk, GdkPixbuf, Gio, GLib, Gtk  # noqa: E402

from taninux.core import wallpaper
from taninux.gui.console import RunnerCard
from taninux.gui.process import ProcessRunner

# RAM-Grenzen: kleine Thumbnails, harter Cap auf die Anzahl gerenderter Bilder.
THUMB_W = 220
THUMB_H = 124
MAX_THUMBS = 60

# Höhenbegrenzung des Grids als Row — verhindert, dass die Gruppe die ganze
# Appearance-Seite sprengt; der Grid scrollt intern.
GRID_MIN_H = 300
GRID_MAX_H = 360


def wallpaper_group() -> Adw.PreferencesGroup:
    """Komplette Wallpaper-UI als eine PreferencesGroup (zum Einhängen in
    Appearance). Enthält die Steuerzeile als Rows und den RAM-sicheren
    Thumbnail-Grid in einem höhenbegrenzten, intern scrollenden Container."""
    if not wallpaper.available():
        return _missing_group()

    group = Adw.PreferencesGroup(
        title="Wallpaper",
        description="Click a thumbnail to set it.",
    )

    runner = ProcessRunner()
    mons = wallpaper.monitors()

    # -- Steuerzeile: Monitor-Combo als ComboRow ---------------------------
    monitor_row = Adw.ComboRow(title="Monitor")
    monitor_row.set_model(Gtk.StringList.new(mons))
    monitor_row.set_selected(0)
    group.add(monitor_row)

    # -- Steuerzeile: Aktionen als ActionRow mit Suffix-Buttons ------------
    actions_row = Adw.ActionRow(title="Wallpapers")
    random_btn = Gtk.Button(label="Random")
    random_btn.add_css_class("pill")
    random_btn.set_valign(Gtk.Align.CENTER)
    open_btn = Gtk.Button(label="Choose folder")
    open_btn.add_css_class("pill")
    open_btn.set_valign(Gtk.Align.CENTER)
    refresh_btn = Gtk.Button(icon_name="view-refresh-symbolic")
    refresh_btn.add_css_class("flat")
    refresh_btn.set_valign(Gtk.Align.CENTER)
    refresh_btn.set_tooltip_text("Reload thumbnails")
    for b in (random_btn, open_btn, refresh_btn):
        actions_row.add_suffix(b)
    group.add(actions_row)

    # -- Status-/Spinner-Zeile ---------------------------------------------
    status = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
    status.set_halign(Gtk.Align.START)
    status.set_margin_top(6)
    status.set_margin_bottom(6)
    spinner = Adw.Spinner()
    spinner.set_valign(Gtk.Align.CENTER)
    status_label = Gtk.Label(xalign=0)
    status_label.add_css_class("dim-label")
    status.append(spinner)
    status.append(status_label)

    # -- Grid in höhenbegrenzter ScrolledWindow ----------------------------
    flow = Gtk.FlowBox()
    flow.set_selection_mode(Gtk.SelectionMode.NONE)
    flow.set_homogeneous(True)
    flow.set_min_children_per_line(2)
    flow.set_max_children_per_line(4)
    flow.set_column_spacing(12)
    flow.set_row_spacing(12)
    flow.set_margin_top(6)
    flow.set_margin_bottom(6)
    flow.set_valign(Gtk.Align.START)

    scroller = Gtk.ScrolledWindow(hexpand=True)
    scroller.set_min_content_height(GRID_MIN_H)
    scroller.set_max_content_height(GRID_MAX_H)
    scroller.set_propagate_natural_height(False)
    scroller.set_child(flow)

    # Status + Grid zusammen in einer Row der Gruppe.
    grid_holder = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
    grid_holder.set_margin_top(6)
    grid_holder.append(status)
    grid_holder.append(scroller)
    group.add(grid_holder)

    # -- Aktionen ----------------------------------------------------------
    def _open_folder(_b: Gtk.Button) -> None:
        from pathlib import Path
        dialog = Gtk.FileDialog(title="Choose wallpaper folder")
        cur = str(wallpaper.wallpaper_dir())
        try:
            if Path(cur).is_dir():
                dialog.set_initial_folder(Gio.File.new_for_path(cur))
        except GLib.Error:
            pass

        def done(d, res) -> None:
            try:
                f = d.select_folder_finish(res)
            except GLib.Error:
                return
            if f and f.get_path():
                wallpaper.set_folder(f.get_path())
                _load()

        dialog.select_folder(_b.get_root(), None, done)

    def _random(_b: Gtk.Button) -> None:
        runner.run(wallpaper.random_argv())

    open_btn.connect("clicked", _open_folder)
    random_btn.connect("clicked", _random)

    # Veränderlicher Zustand fürs Markieren der aktuellen Karte.
    state = {"current": wallpaper.current(), "cards": {}}  # path-str -> card

    def _selected_monitor() -> str:
        return mons[monitor_row.get_selected()]

    def _set(path) -> None:
        runner.run(wallpaper.set_argv(path, _selected_monitor()))

    def _mark_current() -> None:
        cur = state["current"]
        for p, card in state["cards"].items():
            if p == cur:
                card.add_css_class("accent")
            else:
                card.remove_css_class("accent")

    def on_done(_r: ProcessRunner, code: int) -> None:
        if code == 0:
            # Leichtgewichtiger Reload des aktuellen Pfads (kein Bildladen).
            state["current"] = wallpaper.current()
            _mark_current()

    runner.connect("done", on_done)

    # -- ASYNC Thumbnail-Laden ---------------------------------------------
    # Worker erzeugt skalierte Pixbufs für die ersten MAX_THUMBS Pfade; pro Bild
    # baut idle_add die Karte (Picture aus Texture) und hängt sie ein. Der
    # Pixbuf bleibt lokal -> wird nach Texture-Erzeugung freigegeben.
    load_token = {"id": 0}

    def _clear_flow() -> None:
        child = flow.get_first_child()
        while child is not None:
            nxt = child.get_next_sibling()
            flow.remove(child)
            child = nxt
        state["cards"].clear()

    def _make_card(path, texture) -> Gtk.Widget:
        card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
        card.add_css_class("card")
        card.set_overflow(Gtk.Overflow.HIDDEN)

        pic = Gtk.Picture()
        pic.set_content_fit(Gtk.ContentFit.COVER)
        pic.set_size_request(THUMB_W, THUMB_H)
        pic.set_paintable(texture)
        card.append(pic)

        name = Gtk.Label(label=path.name, xalign=0)
        name.set_ellipsize(3)  # Pango.EllipsizeMode.END
        name.add_css_class("caption")
        name.set_margin_start(6)
        name.set_margin_end(6)
        name.set_margin_bottom(4)
        card.append(name)

        # Klick = setzen. GestureClick auf der Karte.
        click = Gtk.GestureClick()
        click.connect("released", lambda *_a, p=path: _set(p))
        card.add_controller(click)
        card.set_cursor(Gdk.Cursor.new_from_name("pointer", None))
        return card

    def _add_card(path, texture, token: int) -> bool:
        if token != load_token["id"]:
            return False  # veralteter Ladevorgang
        card = _make_card(path, texture)
        flow.append(card)
        state["cards"][str(path)] = card
        if str(path) == state["current"]:
            card.add_css_class("accent")
        return False

    def _add_more(extra: int, token: int) -> bool:
        if token != load_token["id"]:
            return False
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        box.set_valign(Gtk.Align.CENTER)
        box.set_halign(Gtk.Align.CENTER)
        box.set_size_request(THUMB_W, THUMB_H)
        lbl = Gtk.Label(label=f"… +{extra} more")
        lbl.add_css_class("dim-label")
        more_btn = Gtk.Button(label="Choose folder")
        more_btn.add_css_class("pill")
        more_btn.connect("clicked", _open_folder)
        box.append(lbl)
        box.append(more_btn)
        flow.append(box)
        return False

    def _finish(token: int) -> bool:
        if token != load_token["id"]:
            return False
        spinner.set_visible(False)
        return False

    def _worker(items, extra: int, token: int) -> None:
        for path in items:
            if token != load_token["id"]:
                return
            try:
                # NUR skaliert laden — niemals voll!
                pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    str(path), THUMB_W, THUMB_H, True
                )
            except (GLib.Error, OSError):
                continue
            texture = Gdk.Texture.new_for_pixbuf(pb)
            pb = None  # Pixbuf freigeben, nur die Texture lebt weiter
            GLib.idle_add(_add_card, path, texture, token)
        if extra > 0:
            GLib.idle_add(_add_more, extra, token)
        GLib.idle_add(_finish, token)

    def _load() -> None:
        load_token["id"] += 1
        token = load_token["id"]
        _clear_flow()
        state["current"] = wallpaper.current()
        all_paths = wallpaper.list_wallpapers()
        total = len(all_paths)
        if total == 0:
            spinner.set_visible(False)
            status_label.set_text(
                f"No images in {wallpaper.wallpaper_dir()}. "
                "Use Open folder to add wallpapers."
            )
            return
        items = all_paths[:MAX_THUMBS]
        extra = max(0, total - MAX_THUMBS)
        spinner.set_visible(True)
        shown = len(items)
        status_label.set_text(
            f"{shown} of {total} wallpapers"
            if extra else f"{total} wallpapers"
        )
        threading.Thread(
            target=_worker, args=(items, extra, token), daemon=True
        ).start()

    refresh_btn.connect("clicked", lambda _b: _load())
    GLib.idle_add(lambda: (_load(), False)[1])

    return group


def build() -> Gtk.Widget:
    """Dünner Wrapper: eigene PreferencesPage, die nur die wallpaper_group()
    enthält. Bleibt erhalten, falls noch jemand build() aufruft."""
    page = Adw.PreferencesPage()
    page.add(wallpaper_group())
    return page


def _missing_group() -> Adw.PreferencesGroup:
    """Gruppe mit Install-Hinweis + RunnerCard, falls waypaper fehlt."""
    group = Adw.PreferencesGroup(
        title="Wallpaper",
        description="waypaper is not installed. Install it to manage wallpapers.",
    )
    row = Adw.ActionRow()
    row.set_child(RunnerCard(
        "Install waypaper",
        ["pacman", "-S", "--needed", "--noconfirm", "waypaper"],
        privileged=True,
    ))
    group.add(row)
    return group
