#!/usr/bin/env python3
import gi, sys, subprocess, threading, os, re, shutil, shlex, json, locale, urllib.request, tempfile
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')

from gi.repository import Gtk, Adw, Gio, GLib, Gdk
from dataclasses import dataclass, fields
from typing import List, Optional

# ---------------------------------------------------
# Konfiguration & Pfade
# ---------------------------------------------------
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
JSON_PATH = os.path.join(BASE_DIR, "apps.json")
SETTINGS_PATH = os.path.join(BASE_DIR, "settings.json")

# ----------------------------- Übersetzungssystem -----------------------------
TRANS = {
    "en": {
        "Void Software Store": "Void Software Store",
        "System Update": "System Update",
        "Suche": "Search",
        "Programme suchen...": "Search programs...",
        "Ansicht": "View",
        "Sortierung": "Sort Order",
        "Sprache": "Language",
        "Erscheinungsbild": "Appearance",
        "Systemstandard": "System Default",
        "Hell": "Light",
        "Dunkel": "Dark",
        "A–Z": "A–Z",
        "Zuletzt hinzugefügt": "Recently Added",
        "Kategorien": "Categories",
        "Alle": "All",
        "Installiert": "Installed",
        "Büro": "Office",
        "Grafik": "Graphics",
        "Multimedia": "Multimedia",
        "Internet": "Internet",
        "Gaming": "Gaming",
        "Tools": "Tools",
        "System": "System",
        "Aktionen": "Actions",
        "Entfernen": "Remove",
        "Installieren": "Install",
        "Fertig. Taste drücken...": "Done. Press any key...",
        "XBPS Paket": "XBPS Package",
        "Flatpak": "Flatpak",
        "Repositories": "Repositories",
        "Void System Repos": "Void System Repos",
        "Non-Free": "Non-Free (Proprietary)",
        "Multilib": "Multilib (32-bit)",
        "Flathub (Flatpak)": "Flathub (Flatpak)",
        "Suchergebnisse": "Search Results",
        "Details": "Details",
        "Version (XBPS)": "Version (XBPS)",
        "Version (Flatpak)": "Version (Flatpak)",
        "Lade...": "Loading...",
        "Unbekannt": "Unknown",
        "Quelle": "Source",
        "Dienste": "Services",
        "Dienst aktivieren": "Enable Service",
        "Kopieren": "Copy"
    },
    "de": {
        "Void Software Store": "Void Software Store",
        "System Update": "Systemaktualisierung",
        "Suche": "Suche",
        "Programme suchen...": "Programme suchen...",
        "Ansicht": "Ansicht",
        "Sortierung": "Sortierung",
        "Sprache": "Sprache",
        "Erscheinungsbild": "Erscheinungsbild",
        "Systemstandard": "Systemstandard",
        "Hell": "Hell",
        "Dunkel": "Dunkel",
        "A–Z": "A–Z",
        "Zuletzt hinzugefügt": "Zuletzt hinzugefügt",
        "Kategorien": "Kategorien",
        "Alle": "Alle",
        "Installiert": "Installiert",
        "Büro": "Büro",
        "Grafik": "Grafik",
        "Multimedia": "Multimedia",
        "Internet": "Internet",
        "Gaming": "Gaming",
        "Tools": "Werkzeuge",
        "System": "System",
        "Aktionen": "Aktionen",
        "Entfernen": "Entfernen",
        "Installieren": "Installieren",
        "Fertig. Taste drücken...": "Fertig. Taste drücken...",
        "XBPS Paket": "XBPS Paket",
        "Flatpak": "Flatpak",
        "Repositories": "Paketquellen",
        "Void System Repos": "Void System Repos",
        "Non-Free": "Non-Free (Proprietär)",
        "Multilib": "Multilib (32-Bit)",
        "Flathub (Flatpak)": "Flathub (Flatpak)",
        "Suchergebnisse": "Suchergebnisse",
        "Details": "Details",
        "Version (XBPS)": "Version (XBPS)",
        "Version (Flatpak)": "Version (Flatpak)",
        "Lade...": "Lade...",
        "Unbekannt": "Unbekannt",
        "Quelle": "Quelle",
        "Dienste": "Dienste (Runit)",
        "Dienst aktivieren": "Dienst aktivieren",
        "Kopieren": "Kopieren"
    }
}

CURRENT_LANG = "en"

def tr(text):
    global CURRENT_LANG
    lang = CURRENT_LANG
    if lang not in TRANS: lang = "en"
    return TRANS[lang].get(text, TRANS["en"].get(text, text))

def get_system_lang():
    try:
        lang_code = locale.getlocale()[0]
        if not lang_code: lang_code = os.environ.get('LANG', 'en')
        if lang_code.startswith("de"): return "de"
    except: pass
    return "en"

def load_all_settings():
    defaults = {"language": "auto", "theme": "dark"}
    if os.path.exists(SETTINGS_PATH):
        try:
            with open(SETTINGS_PATH, 'r') as f:
                data = json.load(f)
                defaults.update(data)
        except: pass
    return defaults

def save_setting(key, value):
    data = load_all_settings()
    data[key] = value
    try:
        with open(SETTINGS_PATH, 'w') as f:
            json.dump(data, f)
    except: pass

@dataclass
class App:
    name: str
    description: str
    category: str
    xbps_name: Optional[str] = None
    flatpak_id: Optional[str] = None
    icon: str = "application-x-executable"
    install_name: Optional[str] = None 
    service_name: Optional[str] = None
    script_file: Optional[str] = None
    is_external: bool = False

class InstallCache:
    def __init__(self):
        self.xbps = set()
        self.flatpaks = set()
        self.flathub_active = False
        self.ready = False

    def refresh(self):
        self.xbps.clear()
        self.flatpaks.clear()
        try:
            out = subprocess.run(['xbps-query', '-l'], capture_output=True, text=True, timeout=5)
            if out.returncode == 0:
                for line in out.stdout.splitlines():
                    parts = line.strip().split()
                    if len(parts) >= 2:
                        pkg = parts[1]
                        m = re.match(r'^(.+?)-\d', pkg)
                        base = m.group(1) if m else pkg.split('-')[0]
                        self.xbps.add(base.lower())
        except: pass
        if shutil.which("flatpak"):
            try:
                out = subprocess.run(['flatpak', 'list', '--app', '--columns=application'], capture_output=True, text=True, timeout=5)
                if out.returncode == 0:
                    for appid in out.stdout.splitlines():
                        if appid.strip(): self.flatpaks.add(appid.strip())
                remote_out = subprocess.run(['flatpak', 'remote-list'], capture_output=True, text=True, timeout=5)
                if "flathub" in remote_out.stdout.lower():
                    self.flathub_active = True
            except: pass
        self.ready = True

class VoidSoftwareStore(Adw.Application):
    def __init__(self):
        super().__init__(application_id="com.voidlinux.softwarestore")
        global CURRENT_LANG
        settings = load_all_settings()
        if settings["language"] == "auto": CURRENT_LANG = get_system_lang()
        else: CURRENT_LANG = settings["language"]
        self.style_manager = Adw.StyleManager.get_default()
        self._apply_theme(settings["theme"])
        self.apps_data = self._load_apps_data()

    def _apply_theme(self, theme_name):
        themes = {"auto": Adw.ColorScheme.DEFAULT, "light": Adw.ColorScheme.FORCE_LIGHT, "dark": Adw.ColorScheme.FORCE_DARK}
        self.style_manager.set_color_scheme(themes.get(theme_name, Adw.ColorScheme.DEFAULT))

    def _load_apps_data(self) -> List[App]:
        loaded_apps = []
        if not os.path.exists(JSON_PATH):
            return [App("Firefox", "Webbrowser", "Internet", xbps_name="firefox", icon="firefox")]
        try:
            with open(JSON_PATH, 'r', encoding='utf-8') as f:
                data = json.load(f)
                valid_fields = {f.name for f in fields(App)}
                for entry in data:
                    filtered_entry = {k: v for k, v in entry.items() if k in valid_fields}
                    loaded_apps.append(App(**filtered_entry))
            return loaded_apps
        except: return [App("Firefox", "Webbrowser", "Internet", xbps_name="firefox", icon="firefox")]

    def do_activate(self):
        win = MainWindow(application=self, apps_data=self.apps_data)
        win.present()

class MainWindow(Adw.ApplicationWindow):
    def __init__(self, application, apps_data):
        super().__init__(application=application)
        self.apps_data = apps_data
        self._added_index = {app.name: idx for idx, app in enumerate(self.apps_data)}
        self.current_category = "Installiert"
        self.install_cache = InstallCache()
        self.repo_switches = {}
        self._is_updating_ui = False
        self._search_timer_id = 0
        self.external_results = []
        
        # --- ICON THEME PAPIRUS ERZWINGEN ---
        settings = Gtk.Settings.get_default()
        if os.path.exists("/usr/share/icons/Papirus"):
            settings.set_property("gtk-icon-theme-name", "Papirus-Dark")
        elif os.path.exists("/usr/share/icons/Papirus-Light"):
            settings.set_property("gtk-icon-theme-name", "Papirus-Light")
        
        # Eigene Icons einbinden
        display = self.get_display()
        icon_theme = Gtk.IconTheme.get_for_display(display)
        local_icons = os.path.join(BASE_DIR, "icons")
        if os.path.exists(local_icons): icon_theme.add_search_path(local_icons)
        # ------------------------------------
        css_provider = Gtk.CssProvider()
        css_provider.load_from_path(os.path.join(BASE_DIR, "styles.css"))
        Gtk.StyleContext.add_provider_for_display(
            self.get_display(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        self.set_default_size(1250, 750)
        self._setup_ui()
        GLib.idle_add(self._filter_apps)
        GLib.idle_add(self._select_category, "Installiert")
        threading.Thread(target=self._warm_up_cache, daemon=True).start()

    def _warm_up_cache(self):
        self.install_cache.refresh()
        GLib.idle_add(self._update_repo_switches)
        GLib.idle_add(self._filter_apps)

    def _setup_ui(self):
        self.set_title(tr("Void Software Store"))
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.set_content(main_box)
        
        hb = Adw.HeaderBar()
        self._main_title = Gtk.Label(label=tr("Void Software Store"), css_classes=["title-2"])
        hb.set_title_widget(self._main_title)
        self.back_button = Gtk.Button(icon_name="go-previous-symbolic", visible=False)
        self.back_button.connect("clicked", lambda w: self._show_grid())
        hb.pack_start(self.back_button)
        up_btn = Gtk.Button(label=tr("System Update"), css_classes=["suggested-action"])
        up_btn.connect("clicked", self._on_update_clicked)
        hb.pack_end(up_btn)
        main_box.append(hb)
        
        paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL, position=320)
        main_box.append(paned)
        paned.set_start_child(self._create_sidebar())
        
        self.main_stack = Adw.ViewStack()
        self.apps_flowbox = Gtk.FlowBox(valign=Gtk.Align.START, max_children_per_line=10, min_children_per_line=2, row_spacing=15, column_spacing=15, selection_mode=Gtk.SelectionMode.NONE, margin_top=20, margin_bottom=20, margin_start=20, margin_end=20)
        scroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER, child=self.apps_flowbox)
        self.main_stack.add_named(scroll, "grid")
        
        self.detail_scroll = Gtk.ScrolledWindow(hscrollbar_policy=Gtk.PolicyType.NEVER)
        self.main_stack.add_named(self.detail_scroll, "details")
        paned.set_end_child(self.main_stack)

        # --- FOOTER ---
        main_box.append(Gtk.Separator())
        footer = Gtk.CenterBox(margin_top=6, margin_bottom=6, margin_start=15, margin_end=15)
        credit_label = Gtk.Label(label="designed by Pinguin-TV   •   Version: 1.4", css_classes=["dim-label", "caption"])
        footer.set_center_widget(credit_label)
        main_box.append(footer)
        # --------------

        self.main_stack.set_visible_child_name("grid")

    def _create_sidebar(self):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin_top=8, margin_bottom=8, margin_start=10, margin_end=10)
        box.add_css_class("sidebar")
        grp_search = Adw.PreferencesGroup(title=tr("Suche"))
        grp_search.set_margin_bottom(6)
        grp_search.set_margin_top(0)
        self.search_entry = Gtk.SearchEntry(placeholder_text=tr("Programme suchen..."))
        self.search_entry.connect("search-changed", self._on_search_input)
        grp_search.add(self.search_entry)
        box.append(grp_search)
        
        grp_view = Adw.PreferencesGroup(title=tr("Ansicht"))
        grp_search.set_margin_bottom(6)
        grp_search.set_margin_top(0)
        settings = load_all_settings()
        
        lang_model = Gtk.StringList.new([tr("Systemstandard"), "English", "Deutsch"])
        self.lang_row = Adw.ComboRow(title=tr("Sprache"), model=lang_model)
        self.lang_row.set_selected({"auto":0, "en":1, "de":2}.get(settings["language"], 0))
        self.lang_row.connect("notify::selected", self._on_lang_changed)
        grp_view.add(self.lang_row)
        
        self.theme_row = Adw.ComboRow(title=tr("Erscheinungsbild"), model=Gtk.StringList.new([tr("Systemstandard"), tr("Hell"), tr("Dunkel")]))
        self.theme_row.set_selected({"auto":0, "light":1, "dark":2}.get(settings["theme"], 0))
        self.theme_row.connect("notify::selected", self._on_theme_changed)
        grp_view.add(self.theme_row)
        
        self.sort_row = Adw.ComboRow(title=tr("Sortierung"), model=Gtk.StringList.new([tr("A–Z"), tr("Zuletzt hinzugefügt")]))
        self.sort_row.connect("notify::selected", lambda w, p: self._filter_apps())
        grp_view.add(self.sort_row)
        box.append(grp_view)

        grp_repos = Adw.PreferencesGroup(title=tr("Repositories"))
        void_expander = Adw.ExpanderRow(title=tr("Void System Repos"))
        repo_list = [("void-repo-nonfree", tr("Non-Free")), ("void-repo-multilib", tr("Multilib"))]
        for pkg, label in repo_list:
            row = Adw.ActionRow(title=label)
            row.set_margin_top(0)
            row.set_margin_bottom(0)
            sw = Gtk.Switch(valign=Gtk.Align.CENTER); sw.connect("state-set", self._on_repo_switch_triggered, pkg)
            row.add_suffix(sw); void_expander.add_row(row); self.repo_switches[pkg] = sw
        grp_repos.add(void_expander)
        self.fh_row = Adw.ActionRow(title=tr("Flathub (Flatpak)"))
        self.fh_switch = Gtk.Switch(valign=Gtk.Align.CENTER); self.fh_switch.connect("state-set", self._on_flathub_switch_triggered)
        self.fh_row.add_suffix(self.fh_switch); grp_repos.add(self.fh_row)
        box.append(grp_repos)

        grp_cat = Adw.PreferencesGroup(title=tr("Kategorien"))

        self.cat_list = Gtk.ListBox(
            selection_mode=Gtk.SelectionMode.SINGLE,
            css_classes=["navigation-sidebar"]
        )

        self.cat_list.connect("row-selected", self._on_cat_selected)
        self.cat_list.connect("row-activated", self._on_cat_activated)

        for c in ["Alle", "Installiert", "Büro", "Grafik", "Multimedia", "Internet", "Gaming", "Tools", "System"]:
            label = Gtk.Label(
                label=tr(c),
                xalign=0
            )
            label.add_css_class("sidebar-label")

            row = Gtk.ListBoxRow()
            row.set_child(label)
            row.set_name(c)
            row.set_size_request(-1, 32) 

            self.cat_list.append(row)

        grp_cat.add(self.cat_list)
        box.append(grp_cat)

        return box
    def _on_cat_selected(self, listbox, row):
        if not row:
            return
        self.current_category = row.get_name()
        self._filter_apps()
        self._show_grid()

    def _on_cat_activated(self, listbox, row):
        if not row:
            return
        self.current_category = row.get_name()
        self._filter_apps()
        self._show_grid()
        
    def _on_search_input(self, entry):
        if self._search_timer_id > 0: GLib.source_remove(self._search_timer_id)
        self.external_results = []; self._filter_apps()
        search_text = entry.get_text().strip().lower()
        if len(search_text) >= 3: self._search_timer_id = GLib.timeout_add(600, self._start_external_search, search_text)

    def _start_external_search(self, query):
        self._search_timer_id = 0
        threading.Thread(target=self._run_repo_search, args=(query,), daemon=True).start()
        return False

    def _run_repo_search(self, query):
        new_apps = []
        try:
            out = subprocess.run(['xbps-query', '-Rs', query], capture_output=True, text=True, timeout=10)
            if out.returncode == 0:
                for line in out.stdout.splitlines()[:20]:
                    if not (line.startswith("[-]") or line.startswith("[*]")): continue
                    parts = line[4:].split(" ", 1); pkg_with_ver = parts[0]; desc = parts[1] if len(parts) > 1 else ""
                    m = re.match(r'^(.+?)-\d', pkg_with_ver); clean_name = m.group(1) if m else pkg_with_ver.split('-')[0]
                    if not any(a.xbps_name == clean_name for a in self.apps_data):
                        new_apps.append(App(clean_name, desc, tr("Suchergebnisse"), xbps_name=clean_name, is_external=True))
        except: pass
        if shutil.which("flatpak"):
            try:
                out = subprocess.run(['flatpak', 'search', '--columns=application,description', query], capture_output=True, text=True, timeout=10)
                if out.returncode == 0:
                    for line in out.stdout.splitlines()[:15]:
                        parts = line.split("\t")
                        if len(parts) >= 2:
                            appid, desc = parts[0].strip(), parts[1].strip()
                            if not any(a.flatpak_id == appid for a in self.apps_data):
                                new_apps.append(App(appid.split(".")[-1].capitalize(), desc, tr("Suchergebnisse"), flatpak_id=appid, icon=appid, is_external=True))
            except: pass
        if new_apps: GLib.idle_add(self._append_external_results, new_apps)

    def _append_external_results(self, results):
        self.external_results = results; self._filter_apps()

    def _filter_apps(self):
        l = self.apps_data; search = self.search_entry.get_text().lower()
        if self.current_category == "Installiert":
            l = [a for a in l if (a.xbps_name and a.xbps_name.lower() in self.install_cache.xbps) or (a.flatpak_id and a.flatpak_id in self.install_cache.flatpaks)]
        elif self.current_category != "Alle":
            l = [a for a in l if a.category == self.current_category]
        if search:
            l = [a for a in l if search in a.name.lower() or search in a.description.lower()]
            if self.external_results: l.extend(self.external_results)
        if self.sort_row.get_selected() == 1: l = sorted(l, key=lambda a: self._added_index.get(a.name, 0), reverse=True)
        else: l = sorted(l, key=lambda a: a.name.lower())
        self._update_list_ui(l)

    def _update_list_ui(self, apps_to_show):
        while child := self.apps_flowbox.get_child_at_index(0): self.apps_flowbox.remove(child)
        for app in apps_to_show: self.apps_flowbox.append(self._create_app_tile(app))

    def _create_app_tile(self, app):
        card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8, css_classes=["card"], width_request=200)
        img = Gtk.Image(icon_name=self._get_icon_name(app), pixel_size=64, margin_top=10); card.append(img)
        lbl = Gtk.Label(label=app.name, css_classes=["title-4"], ellipsize=3); card.append(lbl)
        overlay = Gtk.Overlay(child=card)
        is_inst = (app.xbps_name and app.xbps_name.lower() in self.install_cache.xbps) or (app.flatpak_id and app.flatpak_id in self.install_cache.flatpaks)
        if is_inst:
            badge = Gtk.Label(label=tr("Installiert"), css_classes=["installed-badge"], halign=Gtk.Align.END, valign=Gtk.Align.START, margin_top=5, margin_end=5); overlay.add_overlay(badge)
        btn = Gtk.Button(child=overlay, css_classes=["flat"]); btn.connect("clicked", lambda w: self._show_details(app))
        return btn

    def _show_details(self, app):
        self.back_button.set_visible(True); self._main_title.set_label(app.name)
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24, margin_top=30, margin_bottom=30, margin_start=30, margin_end=30)
        
        # NEU: Screenshot Karussell für Flatpaks
        self.screenshot_carousel = None
        if app.flatpak_id:
            self.screenshot_carousel = Adw.Carousel(spacing=10, height_request=200)
            self.screenshot_carousel.set_visible(False) # Erst zeigen wenn Bilder da sind
            main_box.append(self.screenshot_carousel)
            threading.Thread(target=self._fetch_screenshots, args=(app,), daemon=True).start()

        header = Gtk.Box(spacing=20)
        header.append(Gtk.Image(icon_name=self._get_icon_name(app), pixel_size=96))
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, valign=Gtk.Align.CENTER)
        vbox.append(Gtk.Label(label=app.name, css_classes=["title-1"], halign=Gtk.Align.START))
        vbox.append(Gtk.Label(label=app.description, halign=Gtk.Align.START, wrap=True, margin_top=4))
        header.append(vbox); main_box.append(header)
        
        self.info_group = Adw.PreferencesGroup(title=tr("Details"))
        self.xbps_ver_row = None
        self.flat_ver_row = None
        
        # Hilfsfunktion für den Copy-Button (NEU)
        def add_copy_btn(row):
            btn = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat"], valign=Gtk.Align.CENTER)
            btn.set_tooltip_text(tr("Kopieren"))
            btn.connect("clicked", lambda b: self.get_display().get_clipboard().set(row.get_subtitle() or ""))
            row.add_suffix(btn)

        if app.xbps_name:
            self.xbps_ver_row = Adw.ActionRow(title=tr("Version (XBPS)"), subtitle=tr("Lade..."))
            add_copy_btn(self.xbps_ver_row)
            self.info_group.add(self.xbps_ver_row)
        
        if app.flatpak_id:
            self.flat_ver_row = Adw.ActionRow(title=tr("Version (Flatpak)"), subtitle=tr("Lade..."))
            add_copy_btn(self.flat_ver_row)
            self.info_group.add(self.flat_ver_row)
            
        main_box.append(self.info_group)
        
        # NEU: Service Management (Runit)
        if app.service_name:
            svc_grp = Adw.PreferencesGroup(title=tr("Dienste"))
            row = Adw.ActionRow(title=tr("Dienst aktivieren"), subtitle=f"Service: {app.service_name}")
            sw = Gtk.Switch(valign=Gtk.Align.CENTER)
            # Prüfen ob symlink existiert
            svc_path = f"/var/service/{app.service_name}"
            sw.set_active(os.path.exists(svc_path))
            sw.connect("state-set", self._on_service_toggle, app.service_name)
            row.add_suffix(sw)
            svc_grp.add(row)
            main_box.append(svc_grp)

        actions = Adw.PreferencesGroup(title=tr("Aktionen"))
        # --- Script-Installation (z.B. FreeOffice) ---
        if getattr(app, "script_file", None):
            script_path = os.path.join(BASE_DIR, app.script_file)

            row = Adw.ActionRow(
                title="Skript",
                subtitle=app.script_file
            )

            btn = Gtk.Button(
                label=tr("Installieren"),
                css_classes=["suggested-action"],
                valign=Gtk.Align.CENTER
            )

            btn.connect(
                "clicked",
                lambda b, p=script_path, n=app.name:
                    self._run_term(f"sudo {shlex.quote(p)}", n)
            )

            row.add_suffix(btn)
            actions.add(row)

        if app.xbps_name:
            # Nutze install_name falls gesetzt, sonst xbps_name
            target_pkg = app.install_name if app.install_name else app.xbps_name
            row = Adw.ActionRow(title=tr("XBPS Paket"), subtitle=target_pkg)
            # Check aber immer gegen xbps_name (Paketdatenbank name)
            is_inst = app.xbps_name.lower() in self.install_cache.xbps
            btn = Gtk.Button(label=tr("Entfernen") if is_inst else tr("Installieren"), css_classes=["destructive-action" if is_inst else "suggested-action"], valign=Gtk.Align.CENTER)
            btn.connect("clicked", self._on_install_xbps if not is_inst else self._on_remove_xbps, app)
            row.add_suffix(btn); actions.add(row)
            
        if app.flatpak_id:
            row = Adw.ActionRow(title=tr("Flatpak"), subtitle=app.flatpak_id)
            is_inst = app.flatpak_id in self.install_cache.flatpaks
            btn = Gtk.Button(label=tr("Entfernen") if is_inst else tr("Installieren"), css_classes=["destructive-action" if is_inst else "suggested-action"], valign=Gtk.Align.CENTER)
            btn.connect("clicked", self._on_install_flatpak if not is_inst else self._on_remove_flatpak, app)
            row.add_suffix(btn)
            actions.add(row)
   
        main_box.append(actions)
        
        self.detail_scroll.set_child(main_box); self.main_stack.set_visible_child_name("details")
        if app.xbps_name: threading.Thread(target=self._fetch_xbps_version, args=(app,), daemon=True).start()
        if app.flatpak_id: threading.Thread(target=self._fetch_flatpak_version, args=(app,), daemon=True).start()

    # NEU: Screenshots laden
    def _fetch_screenshots(self, app):
        print(f"DEBUG: Suche Screenshots für {app.flatpak_id}...") 
        try:
            api_url = f"https://flathub.org/api/v2/appstream/{app.flatpak_id}"
            req = urllib.request.Request(api_url, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)'})

            with urllib.request.urlopen(req, timeout=5) as url:
                raw_data = url.read().decode()
                data = json.loads(raw_data)
                
                screenshots = data.get("screenshots", [])
                
                if not screenshots:
                    print(f"DEBUG: Keine Screenshots gefunden.")
                    return

                count = 0
                for i, shot in enumerate(screenshots):
                    if count >= 3: break
                    
                    # 1. Priorität: Das Feld "src" (Standard in V2)
                    img_url = shot.get("src")
                    
                    # 2. Priorität: Falls "src" fehlt, suche in "sizes"
                    if not img_url and "sizes" in shot and isinstance(shot["sizes"], list):
                        try:
                            # Nimm das größte Bild
                            sorted_sizes = sorted(shot["sizes"], key=lambda x: x.get("width", 0))
                            img_url = sorted_sizes[-1].get("src")
                        except: pass
                    
                    # 3. Fallback: Alte Schlüssel
                    if not img_url:
                        img_url = shot.get("url") or shot.get("imgDesktopUrl")

                    if img_url:
                        # --- HIER IST DER TRICK ---
                        # Wenn es eine imgproxy URL ist und AVIF verlangt (f:avif),
                        # ändern wir das zu f:png, damit GTK es sicher anzeigen kann.
                        if "imgproxy.flathub.org" in img_url and "f:avif" in img_url:
                            print(f"DEBUG: Wandle AVIF zu PNG um für: {img_url}")
                            img_url = img_url.replace("f:avif", "f:png")
                        # --------------------------

                        print(f"DEBUG: Lade Bild {i+1}: {img_url}")
                        try:
                            req_img = urllib.request.Request(img_url, headers={'User-Agent': 'Mozilla/5.0'})
                            with urllib.request.urlopen(req_img, timeout=10) as u_img:
                                # Wir nutzen suffix .png, da wir (falls möglich) PNG erzwingen
                                fd, path = tempfile.mkstemp(suffix=".png")
                                with os.fdopen(fd, 'wb') as tmp:
                                    tmp.write(u_img.read())
                                GLib.idle_add(self._add_screenshot_to_ui, path)
                                count += 1
                        except Exception as img_err:
                            print(f"DEBUG: Fehler beim Laden von {img_url}: {img_err}")

        except Exception as e:
            print(f"DEBUG ERROR: {e}")

    def _add_screenshot_to_ui(self, path):
        if self.screenshot_carousel:
            self.screenshot_carousel.set_visible(True)
            # Bild laden
            try:
                texture = Gdk.Texture.new_from_filename(path)
                pic = Gtk.Picture.new_for_paintable(texture)
                pic.set_content_fit(Gtk.ContentFit.COVER)
                pic.set_size_request(350, 200) # Feste Größe für das Bild im Karussell
                # Abgerundete Ecken via CSS
                pic.add_css_class("card")
                self.screenshot_carousel.append(pic)
            except: pass
        # Clean up temp file (könnte man besser machen, aber reicht hier)
        try: os.remove(path)
        except: pass

    # NEU: Runit Toggle Handler
    def _on_service_toggle(self, switch, state, service):
        if self._is_updating_ui: return True # Loop verhindern
        cmd = ""
        if state:
            # Aktivieren: Symlink erstellen
            cmd = f"sudo ln -s /etc/sv/{service} /var/service/"
            action = f"Dienst '{service}' aktivieren"
        else:
            # Deaktivieren: Symlink löschen
            cmd = f"sudo rm /var/service/{service}"
            action = f"Dienst '{service}' deaktivieren"
        
        self._run_term(cmd, action)
        return True # Status ändert sich erst wenn User im Terminal bestätigt hat, UI bleibt erstmal so

    # VERBESSERT: Regex Version Check
    def _fetch_xbps_version(self, app):
        version = tr("Unbekannt")
        pkg_name = app.xbps_name
        
        def extract_ver(raw_str):
            if not raw_str: return None
            raw_str = raw_str.strip()
            match = re.search(r'-(\d.*?)$', raw_str)
            if match: return match.group(1)
            return raw_str

        try:
            # 1. Lokal
            res = subprocess.run(['xbps-query', '-p', 'pkgver', pkg_name], capture_output=True, text=True, timeout=1)
            if res.returncode == 0 and res.stdout.strip():
                v = extract_ver(res.stdout)
                if v: version = v
            # 2. Remote
            if version == tr("Unbekannt"):
                res = subprocess.run(['xbps-query', '-R', '-p', 'pkgver', pkg_name], capture_output=True, text=True, timeout=2)
                if res.returncode == 0 and res.stdout.strip():
                    v = extract_ver(res.stdout)
                    if v: version = v
            # 3. Search Fallback
            if version == tr("Unbekannt"):
                res = subprocess.run(['xbps-query', '-Rs', f'^{pkg_name}$'], capture_output=True, text=True, timeout=3)
                if res.returncode == 0 and res.stdout.strip():
                    parts = res.stdout.splitlines()[0].split()
                    if len(parts) >= 2:
                        v = extract_ver(parts[1])
                        if v: version = v
        except: pass
        GLib.idle_add(self._update_ver_row, self.xbps_ver_row, version)

    def _fetch_flatpak_version(self, app):
        version = tr("Unbekannt")
        try:
            res = subprocess.run(['flatpak', 'info', '--columns=version', app.flatpak_id], capture_output=True, text=True, timeout=2)
            if res.returncode == 0 and res.stdout.strip():
                version = res.stdout.strip()
            else:
                res = subprocess.run(['flatpak', 'search', '--columns=version', app.flatpak_id], capture_output=True, text=True, timeout=4)
                if res.returncode == 0:
                    lines = res.stdout.strip().splitlines()
                    if len(lines) > 0: version = lines[0].strip()
        except: pass
        GLib.idle_add(self._update_ver_row, self.flat_ver_row, version)

    def _update_ver_row(self, row, version):
        if row and version: row.set_subtitle(version)
        return False

    def _show_grid(self):
        self.back_button.set_visible(False); self._main_title.set_label(tr("Void Software Store")); self.main_stack.set_visible_child_name("grid")
    def _select_category(self, name):
        for row in self.cat_list:
            if row.get_name() == name:
                self.cat_list.select_row(row)
                self.current_category = name
                return

    def _get_icon_name(self, app):
        theme = Gtk.IconTheme.get_for_display(self.get_display()); name = str(app.icon).strip()
        return name if theme.has_icon(name) else "package-x-generic-symbolic"

    def _on_lang_changed(self, combo, pspec):
        if self._is_updating_ui: return
        mapping = {0: "auto", 1: "en", 2: "de"}
        choice = mapping.get(combo.get_selected(), "auto"); save_setting("language", choice)
        global CURRENT_LANG; CURRENT_LANG = choice if choice != "auto" else get_system_lang(); self._setup_ui(); self._filter_apps()

    def _on_theme_changed(self, combo, pspec):
        mapping = {0: "auto", 1: "light", 2: "dark"}; choice = mapping.get(combo.get_selected(), "auto"); save_setting("theme", choice)
        self.get_application()._apply_theme(choice)

    def _on_cat_selected(self, listbox, row):
        if not row: 
           return 
        self.current_category = row.get_name(); 
        self._filter_apps(); 
        self._show_grid()

    def _update_repo_switches(self):
        self._is_updating_ui = True
        for pkg, sw in self.repo_switches.items(): sw.set_active(pkg in self.install_cache.xbps)
        self.fh_switch.set_active(self.install_cache.flathub_active); self._is_updating_ui = False

    def _on_repo_switch_triggered(self, widget, state, pkg):
        if self._is_updating_ui: return False
        self._run_term(f"sudo xbps-{'install -S' if state else 'remove -R'} {pkg}", pkg); return True

    def _on_flathub_switch_triggered(self, widget, state):
        if self._is_updating_ui: return False
        cmd = "flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo" if state else "flatpak remote-delete flathub"
        self._run_term(cmd, "Flathub"); return True

    def _on_update_clicked(self, w): self._run_term("sudo xbps-install -Su && flatpak update -y", tr("System Update"))
    
    def _run_term(self, cmd, title):
        full = f"echo '>>> {title}'; {cmd}; echo; echo '{tr('Fertig. Taste drücken...')}' && read -n 1"
        terminals = [['gnome-terminal', '--', 'bash', '-c', full], ['xfce4-terminal', '-e', f"bash -c {shlex.quote(full)}"], ['xterm', '-e', 'bash', '-c', full]]
        for t_cmd in terminals:
            if shutil.which(t_cmd[0]): subprocess.Popen(t_cmd); break
        GLib.timeout_add(4000, self._refresh_after_task)

    def _refresh_after_task(self):
        def task():
            self.install_cache.refresh()

            def ui_update():
                # Repo-Status aktualisieren
                self._update_repo_switches()

                # Auf "Installiert" wechseln
                self.current_category = "Installiert"
                self.cat_list.select_row(
                    next(
                        (r for r in self.cat_list if r.get_name() == "Installiert"),
                        None
                    )
                )

                # Grid neu laden
                self._select_category("Installiert")
                self._filter_apps()
                self._show_grid()

            GLib.idle_add(ui_update)

        threading.Thread(target=task, daemon=True).start()
        return False

    def _on_install_xbps(self, btn, a): 
        # Nutze install_name falls gesetzt
        pkg = a.install_name if a.install_name else a.xbps_name
        self._run_term(f"sudo xbps-install -S {pkg}", a.name)
        
    def _on_remove_xbps(self, btn, a): 
        pkg = a.install_name if a.install_name else a.xbps_name
        self._run_term(f"sudo xbps-remove -R {pkg}", a.name)

    def _on_install_flatpak(self, btn, a): self._run_term(f"flatpak install -y flathub {a.flatpak_id}", a.name)
    def _on_remove_flatpak(self, btn, a): self._run_term(f"flatpak uninstall -y {a.flatpak_id}", a.name)

if __name__ == "__main__":
    app = VoidSoftwareStore()
    app.run(sys.argv)