"""Browse — Pakete suchen und installieren/entfernen.

Default-Scope: offizielle Repos + Flatpak. AUR ist Opt-in (Schalter „Sources")
und braucht paru. Repo-Pakete laufen über den Helper (pacman), AUR über paru,
Flatpak als User. Async via core.search.
"""
from __future__ import annotations

import threading

import gi

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

from taninux.core import panel
from taninux.core import search as pkgsearch
from taninux.gui.console import LogConsole, log_expander
from taninux.gui.pages.appdetail import open_app_detail
from taninux.gui.process import ProcessRunner


def build() -> Gtk.Widget:
    page = Adw.PreferencesPage()

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

    token = {"n": 0}  # gegen veraltete Such-Antworten

    g_sources = Adw.PreferencesGroup(title="Sources")
    aur_row = Adw.SwitchRow(
        title="Arch User Repository (AUR)",
        subtitle="Community packages built from source — requires paru.",
    )
    aur_row.set_active(panel.include_aur())
    g_sources.add(aur_row)
    page.add(g_sources)

    g_search = Adw.PreferencesGroup()
    entry = Gtk.SearchEntry()
    entry.set_placeholder_text("Search apps and packages")
    entry.set_hexpand(True)
    g_search.add(entry)
    page.add(g_search)

    g_results = Adw.PreferencesGroup(title="Results")
    page.add(g_results)
    rows: list[Gtk.Widget] = []

    page.add(log_expander(console))

    def _clear() -> None:
        for r in rows:
            g_results.remove(r)
        rows.clear()

    def _placeholder(text: str) -> None:
        _clear()
        row = Adw.ActionRow(title=text)
        g_results.add(row)
        rows.append(row)

    def _act(item: pkgsearch.Result, btn: Gtk.Button) -> None:
        argv, priv = (pkgsearch.remove_argv if item.installed else pkgsearch.install_argv)(item)
        console.clear()
        btn.set_sensitive(False)
        runner.run(argv, privileged=priv)

    def _open(item: pkgsearch.Result) -> None:
        flatpak = item.source == "flatpak"
        open_app_detail(
            page,
            title=item.extra.get("title", item.name),
            icon=item.name,
            repo_name=None if flatpak else item.name,
            flatpak_id=item.name if flatpak else None,
            remote=item.repo or "flathub",
            on_changed=lambda: _run_search(entry.get_text()),
        )

    def _render(items: list[pkgsearch.Result]) -> None:
        _clear()
        if not items:
            _placeholder("No packages found")
            return
        g_results.set_title(f"Results ({len(items)})")
        for item in items:
            subtitle = f"{item.repo}  ·  {item.version}"
            if item.description:
                subtitle += f"  —  {item.description}"
            row = Adw.ActionRow(title=item.extra.get("title", item.name))
            row.set_subtitle(subtitle)
            row.set_subtitle_lines(2)
            row.set_activatable(True)
            row.connect("activated", lambda _r, it=item: _open(it))
            tag = Gtk.Label(label=item.source)
            tag.add_css_class("dim-label")
            tag.add_css_class("caption")
            tag.set_valign(Gtk.Align.CENTER)
            row.add_prefix(tag)
            btn = Gtk.Button(label="Remove" if item.installed else "Install")
            btn.add_css_class("flat" if item.installed else "suggested-action")
            btn.set_valign(Gtk.Align.CENTER)
            btn.connect("clicked", lambda b, it=item: _act(it, b))
            row.add_suffix(btn)
            g_results.add(row)
            rows.append(row)

    def _run_search(query: str) -> None:
        token["n"] += 1
        my = token["n"]
        if len(query.strip()) < 2:
            g_results.set_title("Results")
            _clear()
            return
        _placeholder(f"Searching “{query}” …")

        def worker() -> None:
            try:
                items = pkgsearch.search(query)
            except Exception:  # noqa: BLE001
                items = []

            def deliver() -> bool:
                if my == token["n"]:
                    _render(items)
                return False

            GLib.idle_add(deliver)

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

    def on_done(_r: ProcessRunner, code: int) -> None:
        console.append_line("✔ done" if code == 0 else f"✘ exit {code}")
        _run_search(entry.get_text())  # Status (installed/…) auffrischen

    debounce = {"id": 0}

    def _on_changed(e: Gtk.SearchEntry) -> None:
        if debounce["id"]:
            GLib.source_remove(debounce["id"])
        text = e.get_text()
        debounce["id"] = GLib.timeout_add(
            400, lambda: (debounce.update(id=0), _run_search(text), False)[-1]
        )

    def _on_aur(row: Adw.SwitchRow, _pspec) -> None:
        panel.set_include_aur(row.get_active())
        _run_search(entry.get_text())

    runner.connect("done", on_done)
    aur_row.connect("notify::active", _on_aur)
    entry.connect("activate", lambda e: _run_search(e.get_text()))
    entry.connect("search-changed", _on_changed)
    return page
