"""Paket-Suche über Repo + AUR (paru) und Flatpak. UI-frei.

`paru -Ss` deckt offizielle Repos UND AUR in einem Aufruf ab; Flatpak wird
separat durchsucht. Ergebnisse werden zu einem einheitlichen `Result` normiert.

Install/Remove laufen als User: paru eskaliert den pacman-Teil selbst über
`--sudo pkexec` (grafischer Polkit-Dialog, kein TTY nötig); Flatpak braucht
ohnehin kein root.
"""
from __future__ import annotations

import re
import shutil
import subprocess
from dataclasses import dataclass, field

MAX_RESULTS = 60

_HEAD = re.compile(r"^(?P<repo>[\w.-]+)/(?P<name>\S+)\s+(?P<ver>\S+)(?P<rest>.*)$")


@dataclass
class Result:
    source: str               # 'repo' | 'aur' | 'flatpak'
    name: str                 # Paketname (Repo/AUR) bzw. Flatpak-App-ID
    version: str
    description: str = ""
    installed: bool = False
    repo: str = ""            # Repo-Name bzw. Flatpak-Remote
    extra: dict[str, str] = field(default_factory=dict)


def _helper() -> str | None:
    for h in ("paru", "yay"):
        if shutil.which(h):
            return h
    return None


def _search_pacman(query: str, include_aur: bool) -> list[Result]:
    # AUR nur via paru/yay; ohne AUR plain pacman (kein paru/base-devel nötig).
    helper = _helper() if include_aur else None
    argv = [helper, "-Ss", query] if helper else ["pacman", "-Ss", query]
    try:
        r = subprocess.run(
            argv, capture_output=True, text=True, check=False, timeout=45,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    if r.returncode not in (0, 1):
        return []

    out: list[Result] = []
    cur: Result | None = None
    for line in r.stdout.splitlines():
        if line[:1].isspace():
            if cur is not None and not cur.description:
                cur.description = line.strip()
            continue
        m = _HEAD.match(line)
        if not m:
            continue
        repo = m.group("repo")
        cur = Result(
            source="aur" if repo == "aur" else "repo",
            name=m.group("name"),
            version=m.group("ver"),
            installed="[installed" in m.group("rest").lower(),
            repo=repo,
        )
        out.append(cur)
    return out


def _flatpak_installed_ids() -> set[str]:
    try:
        r = subprocess.run(
            ["flatpak", "list", "--app", "--columns=application"],
            capture_output=True, text=True, check=False, timeout=10,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return set()
    return {ln.strip() for ln in r.stdout.splitlines() if ln.strip()}


def _search_flatpak(query: str) -> list[Result]:
    if not shutil.which("flatpak"):
        return []
    try:
        r = subprocess.run(
            ["flatpak", "search",
             "--columns=application,version,name,description,remotes", query],
            capture_output=True, text=True, check=False, timeout=30,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    if r.returncode != 0:
        return []

    installed = _flatpak_installed_ids()
    out: list[Result] = []
    for line in r.stdout.splitlines():
        s = line.strip()
        if not s or s.lower().startswith("application"):
            continue
        parts = s.split("\t")
        if len(parts) < 1:
            continue
        app = parts[0]
        ver = parts[1] if len(parts) > 1 and parts[1] != "" else "?"
        name = parts[2] if len(parts) > 2 else app
        desc = parts[3] if len(parts) > 3 else ""
        remote = parts[4].split(",")[0] if len(parts) > 4 and parts[4] else "flathub"
        out.append(Result(
            source="flatpak",
            name=app,
            version=ver,
            description=desc if desc and desc != name else "",
            installed=app in installed,
            repo=remote,
            extra={"title": name},
        ))
    return out


def search(query: str, include_aur: bool | None = None) -> list[Result]:
    """Repo (+AUR falls Opt-in) und Flatpak durchsuchen, zusammengeführt."""
    if include_aur is None:
        from taninux.core import panel
        include_aur = panel.include_aur()
    query = query.strip()
    if len(query) < 2:
        return []
    results = _search_pacman(query, include_aur) + _search_flatpak(query)
    return results[:MAX_RESULTS]


def install_argv(item: Result) -> tuple[list[str], bool]:
    """(argv, privileged). Repo läuft über den Helper (pacman), AUR über paru."""
    if item.source == "flatpak":
        return ["flatpak", "install", "--assumeyes", item.repo, item.name], False
    if item.source == "aur":
        helper = _helper() or "paru"
        return [helper, "--sudo", "pkexec", "-S", "--noconfirm", item.name], False
    return ["pacman", "-S", "--needed", "--noconfirm", item.name], True


def remove_argv(item: Result) -> tuple[list[str], bool]:
    """(argv, privileged). Entfernen baut nie — Repo+AUR über den Helper."""
    if item.source == "flatpak":
        return ["flatpak", "uninstall", "--assumeyes", item.name], False
    return ["pacman", "-Rns", "--noconfirm", item.name], True
