"""App-Detailansicht (Adw.Dialog) im GNOME-Software-Stil.

Zeigt Icon · Name · Beschreibung · Version/Größe/Lizenz/Website und lässt die
Installationsquelle wählen (Repo · Flatpak · AUR falls aktiviert). Quellen und
Metadaten kommen async aus core.appinfo.
"""
from __future__ import annotations

import threading

import gi

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

from taninux.core import appinfo, panel
from taninux.gui.console import LogConsole, log_expander
from taninux.gui.process import ProcessRunner


def _icon_name(name: str, fallback: str = "package-x-generic-symbolic") -> str:
    theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
    return name if (theme and theme.has_icon(name)) else fallback


def open_app_detail(parent: Gtk.Widget, *, title: str, icon: str,
                    repo_name: str | None = None, flatpak_id: str | None = None,
                    remote: str = "flathub", on_changed=None) -> None:
    dialog = Adw.Dialog()
    dialog.set_title(title)
    dialog.set_content_width(540)
    dialog.set_content_height(640)

    console = LogConsole()
    runner = ProcessRunner()
    runner.connect("line", lambda _r, line: console.append_line(line))

    cand: list[appinfo.Detail] = []

    page = Adw.PreferencesPage()
    tv = Adw.ToolbarView()
    tv.add_top_bar(Adw.HeaderBar())
    tv.set_content(page)
    dialog.set_child(tv)

    # -- Kopf: Icon + Name + Summary --------------------------------------
    hg = Adw.PreferencesGroup()
    head = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=16)
    head.set_margin_top(8)
    head.set_margin_bottom(8)
    img = Gtk.Image.new_from_icon_name(_icon_name(icon))
    img.set_pixel_size(72)
    head.append(img)
    tbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
    tbox.set_valign(Gtk.Align.CENTER)
    name_lbl = Gtk.Label(label=title, xalign=0.0)
    name_lbl.add_css_class("title-1")
    summary_lbl = Gtk.Label(label="", xalign=0.0, wrap=True)
    summary_lbl.add_css_class("dim-label")
    tbox.append(name_lbl)
    tbox.append(summary_lbl)
    head.append(tbox)
    hg.add(head)
    page.add(hg)

    # -- Quelle + Aktion --------------------------------------------------
    sg = Adw.PreferencesGroup(title="Install")
    source_combo = Adw.ComboRow(title="Source")
    sg.add(source_combo)
    action_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
    action_box.set_margin_top(10)
    action_btn = Gtk.Button(label="Install")
    action_btn.add_css_class("suggested-action")
    action_btn.add_css_class("pill")
    action_btn.set_sensitive(False)
    spinner = Adw.Spinner()
    spinner.set_valign(Gtk.Align.CENTER)
    spinner.set_visible(False)
    action_box.append(action_btn)
    action_box.append(spinner)
    sg.add(action_box)
    page.add(sg)

    # -- Details ----------------------------------------------------------
    dg = Adw.PreferencesGroup(title="Details")
    page.add(dg)
    detail_rows: list[Gtk.Widget] = []

    page.add(log_expander(console))

    def _selected() -> appinfo.Detail | None:
        idx = source_combo.get_selected()
        return cand[idx] if 0 <= idx < len(cand) else None

    def _static_row(label: str, value: str, *, link: bool = False) -> Gtk.Widget:
        row = Adw.ActionRow(title=label)
        if link:
            btn = Gtk.LinkButton(uri=value, label=value)
            btn.set_valign(Gtk.Align.CENTER)
            row.add_suffix(btn)
        else:
            lab = Gtk.Label(label=value, xalign=1.0, wrap=True)
            lab.add_css_class("dim-label")
            lab.set_selectable(True)
            row.add_suffix(lab)
        return row

    def _show_details(d: appinfo.Detail) -> None:
        for r in detail_rows:
            dg.remove(r)
        detail_rows.clear()
        summary_lbl.set_label(d.summary or "")
        fields = [
            ("Version", d.version),
            ("Download size", d.size),
            ("Installed size", d.installed_size),
            ("License", d.license),
        ]
        for lab, val in fields:
            if val:
                r = _static_row(lab, val)
                dg.add(r)
                detail_rows.append(r)
        if d.url:
            r = _static_row("Website", d.url, link=True)
            dg.add(r)
            detail_rows.append(r)
        if d.installed:
            action_btn.set_label("Remove")
            action_btn.remove_css_class("suggested-action")
            action_btn.add_css_class("destructive-action")
        else:
            action_btn.set_label("Install")
            action_btn.remove_css_class("destructive-action")
            action_btn.add_css_class("suggested-action")
        action_btn.set_sensitive(True)

    def _on_source(_combo, _pspec=None) -> None:
        d = _selected()
        if d:
            _show_details(d)

    source_combo.connect("notify::selected", _on_source)

    def _on_action(_b) -> None:
        d = _selected()
        if not d:
            return
        argv, priv = (appinfo.remove_argv if d.installed else appinfo.install_argv)(d)
        action_btn.set_sensitive(False)
        spinner.set_visible(True)
        console.clear()
        runner.run(argv, privileged=priv)

    action_btn.connect("clicked", _on_action)

    def on_done(_r, code: int) -> None:
        console.append_line("✔ done" if code == 0 else f"✘ exit {code}")
        spinner.set_visible(False)
        if callable(on_changed):
            on_changed()
        _load()  # Quellen/Installed-Status neu ermitteln

    runner.connect("done", on_done)

    def _populate(found: list[appinfo.Detail]) -> None:
        cand.clear()
        cand.extend(found)
        if not found:
            source_combo.set_model(Gtk.StringList.new(["Not available"]))
            source_combo.set_sensitive(False)
            action_btn.set_sensitive(False)
            summary_lbl.set_label("This app could not be found in any enabled source.")
            return
        source_combo.set_sensitive(True)
        labels = []
        for d in found:
            lab = appinfo.SOURCE_LABEL.get(d.source, d.source)
            labels.append(f"{lab} · {d.version}" if d.version else lab)
        source_combo.set_model(Gtk.StringList.new(labels))
        # bevorzugt eine bereits installierte Quelle vorwählen
        sel = next((i for i, d in enumerate(found) if d.installed), 0)
        source_combo.set_selected(sel)
        _show_details(found[sel])

    def _load() -> None:
        include_aur = panel.include_aur()

        def worker() -> None:
            try:
                found = appinfo.candidates(
                    repo_name=repo_name, flatpak_id=flatpak_id,
                    remote=remote, include_aur=include_aur,
                )
            except Exception:  # noqa: BLE001
                found = []
            GLib.idle_add(lambda: (_populate(found), False)[1])

        threading.Thread(target=worker, daemon=True).start()

    _load()
    dialog.present(parent)
