#!/usr/bin/env python3
#--------------------------------------------------------------------------------------------------------
# Name: Linux Lite - Lite Tweaks
# Architecture: amd64
# Author: Jerry Bezencon
# Website: https://www.linuxliteos.com
# Language: Python/GTK4
# Licence: GPLv2
#--------------------------------------------------------------------------------------------------------

import gi
gi.require_version('Gtk', '4.0')

import os
import sys
import subprocess
import threading
import shutil
import re
import time
from pathlib import Path
from datetime import datetime
from gi.repository import Gtk, GLib, Gio, GObject, Gdk

APP_ID = "com.linuxliteos.litetweaks"
APP_TITLE = "Lite Tweaks"
ICON_PATH = "/usr/share/icons/Papirus/24x24/apps/lite-tweaks.png"
ICON_DIR = "/usr/share/liteappsicons/litetweaks"


# ============================================================================
# GTK4 helper widgets (replacements for Adw widgets)
# ============================================================================

class ActionRow(Gtk.ListBoxRow):
    """A GTK4 replacement for Adw.ActionRow."""
    def __init__(self, title="", subtitle="", **kwargs):
        super().__init__(**kwargs)
        self._prefix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self._prefix_box.set_valign(Gtk.Align.CENTER)

        self._label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
        self._label_box.set_hexpand(True)
        self._label_box.set_valign(Gtk.Align.CENTER)

        self._title_label = Gtk.Label(label=title, xalign=0)
        self._title_label.set_ellipsize(3)  # PANGO_ELLIPSIZE_END
        self._label_box.append(self._title_label)

        self._subtitle_label = Gtk.Label(label=subtitle, xalign=0)
        self._subtitle_label.add_css_class("dim-label")
        self._subtitle_label.add_css_class("caption")
        self._subtitle_label.set_ellipsize(3)
        if subtitle:
            self._label_box.append(self._subtitle_label)
        self._subtitle_visible = bool(subtitle)

        self._suffix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self._suffix_box.set_valign(Gtk.Align.CENTER)

        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        hbox.set_margin_top(8)
        hbox.set_margin_bottom(8)
        hbox.set_margin_start(12)
        hbox.set_margin_end(12)
        hbox.append(self._prefix_box)
        hbox.append(self._label_box)
        hbox.append(self._suffix_box)

        self.set_child(hbox)

    def add_prefix(self, widget):
        self._prefix_box.append(widget)

    def add_suffix(self, widget):
        self._suffix_box.append(widget)

    def set_activatable_widget(self, widget):
        self.set_activatable(True)
        self._activatable_widget = widget
        self.connect("activate", lambda row: widget.activate() if hasattr(widget, 'activate') else None)

    def set_subtitle(self, text):
        self._subtitle_label.set_text(text)
        if text and not self._subtitle_visible:
            self._label_box.append(self._subtitle_label)
            self._subtitle_visible = True
        elif not text and self._subtitle_visible:
            self._label_box.remove(self._subtitle_label)
            self._subtitle_visible = False


class PreferencesGroup(Gtk.Box):
    """A GTK4 replacement for Adw.PreferencesGroup."""
    def __init__(self, title="", description="", **kwargs):
        super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=6, **kwargs)

        if title:
            title_label = Gtk.Label(label=title, xalign=0)
            title_label.add_css_class("title-4")
            title_label.set_margin_start(6)
            self.append(title_label)

        if description:
            desc_label = Gtk.Label(label=description, xalign=0)
            desc_label.add_css_class("dim-label")
            desc_label.set_margin_start(6)
            self.append(desc_label)

        self._listbox = Gtk.ListBox()
        self._listbox.set_selection_mode(Gtk.SelectionMode.NONE)
        self._listbox.add_css_class("boxed-list")
        self.append(self._listbox)

    def add(self, row):
        if isinstance(row, Gtk.ListBoxRow):
            self._listbox.append(row)
        else:
            wrapper = Gtk.ListBoxRow()
            wrapper.set_child(row)
            self._listbox.append(wrapper)

    def remove(self, row):
        if isinstance(row, Gtk.ListBoxRow):
            self._listbox.remove(row)
        else:
            # Try to find the wrapper
            child = self._listbox.get_first_child()
            while child:
                if child.get_child() == row:
                    self._listbox.remove(child)
                    return
                child = child.get_next_sibling()


def show_alert(parent, heading, body, responses, callback, extra_child=None, appearances=None):
    """A GTK4 replacement for Adw.AlertDialog.

    Args:
        parent: Parent window
        heading: Dialog heading text
        body: Dialog body text
        responses: List of (id, label) tuples
        callback: Function(response_id) called when a button is clicked
        extra_child: Optional widget to add between body and buttons
        appearances: Optional dict mapping response_id to CSS class (e.g. 'suggested-action', 'destructive-action')
    """
    win = Gtk.Window(title=heading, transient_for=parent, modal=True, resizable=False)
    win.set_default_size(360, -1)

    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
    vbox.set_margin_top(24)
    vbox.set_margin_bottom(24)
    vbox.set_margin_start(24)
    vbox.set_margin_end(24)

    heading_label = Gtk.Label(label=heading)
    heading_label.add_css_class("title-2")
    heading_label.set_wrap(True)
    vbox.append(heading_label)

    if body:
        body_label = Gtk.Label(label=body)
        body_label.set_wrap(True)
        body_label.set_xalign(0)
        vbox.append(body_label)

    if extra_child:
        vbox.append(extra_child)

    btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8, homogeneous=True)
    btn_box.set_margin_top(12)

    for resp_id, resp_label in responses:
        btn = Gtk.Button(label=resp_label)
        if appearances and resp_id in appearances:
            btn.add_css_class(appearances[resp_id])
        btn.connect("clicked", lambda b, rid=resp_id: (callback(rid), win.close()))
        btn_box.append(btn)

    vbox.append(btn_box)
    win.set_child(vbox)
    win.present()
    return win


# Browser cache paths (relative to HOME)
BROWSER_CACHES = {
    'brave': '.cache/BraveSoftware/Brave-Browser/Default/Cache/',
    'chrome': '.cache/google-chrome/',
    'chromium': '.cache/chromium/',
    'firefox': '.cache/mozilla/',
    'edge': '.cache/microsoft-edge/',
    'midori': '.cache/midori/',
    'opera': '.cache/opera/Cache/',
    'palemoon': '.cache/moonchild productions/pale moon/',
    'vivaldi': '.cache/vivaldi/',
}

# Browser desktop file mappings
BROWSER_DESKTOP_FILES = {
    'brave': 'brave-browser.desktop',
    'firefox': 'firefox.desktop',
    'chrome': 'google-chrome.desktop',
    'chromium': 'chromium-browser.desktop',
    'edge': 'microsoft-edge.desktop',
    'midori': 'midori.desktop',
    'opera': 'opera.desktop',
    'palemoon': 'palemoon.desktop',
    'vivaldi': 'vivaldi-stable.desktop',
}

BROWSER_COMMANDS = {
    'brave': 'brave-browser',
    'firefox': 'firefox',
    'chrome': 'google-chrome',
    'chromium': 'chromium-browser',
    'edge': 'microsoft-edge',
    'midori': 'midori',
    'opera': 'opera',
    'palemoon': 'palemoon',
    'vivaldi': 'vivaldi',
}


def get_dir_size(path):
    """Get size of directory in human readable format."""
    if not os.path.exists(path):
        return None
    try:
        result = subprocess.run(['du', '-sh', path], capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            size = result.stdout.split()[0]
            return f"{size}B"
    except:
        pass
    return None


def get_dir_size_bytes(path):
    """Get size of directory in bytes."""
    if not os.path.exists(path):
        return 0
    try:
        result = subprocess.run(['du', '-sb', path], capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            return int(result.stdout.split()[0])
    except:
        pass
    return 0


def run_command(cmd, shell=False):
    """Run a command and return (success, output)."""
    try:
        if shell:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=300)
        else:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        return result.returncode == 0, result.stdout + result.stderr
    except subprocess.TimeoutExpired:
        return False, "Command timed out"
    except Exception as e:
        return False, str(e)


def check_command_exists(cmd):
    """Check if a command exists in PATH."""
    return shutil.which(cmd) is not None


def check_internet():
    """Check if internet is available."""
    try:
        result = subprocess.run(['curl', '-sk', '--connect-timeout', '5', 'https://google.com/'],
                              capture_output=True, timeout=10)
        return result.returncode == 0
    except:
        return False


# ============================================================================
# Root Actions - These run with elevated privileges via pkexec
# ============================================================================

def root_action_apt_clean():
    """Clean the package cache."""
    print("Stage 1: Cleaning package cache...", flush=True)
    success, output = run_command(['apt-get', 'clean'])
    if success:
        print("  Package cache cleared.", flush=True)
    else:
        print(f"  Error: {output}", flush=True)
    return 0 if success else 1


def root_action_clear_mem():
    """Free up system memory."""
    print("Stage 1: Syncing filesystem...", flush=True)
    subprocess.run(['sync'])
    print("  Filesystem synced", flush=True)
    print("Stage 2: Dropping caches...", flush=True)
    with open('/proc/sys/vm/drop_caches', 'w') as f:
        f.write('3')
    print("  System memory freed.", flush=True)
    return 0


def root_action_log_archives():
    """Delete archived log files."""
    print("Stage 1: Scanning /var/log for archived files...", flush=True)
    patterns = ['*.gz', '*.old', 'vboxadd*', '*.0', '*.1', '*.2', '*.3', '*.4', '*.5', '*.6', '*.7']
    print("Stage 2: Removing archived log files...", flush=True)
    for pattern in patterns:
        subprocess.run(['find', '/var/log', '-name', pattern, '-delete'], capture_output=True)
    print("  Archived logs cleared.", flush=True)
    return 0


def root_action_sysd_logs():
    """Delete SystemD journal logs."""
    print("Stage 1: Rotating SystemD journals...", flush=True)
    subprocess.run(['journalctl', '--rotate'], capture_output=True)
    print("  Journals rotated", flush=True)
    print("Stage 2: Vacuuming journal logs...", flush=True)
    subprocess.run(['journalctl', '--vacuum-size=1M'], capture_output=True)
    print("  SystemD logs cleared.", flush=True)
    return 0


def root_action_resid_config():
    """Remove residual configuration files."""
    print("Stage 1: Scanning for residual configs...", flush=True)
    result = subprocess.run(['dpkg', '-l'], capture_output=True, text=True)
    packages = []
    for line in result.stdout.splitlines():
        if line.startswith('rc'):
            parts = line.split()
            if len(parts) >= 2:
                packages.append(parts[1])
    if packages:
        print(f"  Found {len(packages)} residual config(s)", flush=True)
        print("Stage 2: Purging residual configs...", flush=True)
        subprocess.run(['dpkg', '--purge'] + packages)
        print(f"  Removed {len(packages)} residual config packages.", flush=True)
    else:
        print("  No residual configuration files found.", flush=True)
    return 0


def root_action_remove_pkgs(username):
    """Autoremove unneeded packages."""
    print("Stage 1: Cleaning obsolete package files...", flush=True)
    subprocess.run(['apt-get', 'autoclean', '-y'], capture_output=True)
    print("  Autoclean complete", flush=True)
    print("Stage 2: Removing unneeded packages...", flush=True)
    subprocess.run(['apt-get', 'autoremove', '-y'], capture_output=True)
    print("  Autoremove complete", flush=True)

    print("Stage 3: Updating package status...", flush=True)
    # Update dryapt file
    dryapt_file = f"/home/{username}/.local/share/.dryapt"
    now = datetime.now().strftime("%Y-%m-%d %H")
    try:
        with open(dryapt_file, 'w') as f:
            f.write("0\n")
            f.write(f"{now}\n")
        os.chown(dryapt_file, int(subprocess.run(['id', '-u', username], capture_output=True, text=True).stdout.strip()),
                 int(subprocess.run(['id', '-g', username], capture_output=True, text=True).stdout.strip()))
    except:
        pass
    print("  Unneeded packages cleared.", flush=True)
    return 0


def root_action_fix_apt():
    """Package system repair."""
    print("Stage 1: Checking internet connection...", flush=True)
    if not check_internet():
        print("ERROR: No internet connection. Package System Repair requires internet access.", flush=True)
        return 1
    print("  Internet connection OK", flush=True)

    print("Stage 2: Fixing broken dependencies...", flush=True)
    result = subprocess.run(['apt-get', 'install', '-f', '-y'], capture_output=True, text=True)
    if result.returncode == 0:
        print("  Broken dependencies fixed", flush=True)
    else:
        print(f"  WARNING: apt-get install -f returned code {result.returncode}", flush=True)

    print("Stage 3: Rebuilding Ubuntu sources...", flush=True)
    # Get Ubuntu codename
    result = subprocess.run(['lsb_release', '-sc'], capture_output=True, text=True)
    codename = result.stdout.strip() if result.returncode == 0 else 'resolute'
    print(f"  Detected codename: {codename}", flush=True)

    # Rebuild ubuntu.sources in DEB822 format
    ubuntu_sources = f"""Types: deb deb-src
URIs: http://archive.ubuntu.com/ubuntu/
Suites: {codename} {codename}-updates {codename}-backports
Components: main restricted universe multiverse
Architectures: amd64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg

Types: deb deb-src
URIs: http://archive.ubuntu.com/ubuntu/
Suites: {codename}-security
Components: main restricted universe multiverse
Architectures: amd64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
"""

    with open('/etc/apt/sources.list.d/ubuntu.sources', 'w') as f:
        f.write(ubuntu_sources)
    print("  Written /etc/apt/sources.list.d/ubuntu.sources", flush=True)

    print("Stage 4: Rebuilding Linux Lite sources...", flush=True)
    # Check LL version and update linuxlite.sources in DEB822 format
    try:
        with open('/etc/llver', 'r') as f:
            llver = f.read().split()[2].split('.')[0]

        release_names = {'5': 'emerald', '6': 'fluorite', '7': 'galena', '8': 'hematite'}
        if llver in release_names:
            ll_sources = (f"Types: deb\n"
                          f"URIs: http://repo.linuxliteos.com/linuxlite/\n"
                          f"Suites: {release_names[llver]}\n"
                          f"Components: main\n"
                          f"Architectures: amd64\n"
                          f"Signed-By: /usr/share/keyrings/linuxlite-archive-keyring.gpg\n")
            with open('/etc/apt/sources.list.d/linuxlite.sources', 'w') as f:
                f.write(ll_sources)
            print(f"  Written /etc/apt/sources.list.d/linuxlite.sources (suite: {release_names[llver]})", flush=True)
            # Remove old .list file if it exists
            if os.path.exists('/etc/apt/sources.list.d/linuxlite.list'):
                os.remove('/etc/apt/sources.list.d/linuxlite.list')
                print("  Removed legacy linuxlite.list", flush=True)
        else:
            print(f"  WARNING: Unknown Linux Lite version: {llver}", flush=True)
    except Exception as e:
        print(f"  WARNING: Could not update Linux Lite sources: {e}", flush=True)

    print("Stage 5: Cleaning package lists...", flush=True)
    if os.path.exists('/var/lib/apt/lists.old'):
        shutil.rmtree('/var/lib/apt/lists.old', ignore_errors=True)
        print("  Removed old package lists", flush=True)
    subprocess.run(['apt-get', 'clean'], capture_output=True)
    if os.path.exists('/var/lib/apt/lists'):
        shutil.move('/var/lib/apt/lists', '/var/lib/apt/lists.old')
    os.makedirs('/var/lib/apt/lists/partial', exist_ok=True)
    subprocess.run(['apt-get', 'clean'], capture_output=True)
    print("  Package cache cleaned", flush=True)

    print("Stage 6: Updating package lists...", flush=True)
    result = subprocess.run(['apt-get', 'update'], capture_output=True, text=True)
    if result.returncode == 0:
        print("  Package lists updated successfully", flush=True)
    else:
        print(f"  WARNING: apt-get update returned code {result.returncode}", flush=True)
        if result.stderr:
            for line in result.stderr.strip().split('\n')[-3:]:
                print(f"  {line}", flush=True)

    print("Stage 7: Configuring pending packages...", flush=True)
    result = subprocess.run(['dpkg', '--configure', '-a'], capture_output=True, text=True)
    if result.returncode == 0:
        print("  Package configuration OK", flush=True)
    else:
        print(f"  WARNING: dpkg --configure returned code {result.returncode}", flush=True)

    print("Package system repair completed.", flush=True)
    return 0


def root_action_fix_bootup():
    """Fix the bootup splash."""
    print("Stage 1: Reading OS version...", flush=True)

    # Get LL version
    try:
        with open('/etc/llver', 'r') as f:
            ll_version = f.read().split()[2]
    except Exception:
        print("ERROR: Unknown OS version", flush=True)
        return 1

    major_version = ll_version.split('.')[0]
    if major_version not in ['7', '8']:
        print(f"ERROR: Release {ll_version} not supported", flush=True)
        return 1

    print(f"  Detected Linux Lite {ll_version}", flush=True)

    print("Stage 2: Setting Plymouth boot splash theme...", flush=True)
    theme_path = '/usr/share/plymouth/themes/linuxlite/linuxlite.plymouth'
    if os.path.exists(theme_path):
        subprocess.run(['plymouth-set-default-theme', 'linuxlite'], capture_output=True, text=True)
        print("  Plymouth theme set to: linuxlite", flush=True)
    else:
        print(f"  WARNING: Plymouth theme not found: {theme_path}", flush=True)

    print("Stage 3: Rebuilding initramfs (this may take a moment)...", flush=True)
    result = subprocess.run(['update-initramfs', '-u'], capture_output=True, text=True)
    if result.returncode == 0:
        print("  Initramfs updated successfully", flush=True)
    else:
        print(f"  WARNING: update-initramfs returned code {result.returncode}", flush=True)
        if result.stderr:
            print(f"  {result.stderr.strip()}", flush=True)

    print("Stage 4: Updating /etc/issue...", flush=True)
    with open('/etc/issue', 'w') as f:
        f.write(f"Linux Lite {ll_version} LTS \\n \\l\n")
    print(f"  Set to: Linux Lite {ll_version} LTS", flush=True)

    print("Stage 5: Updating /etc/lsb-release...", flush=True)
    with open('/etc/lsb-release', 'r') as f:
        content = f.read()
    content = re.sub(r'^DISTRIB_DESCRIPTION=.*$', f'DISTRIB_DESCRIPTION="Linux Lite {ll_version}"', content, flags=re.MULTILINE)
    with open('/etc/lsb-release', 'w') as f:
        f.write(content)
    print(f"  DISTRIB_DESCRIPTION set to: Linux Lite {ll_version}", flush=True)

    print("Bootup fix completed successfully.", flush=True)
    return 0


def root_action_hostname(new_hostname, username):
    """Change the system hostname."""
    print("Stage 1: Updating kernel hostname...", flush=True)

    old_hostname = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()

    # Update /proc/sys/kernel/hostname
    with open('/proc/sys/kernel/hostname', 'w') as f:
        f.write(new_hostname)
    print(f"  Hostname: {old_hostname} -> {new_hostname}", flush=True)

    print("Stage 2: Updating /etc/hosts...", flush=True)
    # Update /etc/hosts
    with open('/etc/hosts', 'r') as f:
        content = f.read()
    content = re.sub(r'^127\.0\.1\.1.*$', f'127.0.1.1\t{new_hostname}', content, flags=re.MULTILINE)
    with open('/etc/hosts', 'w') as f:
        f.write(content)
    print("  /etc/hosts updated", flush=True)

    print("Stage 3: Updating /etc/hostname...", flush=True)
    # Update /etc/hostname
    with open('/etc/hostname', 'w') as f:
        f.write(new_hostname + '\n')
    print("  /etc/hostname updated", flush=True)

    print("Stage 4: Restarting NetworkManager...", flush=True)
    subprocess.run(['systemctl', 'restart', 'NetworkManager'])
    print("  NetworkManager restarted", flush=True)

    print("Stage 5: Updating xauth entries...", flush=True)
    # Update xauth
    home_dir = f"/home/{username}"
    result = subprocess.run(['xauth', 'list'], capture_output=True, text=True, env={'HOME': home_dir})
    for line in result.stdout.splitlines():
        if old_hostname in line:
            new_entry = line.replace(old_hostname, new_hostname)
            parts = new_entry.split()
            if len(parts) >= 3:
                subprocess.run(['su', username, '-c', f'xauth add {parts[0]} {parts[1]} {parts[2]}'],
                             env={'HOME': home_dir})

    # Copy xauth-cleanup.desktop to autostart
    autostart_dir = f"{home_dir}/.config/autostart"
    os.makedirs(autostart_dir, exist_ok=True)
    xauth_desktop = "/usr/local/sbin/xauth-cleanup.desktop"
    if os.path.exists(xauth_desktop):
        dest = f"{autostart_dir}/xauth-cleanup.desktop"
        shutil.copy(xauth_desktop, dest)
        uid = int(subprocess.run(['id', '-u', username], capture_output=True, text=True).stdout.strip())
        gid = int(subprocess.run(['id', '-g', username], capture_output=True, text=True).stdout.strip())
        os.chown(dest, uid, gid)
    print("  xauth updated", flush=True)

    print("Stage 6: Checking Samba configuration...", flush=True)
    # Update Samba if configured
    samba_conf = '/etc/samba/smb.conf'
    if os.path.exists(samba_conf):
        with open(samba_conf, 'r') as f:
            content = f.read()
        content = re.sub(r'^netbios name =.*$', f'netbios name = {new_hostname}', content, flags=re.MULTILINE)
        with open(samba_conf, 'w') as f:
            f.write(content)
        subprocess.run(['systemctl', 'reload', 'smbd'], capture_output=True)
        subprocess.run(['systemctl', 'restart', 'nmbd'], capture_output=True)
        print(f"  Samba netbios name updated to {new_hostname}", flush=True)
    else:
        print("  Samba not configured, skipping", flush=True)

    print("Hostname changed successfully.", flush=True)
    return 0


def root_action_numlock(enable):
    """Enable or disable numlock at login."""
    print("Stage 1: Updating LightDM configuration...", flush=True)
    lightdm_conf = '/etc/lightdm/lightdm.conf'
    if not os.path.exists(lightdm_conf):
        print("ERROR: lightdm.conf not found", flush=True)
        return 1

    with open(lightdm_conf, 'r') as f:
        content = f.read()

    if enable:
        content = content.replace('numlockx off', 'numlockx on')
        print("  Numlock enabled at login.", flush=True)
    else:
        content = content.replace('numlockx on', 'numlockx off')
        print("  Numlock disabled at login.", flush=True)

    with open(lightdm_conf, 'w') as f:
        f.write(content)
    return 0


def root_action_save_session(mode):
    """Manage save session settings."""
    print("Stage 1: Updating session save settings...", flush=True)
    kiosk_dir = '/etc/xdg/xfce4/kiosk'
    kioskrc = f'{kiosk_dir}/kioskrc'

    os.makedirs(kiosk_dir, exist_ok=True)

    if not os.path.exists(kioskrc):
        with open(kioskrc, 'w') as f:
            f.write("""[xfce4-panel]
CustomizePanel=ALL

[xfce4-session]
CustomizeSplash=ALL
CustomizeChooser=ALL
CustomizeLogout=ALL
CustomizeCompatibility=%sudo
CustomizeSecurity=NONE
Shutdown=ALL
SaveSession=ALL
""")

    with open(kioskrc, 'r') as f:
        content = f.read()

    content = re.sub(r'^SaveSession=.*$', f'SaveSession={mode}', content, flags=re.MULTILINE)

    with open(kioskrc, 'w') as f:
        f.write(content)

    print(f"  Save session set to {mode}.", flush=True)
    return 0


def root_action_kiosk_mode(setting, value):
    """Configure login/logout options."""
    print("Stage 1: Updating kiosk configuration...", flush=True)
    kiosk_dir = '/etc/xdg/xfce4/kiosk'
    kioskrc = f'{kiosk_dir}/kioskrc'
    pwrlogin = '/etc/lightdm/lightdm-gtk-greeter.conf'

    os.makedirs(kiosk_dir, exist_ok=True)

    if not os.path.exists(kioskrc):
        with open(kioskrc, 'w') as f:
            f.write("""[xfce4-panel]
CustomizePanel=ALL

[xfce4-session]
CustomizeSplash=ALL
CustomizeChooser=ALL
CustomizeLogout=ALL
CustomizeCompatibility=%sudo
CustomizeSecurity=NONE
Shutdown=ALL
SaveSession=ALL
""")

    if setting == 'shutdown':
        with open(kioskrc, 'r') as f:
            content = f.read()
        content = re.sub(r'^Shutdown=.*$', f'Shutdown={value}', content, flags=re.MULTILINE)
        with open(kioskrc, 'w') as f:
            f.write(content)
        print(f"  Logout shutdown options set to {value}.", flush=True)

    elif setting == 'login_power':
        if os.path.exists(pwrlogin):
            with open(pwrlogin, 'r') as f:
                content = f.read()
            if value == 'enable':
                content = re.sub(r'^indicators =.*$', 'indicators = ~host;~spacer;~clock;~spacer;~power', content, flags=re.MULTILINE)
                print("  Login power options enabled.", flush=True)
            else:
                content = re.sub(r'^indicators =.*$', 'indicators = ~host;~spacer;~clock;~spacer;~~Linux Lite', content, flags=re.MULTILINE)
                print("  Login power options disabled.", flush=True)
            with open(pwrlogin, 'w') as f:
                f.write(content)

    return 0


def root_action_preload(action):
    """Manage preload service."""
    if action == 'install':
        print("Stage 1: Installing preload...", flush=True)
        result = subprocess.run(['apt-get', 'install', 'preload', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  Preload installed successfully.", flush=True)
        else:
            print(f"  ERROR: Installation failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'remove':
        print("Stage 1: Stopping preload service...", flush=True)
        subprocess.run(['service', 'preload', 'stop'], capture_output=True)
        print("Stage 2: Removing preload...", flush=True)
        result = subprocess.run(['apt-get', 'remove', 'preload', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  Preload removed successfully.", flush=True)
        else:
            print(f"  ERROR: Removal failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'start':
        print("Stage 1: Starting preload service...", flush=True)
        result = subprocess.run(['service', 'preload', 'start'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  Preload started.", flush=True)
        else:
            print(f"  ERROR: Failed to start (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'stop':
        print("Stage 1: Stopping preload service...", flush=True)
        result = subprocess.run(['service', 'preload', 'stop'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  Preload stopped.", flush=True)
        else:
            print(f"  ERROR: Failed to stop (code {result.returncode})", flush=True)
        return result.returncode
    return 1


def root_action_zram(action):
    """Manage zRAM service."""
    if action == 'install':
        print("Stage 1: Installing zRAM...", flush=True)
        result = subprocess.run(['apt-get', 'install', 'zram-config', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  zRAM installed", flush=True)
            print("Stage 2: Starting zRAM service...", flush=True)
            subprocess.run(['systemctl', 'start', 'zram-config'], capture_output=True)
            print("  zRAM started.", flush=True)
        else:
            print(f"  ERROR: Installation failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'remove':
        print("Stage 1: Stopping zRAM service...", flush=True)
        subprocess.run(['service', 'zram-config', 'stop'], capture_output=True)
        print("Stage 2: Removing zRAM...", flush=True)
        result = subprocess.run(['apt-get', 'remove', 'zram-config', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  zRAM removed successfully.", flush=True)
        else:
            print(f"  ERROR: Removal failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'start':
        print("Stage 1: Starting zRAM service...", flush=True)
        result = subprocess.run(['service', 'zram-config', 'start'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  zRAM started.", flush=True)
        else:
            print(f"  ERROR: Failed to start (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'stop':
        print("Stage 1: Stopping zRAM service...", flush=True)
        result = subprocess.run(['service', 'zram-config', 'stop'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  zRAM stopped.", flush=True)
        else:
            print(f"  ERROR: Failed to stop (code {result.returncode})", flush=True)
        return result.returncode
    return 1


def root_action_tlp(action):
    """Manage TLP service.

    On tlp 1.6+ the bare `tlp start` CLI command applies current power
    settings once and exits — it does NOT activate the systemd unit, so
    the GUI's status check immediately reads the service back as Stopped.
    Use systemctl to manage the unit; tlp-stat is unreliable for status.
    """
    if action == 'install':
        print("Stage 1: Installing TLP...", flush=True)
        result = subprocess.run(['apt-get', 'install', 'tlp', 'tlp-rdw', 'smartmontools', 'ethtool', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  TLP installed", flush=True)
            print("Stage 2: Enabling and starting TLP service...", flush=True)
            subprocess.run(['systemctl', 'enable', '--now', 'tlp.service'], capture_output=True)
            print("  TLP enabled and started.", flush=True)
        else:
            print(f"  ERROR: Installation failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'remove':
        print("Stage 1: Stopping and disabling TLP service...", flush=True)
        subprocess.run(['systemctl', 'disable', '--now', 'tlp.service'], capture_output=True)
        print("Stage 2: Removing TLP...", flush=True)
        result = subprocess.run(['apt-get', 'remove', 'tlp', 'tlp-rdw', 'smartmontools', 'ethtool', '-y'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  TLP removed successfully.", flush=True)
        else:
            print(f"  ERROR: Removal failed (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'start':
        print("Stage 1: Enabling and starting TLP service...", flush=True)
        result = subprocess.run(['systemctl', 'enable', '--now', 'tlp.service'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  TLP enabled and started.", flush=True)
        else:
            print(f"  ERROR: Failed to start (code {result.returncode})", flush=True)
        return result.returncode
    elif action == 'stop':
        print("Stage 1: Stopping and disabling TLP service...", flush=True)
        result = subprocess.run(['systemctl', 'disable', '--now', 'tlp.service'], capture_output=True, text=True)
        if result.returncode == 0:
            print("  TLP stopped and disabled.", flush=True)
        else:
            print(f"  ERROR: Failed to stop (code {result.returncode})", flush=True)
        return result.returncode
    return 1


def root_action_find_files(min_size, max_size, output_file):
    """Find large files within size range."""
    print(f"Finding files between {min_size}MB and {max_size}MB...")
    cmd = f"find / -size +{min_size}M -size -{max_size}M -exec du -mh {{}} + 2>/dev/null | grep '[0-9][MG]' | sort -h -r"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    with open(output_file, 'w') as f:
        f.write(result.stdout)
    print(f"Results saved to {output_file}")
    return 0


def handle_root_action():
    """Handle root action when called with --root-action."""
    if len(sys.argv) < 3:
        print("Usage: lite-tweaks --root-action ACTION [args...]")
        return 1

    action = sys.argv[2]
    args = sys.argv[3:]

    actions = {
        'apt-clean': lambda: root_action_apt_clean(),
        'clear-mem': lambda: root_action_clear_mem(),
        'log-archives': lambda: root_action_log_archives(),
        'sysd-logs': lambda: root_action_sysd_logs(),
        'resid-config': lambda: root_action_resid_config(),
        'remove-pkgs': lambda: root_action_remove_pkgs(args[0]) if args else 1,
        'fix-apt': lambda: root_action_fix_apt(),
        'fix-bootup': lambda: root_action_fix_bootup(),
        'hostname': lambda: root_action_hostname(args[0], args[1]) if len(args) >= 2 else 1,
        'numlock': lambda: root_action_numlock(args[0] == 'enable') if args else 1,
        'save-session': lambda: root_action_save_session(args[0]) if args else 1,
        'kiosk-mode': lambda: root_action_kiosk_mode(args[0], args[1]) if len(args) >= 2 else 1,
        'preload': lambda: root_action_preload(args[0]) if args else 1,
        'zram': lambda: root_action_zram(args[0]) if args else 1,
        'tlp': lambda: root_action_tlp(args[0]) if args else 1,
        'find-files': lambda: root_action_find_files(args[0], args[1], args[2]) if len(args) >= 3 else 1,
    }

    if action in actions:
        return actions[action]()
    else:
        print(f"Unknown action: {action}")
        return 1


# ============================================================================
# Dryapt functionality (background package check)
# ============================================================================

def run_dryapt():
    """Run dryapt check in background."""
    home = os.path.expanduser("~")
    dryapt_file = os.path.join(home, ".local/share/.dryapt")
    now = datetime.now().strftime("%Y-%m-%d %H")

    # Check if we need to run
    if os.path.exists(dryapt_file):
        try:
            with open(dryapt_file, 'r') as f:
                lines = f.readlines()
            if len(lines) >= 1:
                count = int(lines[0].strip())
                if count > 0:
                    return  # Already have a count
            if len(lines) >= 2:
                last_check = lines[1].strip()
                if last_check == now:
                    return  # Already checked this hour
        except:
            pass

    # Run the check
    try:
        result = subprocess.run(['apt-get', '-s', 'autoremove'], capture_output=True, text=True, timeout=60)
        count = 0
        for line in result.stdout.splitlines():
            if line.startswith('Remv '):
                count += 1

        os.makedirs(os.path.dirname(dryapt_file), exist_ok=True)
        with open(dryapt_file, 'w') as f:
            f.write(f"{count}\n{now}\n")
    except:
        pass


# ============================================================================
# Xauth cleanup functionality
# ============================================================================

def run_xauth_cleanup():
    """Clean up stale xauth entries."""
    try:
        hostname = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()
        result = subprocess.run(['xauth', 'list'], capture_output=True, text=True)

        for line in result.stdout.splitlines():
            parts = line.split()
            if parts:
                entry_host = parts[0].split('/')[0]
                if entry_host != hostname:
                    subprocess.run(['xauth', 'remove', parts[0]], capture_output=True)

        # Remove the autostart file
        autostart_file = os.path.expanduser("~/.config/autostart/xauth-cleanup.desktop")
        if os.path.exists(autostart_file):
            os.remove(autostart_file)
    except:
        pass


# ============================================================================
# GTK4 GUI Application
# ============================================================================

class TweakItem:
    """Represents a selectable tweak item."""
    def __init__(self, id, name, task, category, grade, description, enabled=False, visible=True):
        self.id = id
        self.name = name
        self.task = task
        self.category = category
        self.grade = grade
        self.description = description
        self.enabled = enabled
        self.visible = visible
        self.check_button = None
        self.row = None


class ProgressWindow(Gtk.Window):
    """Progress window for running tasks with task list."""
    def __init__(self, parent, title="Running Tasks"):
        super().__init__(title=title, transient_for=parent, modal=True)
        self.set_default_size(450, 400)

        self.task_rows = {}  # task_id -> (row, status_icon)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_top(16)
        box.set_margin_bottom(16)
        box.set_margin_start(16)
        box.set_margin_end(16)

        # Current status label
        self.status_label = Gtk.Label(label="Starting...")
        self.status_label.set_wrap(True)
        self.status_label.add_css_class("title-4")
        box.append(self.status_label)

        # Progress bar
        self.progress_bar = Gtk.ProgressBar()
        self.progress_bar.set_show_text(True)
        box.append(self.progress_bar)

        # Task list in a scrolled window
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)

        self.task_list = Gtk.ListBox()
        self.task_list.set_selection_mode(Gtk.SelectionMode.NONE)
        self.task_list.add_css_class("boxed-list")
        scrolled.set_child(self.task_list)
        box.append(scrolled)

        self.set_child(box)

    def add_task(self, task_id, task_name):
        """Add a task to the list."""
        def _add():
            row = ActionRow(title=task_name)
            status_icon = Gtk.Image.new_from_icon_name("content-loading-symbolic")
            status_icon.add_css_class("dim-label")
            row.add_suffix(status_icon)
            self.task_list.append(row)
            self.task_rows[task_id] = (row, status_icon)
        GLib.idle_add(_add)

    def set_task_running(self, task_id):
        """Mark a task as currently running."""
        def _update():
            if task_id in self.task_rows:
                row, icon = self.task_rows[task_id]
                icon.set_from_icon_name("media-playback-start-symbolic")
                icon.remove_css_class("dim-label")
                icon.remove_css_class("success")
                icon.add_css_class("accent")
        GLib.idle_add(_update)

    def set_task_completed(self, task_id):
        """Mark a task as completed."""
        def _update():
            if task_id in self.task_rows:
                row, icon = self.task_rows[task_id]
                icon.set_from_icon_name("emblem-ok-symbolic")
                icon.remove_css_class("dim-label")
                icon.remove_css_class("accent")
                icon.add_css_class("success")
        GLib.idle_add(_update)

    def set_status(self, text):
        GLib.idle_add(self._set_status, text)

    def _set_status(self, text):
        self.status_label.set_text(text)

    def set_progress(self, fraction):
        GLib.idle_add(self._set_progress, fraction)

    def _set_progress(self, fraction):
        self.progress_bar.set_fraction(fraction)
        self.progress_bar.set_text(f"{int(fraction * 100)}%")


class DiskItem(GObject.Object):
    """GObject wrapper for disk info."""
    def __init__(self, device, fs_type, size, used, free, percent, mount):
        super().__init__()
        self.device = device
        self.fs_type = fs_type
        self.size = size
        self.used = used
        self.free = free
        self.percent = percent
        self.mount = mount


class DiskUsageWindow(Gtk.Window):
    """Window to display disk usage."""
    def __init__(self, parent):
        super().__init__(title="Disk Usage", transient_for=parent)
        self.set_default_size(700, 450)
        self.parent_window = parent

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        header = Gtk.HeaderBar()
        self.set_titlebar(header)

        # Create list model
        self.list_store = Gio.ListStore(item_type=DiskItem)

        # Create column view
        self.column_view = Gtk.ColumnView()
        self.column_view.set_model(Gtk.SingleSelection.new(self.list_store))

        # Add columns
        for title, attr in [("Device", "device"), ("Type", "fs_type"), ("Size", "size"),
                           ("Used", "used"), ("Free", "free"), ("%Used", "percent"), ("Mount", "mount")]:
            factory = Gtk.SignalListItemFactory()
            factory.connect("setup", self._on_factory_setup)
            factory.connect("bind", self._on_factory_bind, attr)
            column = Gtk.ColumnViewColumn(title=title, factory=factory)
            column.set_resizable(True)
            self.column_view.append_column(column)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_child(self.column_view)
        scrolled.set_vexpand(True)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        box.set_margin_top(12)
        box.set_margin_bottom(12)
        box.set_margin_start(12)
        box.set_margin_end(12)

        info_label = Gtk.Label(label="Double-click a partition to open in file manager. Click column headers to sort.")
        info_label.set_wrap(True)
        info_label.add_css_class("dim-label")
        box.append(info_label)
        box.append(scrolled)

        # Open button
        open_btn = Gtk.Button(label="Open in File Manager")
        open_btn.connect("clicked", self._on_open_clicked)
        open_btn.add_css_class("suggested-action")
        box.append(open_btn)

        box.set_vexpand(True)
        vbox.append(box)
        self.set_child(vbox)

        self._load_disk_info()

    def _on_factory_setup(self, factory, list_item):
        label = Gtk.Label()
        label.set_xalign(0)
        list_item.set_child(label)

    def _on_factory_bind(self, factory, list_item, attr):
        item = list_item.get_item()
        label = list_item.get_child()
        label.set_text(getattr(item, attr, ""))

    def _on_open_clicked(self, button):
        selection = self.column_view.get_model()
        item = selection.get_selected_item()
        if item and item.mount:
            subprocess.Popen(['xdg-open', item.mount])

    def _load_disk_info(self):
        result = subprocess.run(['df', '-h', '-T'], capture_output=True, text=True)
        for line in result.stdout.splitlines()[1:]:
            parts = line.split()
            if len(parts) >= 7 and parts[0].startswith('/dev/'):
                item = DiskItem(
                    device=parts[0],
                    fs_type=parts[1],
                    size=parts[2],
                    used=parts[3],
                    free=parts[4],
                    percent=parts[5],
                    mount=' '.join(parts[6:])
                )
                self.list_store.append(item)


class DefaultBrowserWindow(Gtk.Window):
    """Window to select default web browser."""
    def __init__(self, parent):
        super().__init__(title="Default Web Browser", transient_for=parent)
        self.set_default_size(500, 400)
        self.parent_window = parent

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        header = Gtk.HeaderBar()
        set_btn = Gtk.Button(label="Set Default")
        set_btn.add_css_class("suggested-action")
        set_btn.connect("clicked", self._on_set_clicked)
        header.pack_end(set_btn)
        self.set_titlebar(header)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)

        self.pref_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        self.pref_page.set_margin_top(12)
        self.pref_page.set_margin_bottom(12)
        self.pref_page.set_margin_start(12)
        self.pref_page.set_margin_end(12)
        self.pref_group = PreferencesGroup(title="Select your default web browser")
        self.pref_page.append(self.pref_group)

        self.check_group = Gtk.CheckButton()
        self.browser_rows = {}

        # Check which browsers are installed and which is default
        mimeapps = os.path.expanduser("~/.config/mimeapps.list")
        current_default = None
        if os.path.exists(mimeapps):
            with open(mimeapps, 'r') as f:
                for line in f:
                    if line.startswith('x-scheme-handler/http='):
                        desktop = line.split('=')[1].strip().rstrip(';').split(';')[0]
                        current_default = desktop
                        break

        for browser_id, cmd in BROWSER_COMMANDS.items():
            if check_command_exists(cmd):
                desktop_file = BROWSER_DESKTOP_FILES.get(browser_id, '')
                is_default = current_default == desktop_file if current_default else False

                name = browser_id.replace('_', ' ').title()
                if browser_id == 'edge':
                    name = 'Microsoft Edge'
                elif browser_id == 'palemoon':
                    name = 'Pale Moon'
                elif browser_id == 'chrome':
                    name = 'Google Chrome'

                status = "Default" if is_default else ""

                row = ActionRow(title=name, subtitle=status)
                check = Gtk.CheckButton()
                check.set_group(self.check_group)
                if is_default:
                    check.set_active(True)
                row.add_prefix(check)
                row.set_activatable_widget(check)

                self.browser_rows[browser_id] = (row, check)
                self.pref_group.add(row)

        scrolled.set_child(self.pref_page)
        vbox.append(scrolled)
        self.set_child(vbox)

    def _on_set_clicked(self, button):
        for browser_id, (row, check) in self.browser_rows.items():
            if check.get_active():
                self._set_default_browser(browser_id)
                self.close()
                return

    def _set_default_browser(self, browser_id):
        desktop_file = BROWSER_DESKTOP_FILES.get(browser_id)
        if not desktop_file:
            return

        mimeapps = os.path.expanduser("~/.config/mimeapps.list")

        # Ensure file exists with proper sections
        if not os.path.exists(mimeapps):
            with open(mimeapps, 'w') as f:
                f.write("[Added Associations]\n\n[Default Applications]\n")

        with open(mimeapps, 'r') as f:
            content = f.read()

        # Update mime associations
        mime_types = [
            'application/xhtml+xml',
            'text/html',
            'x-scheme-handler/http',
            'x-scheme-handler/https',
        ]

        for mime in mime_types:
            pattern = rf'^{re.escape(mime)}=.*$'
            replacement = f'{mime}={desktop_file};firefox.desktop;' if browser_id != 'firefox' else f'{mime}={desktop_file};'
            if re.search(pattern, content, re.MULTILINE):
                content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
            else:
                if '[Default Applications]' in content:
                    content = content.replace('[Default Applications]', f'[Default Applications]\n{replacement}')

        with open(mimeapps, 'w') as f:
            f.write(content)

        # Also use xdg-settings
        subprocess.run(['xdg-settings', 'set', 'default-web-browser', desktop_file], capture_output=True)


class HibernateSuspendWindow(Gtk.Window):
    """Window to configure hibernate/suspend buttons."""
    def __init__(self, parent):
        super().__init__(title="Hibernate / Suspend Buttons", transient_for=parent)
        self.set_default_size(500, 500)
        self.parent_window = parent

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        header = Gtk.HeaderBar()
        self.set_titlebar(header)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)

        self.pref_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        self.pref_page.set_margin_top(12)
        self.pref_page.set_margin_bottom(12)
        self.pref_page.set_margin_start(12)
        self.pref_page.set_margin_end(12)
        group = PreferencesGroup(title="Show or hide buttons on the logout screen")
        self.pref_page.append(group)

        self.session_file = os.path.expanduser("~/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-session.xml")
        self._ensure_session_defaults()

        self.switches = {}

        for btn_name, prop_name in [("Hibernate", "ShowHibernate"),
                                    ("Suspend", "ShowSuspend"),
                                    ("Hybrid Sleep", "ShowHybridSleep"),
                                    ("Switch User", "ShowSwitchUser")]:
            row = ActionRow(title=btn_name, subtitle=f"Show {btn_name} button on logout screen")
            switch = Gtk.Switch()
            switch.set_valign(Gtk.Align.CENTER)
            switch.set_active(self._get_button_state(prop_name))
            switch.connect("notify::active", self._on_switch_toggled, prop_name)
            row.add_suffix(switch)
            self.switches[prop_name] = switch
            group.add(row)

        # Show/Hide all buttons
        group2 = PreferencesGroup(title="Quick Actions")
        self.pref_page.append(group2)

        show_all_row = ActionRow(title="Show All", subtitle="Show all buttons on logout screen")
        show_all_btn = Gtk.Button(label="Apply")
        show_all_btn.set_valign(Gtk.Align.CENTER)
        show_all_btn.connect("clicked", lambda b: self._set_all_buttons(True))
        show_all_row.add_suffix(show_all_btn)
        group2.add(show_all_row)

        hide_all_row = ActionRow(title="Hide All", subtitle="Hide all buttons on logout screen")
        hide_all_btn = Gtk.Button(label="Apply")
        hide_all_btn.set_valign(Gtk.Align.CENTER)
        hide_all_btn.connect("clicked", lambda b: self._set_all_buttons(False))
        hide_all_row.add_suffix(hide_all_btn)
        group2.add(hide_all_row)

        scrolled.set_child(self.pref_page)
        vbox.append(scrolled)
        self.set_child(vbox)

    def _ensure_session_defaults(self):
        if not os.path.exists(self.session_file):
            return

        with open(self.session_file, 'r') as f:
            content = f.read()

        if '<property name="shutdown" type="empty">' not in content:
            # Add default shutdown properties
            shutdown_props = '''    <property name="shutdown" type="empty">
        <property name="ShowHibernate" type="bool" value="true"/>
        <property name="ShowSuspend" type="bool" value="true"/>
        <property name="ShowHybridSleep" type="bool" value="true"/>
        <property name="ShowSwitchUser" type="bool" value="true"/>
    </property>
'''
            content = content.replace('</channel>', shutdown_props + '</channel>')
            with open(self.session_file, 'w') as f:
                f.write(content)

    def _get_button_state(self, prop_name):
        if not os.path.exists(self.session_file):
            return True

        with open(self.session_file, 'r') as f:
            content = f.read()

        match = re.search(rf'<property name="{prop_name}" type="bool" value="(true|false)"/>', content)
        return match.group(1) == 'true' if match else True

    def _on_switch_toggled(self, switch, param, prop_name):
        self._set_button_state(prop_name, switch.get_active())
        self._notify_reboot()

    def _set_button_state(self, prop_name, value):
        if not os.path.exists(self.session_file):
            return

        with open(self.session_file, 'r') as f:
            content = f.read()

        value_str = 'true' if value else 'false'
        content = re.sub(
            rf'<property name="{prop_name}" type="bool" value="(true|false)"/>',
            f'<property name="{prop_name}" type="bool" value="{value_str}"/>',
            content
        )

        with open(self.session_file, 'w') as f:
            f.write(content)

    def _set_all_buttons(self, value):
        for prop_name, switch in self.switches.items():
            switch.set_active(value)
            self._set_button_state(prop_name, value)
        self._notify_reboot()

    def _notify_reboot(self):
        """Show reboot required notification."""
        self.parent_window.show_toast("A reboot is required to apply changes")


class ServiceManagerWindow(Gtk.Window):
    """Generic service manager window for Preload/zRAM/TLP."""
    def __init__(self, parent, service_name, title, install_packages, check_running_func, description):
        super().__init__(title=title, transient_for=parent)
        self.set_default_size(450, 380)
        self.parent_window = parent
        self.service_name = service_name
        self.install_packages = install_packages
        self.check_running_func = check_running_func

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        header = Gtk.HeaderBar()
        self.set_titlebar(header)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)

        self.pref_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        self.pref_page.set_margin_top(12)
        self.pref_page.set_margin_bottom(12)
        self.pref_page.set_margin_start(12)
        self.pref_page.set_margin_end(12)

        # Status group
        status_group = PreferencesGroup(title="Status")
        self.pref_page.append(status_group)

        self.status_row = ActionRow(title="Service Status")
        status_group.add(self.status_row)

        # Actions group
        self.actions_group = PreferencesGroup(title="Actions")
        self.pref_page.append(self.actions_group)

        self.action_rows = {}

        scrolled.set_child(self.pref_page)
        vbox.append(scrolled)
        self.set_child(vbox)

        self._refresh_status()

    def _is_installed(self):
        result = subprocess.run(['dpkg', '-l'] + self.install_packages[:1], capture_output=True, text=True)
        return 'ii' in result.stdout

    def _refresh_status(self):
        # Clear existing action rows
        for row in list(self.action_rows.values()):
            self.actions_group.remove(row)
        self.action_rows.clear()

        installed = self._is_installed()

        if not installed:
            self.status_row.set_subtitle("Not installed")

            install_row = ActionRow(title="Install", subtitle=f"Install {self.service_name}")
            install_btn = Gtk.Button(label="Install")
            install_btn.set_valign(Gtk.Align.CENTER)
            install_btn.add_css_class("suggested-action")
            install_btn.connect("clicked", self._on_install)
            install_row.add_suffix(install_btn)
            self.actions_group.add(install_row)
            self.action_rows['install'] = install_row
        else:
            running = self.check_running_func()
            self.status_row.set_subtitle("Running" if running else "Stopped")

            if not running:
                start_row = ActionRow(title="Start", subtitle=f"Start {self.service_name} service")
                start_btn = Gtk.Button(label="Start")
                start_btn.set_valign(Gtk.Align.CENTER)
                start_btn.connect("clicked", self._on_start)
                start_row.add_suffix(start_btn)
                self.actions_group.add(start_row)
                self.action_rows['start'] = start_row
            else:
                stop_row = ActionRow(title="Stop", subtitle=f"Stop {self.service_name} service")
                stop_btn = Gtk.Button(label="Stop")
                stop_btn.set_valign(Gtk.Align.CENTER)
                stop_btn.connect("clicked", self._on_stop)
                stop_row.add_suffix(stop_btn)
                self.actions_group.add(stop_row)
                self.action_rows['stop'] = stop_row

            remove_row = ActionRow(title="Remove", subtitle=f"Uninstall {self.service_name}")
            remove_btn = Gtk.Button(label="Remove")
            remove_btn.set_valign(Gtk.Align.CENTER)
            remove_btn.add_css_class("destructive-action")
            remove_btn.connect("clicked", self._on_remove)
            remove_row.add_suffix(remove_btn)
            self.actions_group.add(remove_row)
            self.action_rows['remove'] = remove_row

    def _run_action(self, action):
        # Disable all action buttons and show progress
        for row in self.action_rows.values():
            row.set_sensitive(False)

        self.status_row.set_subtitle(f"{action.capitalize()}ing {self.service_name}...")

        # Add a progress label if not already present
        if not hasattr(self, 'progress_label'):
            self.progress_label = Gtk.Label(label="")
            self.progress_label.set_wrap(True)
            self.progress_label.set_xalign(0)
            self.progress_label.add_css_class("dim-label")
            self.progress_label.set_margin_start(12)
            self.progress_label.set_margin_end(12)
            self.pref_page.append(self.progress_label)

        def do_action():
            cmd = ['pkexec', '/usr/bin/lite-tweaks', '--root-action',
                   self.service_name.lower(), action]
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
            for line in proc.stdout:
                line = line.strip()
                if line:
                    GLib.idle_add(self.progress_label.set_text, line)
            proc.wait()
            GLib.idle_add(self._on_action_complete)

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

    def _on_action_complete(self):
        self._refresh_status()
        if hasattr(self, 'progress_label'):
            self.progress_label.set_text("")

    def _on_install(self, button):
        self._run_action('install')

    def _on_start(self, button):
        self._run_action('start')

    def _on_stop(self, button):
        self._run_action('stop')

    def _on_remove(self, button):
        self._run_action('remove')


class LiteTweaksApp(Gtk.Application):
    """Main application class."""

    def __init__(self):
        super().__init__(application_id=APP_ID)
        self.window = None
        self.tweaks = []
        self.selected_tweaks = set()

    def _apply_css(self):
        css = b"""
        .dark-text { color: #1e1e1e; }
        .dim-label { color: #4a4a4a; }
        .app-notification {
            background-color: #323232;
            color: #ffffff;
            border-radius: 8px;
            padding: 8px 16px;
        }
        """
        provider = Gtk.CssProvider()
        provider.load_from_data(css)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(), provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

    def do_activate(self):
        self._apply_css()
        if not self.window:
            self.window = LiteTweaksWindow(application=self)
        self.window.present()


class LiteTweaksWindow(Gtk.ApplicationWindow):
    """Main application window."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_icon_name("lite-tweaks")
        self.set_title(APP_TITLE)
        self.set_default_size(900, 700)

        self.tweaks = []
        self.tweak_rows = {}
        self.selected_tweaks = set()

        # Main layout
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        # Header bar
        header = Gtk.HeaderBar()
        header.set_title_widget(Gtk.Label(label=APP_TITLE))

        # Begin button
        self.begin_btn = Gtk.Button(label="Begin")
        self.begin_btn.add_css_class("suggested-action")
        self.begin_btn.connect("clicked", self._on_begin_clicked)
        header.pack_end(self.begin_btn)

        self.set_titlebar(header)

        # Create scrolled window with preferences page
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)

        self.pref_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        self.pref_page.set_margin_top(12)
        self.pref_page.set_margin_bottom(12)
        self.pref_page.set_margin_start(12)
        self.pref_page.set_margin_end(12)

        # Add info banner
        info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        info_box.set_margin_top(12)
        info_box.set_margin_bottom(6)
        info_box.set_margin_start(12)
        info_box.set_margin_end(12)

        info_label = Gtk.Label()
        info_label.set_markup(
            "Select tasks and click <b>Begin</b>. "
            "<span foreground='#4caf50'>Safe</span> = no risk, "
            "<span foreground='#e53935'>Caution</span> = system changes."
        )
        info_label.set_wrap(True)
        info_box.append(info_label)

        # Create preference groups for each category
        self._populate_groups()

        scrolled.set_child(self.pref_page)

        # Add info box above scrolled content
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        main_box.append(info_box)
        main_box.append(scrolled)

        main_box.set_vexpand(True)
        vbox.append(main_box)

        # Toast overlay for notifications
        self.toast_overlay = Gtk.Overlay()
        self.toast_overlay.set_child(vbox)
        self.set_child(self.toast_overlay)

        # Toast label (hidden by default)
        self._toast_label = Gtk.Label()
        self._toast_label.set_halign(Gtk.Align.CENTER)
        self._toast_label.set_valign(Gtk.Align.END)
        self._toast_label.set_margin_bottom(24)
        self._toast_label.add_css_class("app-notification")
        self._toast_label.set_visible(False)

        toast_frame = Gtk.Frame()
        toast_frame.set_child(self._toast_label)
        toast_frame.set_halign(Gtk.Align.CENTER)
        toast_frame.set_valign(Gtk.Align.END)
        toast_frame.set_margin_bottom(24)
        toast_frame.set_visible(False)
        self._toast_frame = toast_frame
        self.toast_overlay.add_overlay(toast_frame)

        # Run dryapt check in background
        threading.Thread(target=run_dryapt, daemon=True).start()

    def show_toast(self, message, timeout=3):
        """Show a brief toast notification at the bottom of the window."""
        self._toast_label.set_text(message)
        self._toast_label.set_visible(True)
        self._toast_frame.set_visible(True)
        GLib.timeout_add_seconds(timeout, self._dismiss_toast)

    def _dismiss_toast(self):
        self._toast_label.set_visible(False)
        self._toast_frame.set_visible(False)
        return False

    def _populate_groups(self):
        """Build all preference groups on the page."""
        self._create_cleaning_group()
        self._create_performance_group()
        self._create_system_group()
        self._create_information_group()
        self._create_preferences_group()


    def _refresh_groups(self):
        """Clear and rebuild all preference groups to reflect current state."""
        # Remove all children from pref_page
        child = self.pref_page.get_first_child()
        while child:
            next_child = child.get_next_sibling()
            self.pref_page.remove(child)
            child = next_child

        # Reset tweak tracking
        self.tweaks.clear()
        self.tweak_rows.clear()
        self.selected_tweaks.clear()

        # Rebuild
        self._populate_groups()

    def _create_tweak_row(self, tweak):
        """Create an ActionRow for a tweak item."""
        row = ActionRow(title=tweak.name, subtitle=tweak.description)

        check = Gtk.CheckButton()
        check.connect("toggled", self._on_check_toggled, tweak)
        row.add_prefix(check)
        row.set_activatable_widget(check)

        # Task label with grade color
        task_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)

        grade_label = Gtk.Label(label=tweak.grade)
        if tweak.grade == "Safe":
            grade_label.add_css_class("success")
        elif tweak.grade == "Caution":
            grade_label.add_css_class("error")
        task_box.append(grade_label)

        row.add_suffix(task_box)

        tweak.check_button = check
        tweak.row = row
        self.tweak_rows[tweak.id] = row

        return row

    def _on_check_toggled(self, check, tweak):
        if check.get_active():
            self.selected_tweaks.add(tweak.id)
        else:
            self.selected_tweaks.discard(tweak.id)

    def _create_cleaning_group(self):
        """Create the Cleaning preferences group."""
        group = PreferencesGroup(title="Cleaning", description="Clean caches, logs, and temporary files")
        self.pref_page.append(group)

        home = os.path.expanduser("~")

        # Browser caches
        for browser_id, cache_path in BROWSER_CACHES.items():
            full_path = os.path.join(home, cache_path)
            if os.path.exists(full_path) and get_dir_size_bytes(full_path) > 0:
                size = get_dir_size(full_path)
                name = browser_id.replace('_', ' ').title()
                if browser_id == 'edge':
                    name = 'Microsoft Edge'
                elif browser_id == 'palemoon':
                    name = 'Pale Moon'

                desc = f"Remove {size} from your {name} cache"
                tweak = TweakItem(f"browser_{browser_id}", f"{name} Cache", "Clean", "Internet", "Safe", desc)
                self.tweaks.append(tweak)
                group.add(self._create_tweak_row(tweak))

        # Thumbnail cache
        thumb_path = os.path.join(home, ".cache/thumbnails")
        thumb_size = get_dir_size_bytes(thumb_path)
        if os.path.exists(thumb_path) and thumb_size > 0:
            size = get_dir_size(thumb_path)
            tweak = TweakItem("thumbnails", "Thumbnail Cache", "Clean", "Images", "Safe",
                            f"Remove {size} from your thumbnail cache")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # Trash
        trash_path = os.path.join(home, ".local/share/Trash/files")
        if os.path.exists(trash_path) and os.listdir(trash_path):
            size = get_dir_size(os.path.join(home, ".local/share/Trash"))
            tweak = TweakItem("trash", "Trash Bin", "Clean", "Home", "Safe",
                            f"Remove {size} from your Trash bin" if size else "Empty the Trash bin")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # Package cache
        apt_cache = "/var/cache/apt/archives"
        apt_size = get_dir_size_bytes(apt_cache)
        if os.path.exists(apt_cache) and apt_size > 0:
            size = get_dir_size(apt_cache)
            tweak = TweakItem("apt_clean", "Package Cache", "Clean", "Packages", "Safe",
                            f"Clean {size} from your package cache")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # Log archives (*.gz, *.old, *.1, etc. in /var/log)
        log_archive_size = 0
        if os.path.exists("/var/log"):
            try:
                result = subprocess.run(
                    ['find', '/var/log', '-type', 'f', '(', '-name', '*.gz', '-o', '-name', '*.old', '-o', '-name', '*.1', '-o', '-name', '*.2', ')'],
                    capture_output=True, text=True, timeout=10
                )
                if result.returncode == 0 and result.stdout.strip():
                    files = result.stdout.strip().split('\n')
                    for f in files:
                        try:
                            log_archive_size += os.path.getsize(f)
                        except:
                            pass
            except:
                pass
        if log_archive_size > 0:
            size_str = subprocess.run(['numfmt', '--to=iec', str(log_archive_size)], capture_output=True, text=True).stdout.strip()
            tweak = TweakItem("log_archives", "Log Archives", "Clean", "System", "Safe",
                            f"Remove {size_str}B of archived log files from /var/log")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # SystemD logs
        journal_size = 0
        journal_path = "/var/log/journal"
        if os.path.exists(journal_path):
            journal_size = get_dir_size_bytes(journal_path)
        if journal_size > 50 * 1024 * 1024:  # Only show if over 50MB
            size_str = get_dir_size(journal_path)
            tweak = TweakItem("sysd_logs", "SystemD Logs", "Clean", "System", "Safe",
                            f"Remove {size_str} of SystemD journal logs")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # Residual config
        result = subprocess.run(['dpkg', '-l'], capture_output=True, text=True)
        rc_count = sum(1 for line in result.stdout.splitlines() if line.startswith('rc'))
        if rc_count > 0:
            tweak = TweakItem("resid_config", "Residual Config Files", "Clean", "Packages", "Safe",
                            f"Remove {rc_count} residual configuration files")
            self.tweaks.append(tweak)
            group.add(self._create_tweak_row(tweak))

        # Autoremove
        dryapt_file = os.path.join(home, ".local/share/.dryapt")
        if os.path.exists(dryapt_file):
            try:
                with open(dryapt_file, 'r') as f:
                    count = int(f.readline().strip())
                if count > 0:
                    tweak = TweakItem("autoremove", "Autoremove Packages", "Clean", "Packages", "Safe",
                                    f"You can autoremove {count} packages")
                    self.tweaks.append(tweak)
                    group.add(self._create_tweak_row(tweak))
            except:
                pass

    def _create_performance_group(self):
        """Create the Performance preferences group."""
        group = PreferencesGroup(title="Performance", description="Optimize system performance")
        self.pref_page.append(group)

        # Clear memory
        tweak = TweakItem("clear_mem", "Clear Memory", "Performance", "System", "Safe",
                        "Free up memory on your system")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Preload
        is_preload_installed = subprocess.run(['dpkg', '-l', 'preload'], capture_output=True, text=True)
        preload_desc = "Preload is installed (select to modify)" if 'ii' in is_preload_installed.stdout else "Fetch commonly used apps into Memory"
        tweak = TweakItem("preload", "Preload Apps", "Performance", "System", "Safe", preload_desc)
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # zRAM
        is_zram_installed = subprocess.run(['dpkg', '-l', 'zram-config'], capture_output=True, text=True)
        zram_desc = "zRAM is installed (select to modify)" if 'ii' in is_zram_installed.stdout else "Use Memory more efficiently on older systems"
        tweak = TweakItem("zram", "zRAM", "Performance", "System", "Safe", zram_desc)
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # TLP
        is_tlp_installed = subprocess.run(['dpkg', '-l', 'tlp'], capture_output=True, text=True)
        tlp_desc = "TLP is installed (select to modify)" if 'ii' in is_tlp_installed.stdout else "TLP may help improve battery life in Laptops"
        tweak = TweakItem("tlp", "TLP (Laptops)", "Performance", "System", "Caution", tlp_desc)
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

    def _create_system_group(self):
        """Create the System preferences group."""
        group = PreferencesGroup(title="System", description="System configuration and repair")
        self.pref_page.append(group)

        # Hostname
        tweak = TweakItem("hostname", "Hostname", "Edit", "System", "Caution",
                        "Change the computer hostname")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Numlock
        tweak = TweakItem("numlock", "Numlock", "Edit", "System", "Safe",
                        "Enable/Disable Numlock at Login")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Manage Save Session
        tweak = TweakItem("save_session", "Manage Save Session", "Administration", "System", "Safe",
                        "Enable or disable saving of your session")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Login/Logout Options
        tweak = TweakItem("kiosk_mode", "Login & Logout Options", "Administration", "System", "Safe",
                        "Enable or disable basic Login & Logout options")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Bootup Fix
        tweak = TweakItem("fix_bootup", "Bootup Fix", "Fix", "Repair", "Caution",
                        "Restore the boot splash to Linux Lite")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Package System Repair
        tweak = TweakItem("fix_apt", "Package System Repair", "Fix", "Repair", "Caution",
                        "Restore the package management system")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Taskbar Restore
        tweak = TweakItem("taskbar_restore", "Taskbar Restore", "Fix", "UI", "Safe",
                        "Restore the Taskbar and Tray icons to default")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

    def _create_information_group(self):
        """Create the Information preferences group."""
        group = PreferencesGroup(title="Information", description="View system information")
        self.pref_page.append(group)

        # Disk Usage
        tweak = TweakItem("disk_usage", "Display Disk Usage", "Information", "System", "Safe",
                        "Display overall disk usage")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Large Files
        tweak = TweakItem("large_files", "Locate Large Files", "Information", "System", "Caution",
                        "Find large files in your system")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

    def _create_preferences_group(self):
        """Create the Preferences group."""
        group = PreferencesGroup(title="Preferences", description="Configure system preferences")
        self.pref_page.append(group)

        # Default Browser
        tweak = TweakItem("default_browser", "Default Web Browser", "Preferences", "Internet", "Safe",
                        "Set your default Web browser")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

        # Hibernate/Suspend
        tweak = TweakItem("hibernate_suspend", "Hibernate, Suspend", "Preferences", "Session", "Safe",
                        "Hide or show these buttons at Logout")
        self.tweaks.append(tweak)
        group.add(self._create_tweak_row(tweak))

    def _on_begin_clicked(self, button):
        """Handle Begin button click."""
        if not self.selected_tweaks:
            show_alert(self, "No Tasks Selected", "Please select at least one task.",
                       [("ok", "OK")], lambda r: None)
            return

        # Process selected tweaks
        self._run_selected_tweaks()

    def _run_selected_tweaks(self):
        """Run all selected tweaks."""
        # Separate into GUI actions and batch actions
        gui_actions = {'disk_usage', 'default_browser', 'hibernate_suspend',
                      'preload', 'zram', 'tlp', 'hostname', 'numlock',
                      'save_session', 'kiosk_mode', 'large_files'}

        batch_tasks = []

        for tweak_id in list(self.selected_tweaks):
            if tweak_id in gui_actions:
                self._run_gui_action(tweak_id)
            else:
                batch_tasks.append(tweak_id)

        if batch_tasks:
            self._run_batch_tasks(batch_tasks)

        # Clear selections
        for tweak in self.tweaks:
            if tweak.check_button:
                tweak.check_button.set_active(False)
        self.selected_tweaks.clear()

    def _run_gui_action(self, tweak_id):
        """Run a GUI-based action."""
        if tweak_id == 'disk_usage':
            win = DiskUsageWindow(self)
            win.present()
        elif tweak_id == 'default_browser':
            win = DefaultBrowserWindow(self)
            win.present()
        elif tweak_id == 'hibernate_suspend':
            win = HibernateSuspendWindow(self)
            win.present()
        elif tweak_id == 'preload':
            def check_preload():
                result = subprocess.run(['pgrep', 'preload'], capture_output=True)
                return result.returncode == 0
            win = ServiceManagerWindow(self, 'preload', 'Preload Apps', ['preload'], check_preload,
                                       "Preload monitors application usage and pre-loads common apps")
            win.present()
        elif tweak_id == 'zram':
            def check_zram():
                return os.path.exists('/dev/zram0')
            win = ServiceManagerWindow(self, 'zram', 'zRAM', ['zram-config'], check_zram,
                                       "zRAM creates compressed RAM-based swap")
            win.present()
        elif tweak_id == 'tlp':
            def check_tlp():
                # systemctl is-active is machine-readable and locale-stable;
                # tlp-stat's "State = enabled/disabled" line lies if the
                # service was activated only via `tlp start` (one-shot apply).
                result = subprocess.run(['systemctl', 'is-active', 'tlp.service'],
                                        capture_output=True, text=True)
                return result.stdout.strip() == 'active'
            win = ServiceManagerWindow(self, 'tlp', 'TLP Power Management', ['tlp'], check_tlp,
                                       "TLP optimizes power consumption")
            win.present()
        elif tweak_id == 'hostname':
            self._show_hostname_dialog()
        elif tweak_id == 'numlock':
            self._show_numlock_dialog()
        elif tweak_id == 'save_session':
            self._show_save_session_dialog()
        elif tweak_id == 'kiosk_mode':
            self._show_kiosk_mode_dialog()
        elif tweak_id == 'large_files':
            self._show_large_files_dialog()

    def _show_hostname_dialog(self):
        """Show hostname change dialog."""
        current = subprocess.run(['hostname'], capture_output=True, text=True).stdout.strip()

        entry = Gtk.Entry()
        entry.set_text(current)
        entry.set_margin_start(12)
        entry.set_margin_end(12)

        def on_response(response):
            if response == "change":
                new_hostname = entry.get_text().strip()
                if new_hostname and new_hostname != current:
                    if re.match(r'^[a-zA-Z0-9][-a-zA-Z0-9]*$', new_hostname):
                        username = os.environ.get('USER', 'user')
                        subprocess.run(['pkexec', '/usr/bin/lite-tweaks', '--root-action',
                                      'hostname', new_hostname, username])
                        self.show_toast(f"Hostname changed to {new_hostname}")
                    else:
                        show_alert(self, "Invalid Hostname",
                                   "Hostname must start with a letter/number and contain only letters, numbers, and hyphens.",
                                   [("ok", "OK")], lambda r: None)

        show_alert(self, "Change Hostname",
                   f"Current hostname: {current}\n\nEnter the new hostname:",
                   [("cancel", "Cancel"), ("change", "Change")],
                   on_response, extra_child=entry,
                   appearances={"change": "suggested-action"})

    def _show_numlock_dialog(self):
        """Show numlock configuration dialog."""
        # Check current state
        lightdm_conf = '/etc/lightdm/lightdm.conf'
        current_enabled = False
        if os.path.exists(lightdm_conf):
            with open(lightdm_conf, 'r') as f:
                current_enabled = 'numlockx on' in f.read()

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_start(12)
        box.set_margin_end(12)

        enable_check = Gtk.CheckButton(label="Enable Numlock at login")
        enable_check.set_active(current_enabled)
        box.append(enable_check)

        def on_response(response):
            if response == "apply":
                enabled = enable_check.get_active()
                action = 'enable' if enabled else 'disable'
                subprocess.run(['pkexec', '/usr/bin/lite-tweaks', '--root-action', 'numlock', action])
                state = "enabled" if enabled else "disabled"
                self.show_toast(f"Numlock {state} at login")

        show_alert(self, "Numlock at Login", "Enable or disable Numlock at login.",
                   [("cancel", "Cancel"), ("apply", "Apply")],
                   on_response, extra_child=box,
                   appearances={"apply": "suggested-action"})

    def _show_save_session_dialog(self):
        """Show save session configuration dialog."""
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        box.set_margin_start(12)
        box.set_margin_end(12)

        group = Gtk.CheckButton()

        all_btn = Gtk.CheckButton(label="Enable for all users")
        all_btn.set_group(group)
        all_btn.set_active(True)
        box.append(all_btn)

        admin_btn = Gtk.CheckButton(label="Enable for admins only")
        admin_btn.set_group(group)
        box.append(admin_btn)

        none_btn = Gtk.CheckButton(label="Disable for all users")
        none_btn.set_group(group)
        box.append(none_btn)

        def on_response(response):
            if response == "apply":
                if all_btn.get_active():
                    mode = 'ALL'
                    desc = "all users"
                elif admin_btn.get_active():
                    mode = '%sudo'
                    desc = "admins only"
                else:
                    mode = 'NONE'
                    desc = "disabled"
                subprocess.run(['pkexec', '/usr/bin/lite-tweaks', '--root-action', 'save-session', mode])
                self.show_toast(f"Save session set to {desc}")

        show_alert(self, "Manage Save Session",
                   "Configure who can save their session at logout.",
                   [("cancel", "Cancel"), ("apply", "Apply")],
                   on_response, extra_child=box,
                   appearances={"apply": "suggested-action"})

    def _show_kiosk_mode_dialog(self):
        """Show login/logout options dialog."""
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_start(12)
        box.set_margin_end(12)

        # Logout screen options
        logout_label = Gtk.Label(label="Logout Screen Shutdown Options:")
        logout_label.set_xalign(0)
        box.append(logout_label)

        logout_group = Gtk.CheckButton()

        all_btn = Gtk.CheckButton(label="Show all options for all users")
        all_btn.set_group(logout_group)
        all_btn.set_active(True)
        box.append(all_btn)

        admin_btn = Gtk.CheckButton(label="Show logout only, except for admins")
        admin_btn.set_group(logout_group)
        box.append(admin_btn)

        none_btn = Gtk.CheckButton(label="Show logout only for all users")
        none_btn.set_group(logout_group)
        box.append(none_btn)

        def on_response(response):
            if response == "apply":
                if all_btn.get_active():
                    value = 'ALL'
                    desc = "all users"
                elif admin_btn.get_active():
                    value = '%sudo'
                    desc = "admins only"
                else:
                    value = 'NONE'
                    desc = "logout only"
                subprocess.run(['pkexec', '/usr/bin/lite-tweaks', '--root-action', 'kiosk-mode', 'shutdown', value])
                self.show_toast(f"Shutdown options set to {desc}")

        show_alert(self, "Login & Logout Options",
                   "Configure power options visibility.",
                   [("cancel", "Cancel"), ("apply", "Apply")],
                   on_response, extra_child=box,
                   appearances={"apply": "suggested-action"})

    def _show_large_files_dialog(self):
        """Show large files finder dialog."""
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        box.set_margin_start(12)
        box.set_margin_end(12)

        min_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        min_label = Gtk.Label(label="Minimum size (MB):")
        min_spin = Gtk.SpinButton()
        min_spin.set_range(25, 5000)
        min_spin.set_value(25)
        min_spin.set_increments(25, 100)
        min_box.append(min_label)
        min_box.append(min_spin)
        box.append(min_box)

        max_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        max_label = Gtk.Label(label="Maximum size (MB):")
        max_spin = Gtk.SpinButton()
        max_spin.set_range(50, 5000)
        max_spin.set_value(500)
        max_spin.set_increments(25, 100)
        max_box.append(max_label)
        max_box.append(max_spin)
        box.append(max_box)

        def on_response(response):
            if response == "search":
                min_size = int(min_spin.get_value())
                max_size = int(max_spin.get_value())
                if max_size <= min_size:
                    show_alert(self, "Invalid Range",
                               "Maximum must be greater than minimum.",
                               [("ok", "OK")], lambda r: None)
                    return

                output_file = '/var/log/findfilesrange.log'
                subprocess.run(['pkexec', '/usr/bin/lite-tweaks', '--root-action',
                              'find-files', str(min_size), str(max_size), output_file])

                # Open results in text editor
                if os.path.exists(output_file):
                    subprocess.Popen(['xdg-open', output_file])

        show_alert(self, "Find Large Files",
                   "Specify the file size range to search for.",
                   [("cancel", "Cancel"), ("search", "Search")],
                   on_response, extra_child=box,
                   appearances={"search": "suggested-action"})

    def _run_batch_tasks(self, tasks):
        """Run batch tasks with progress."""
        # Task ID to human-readable name mapping
        TASK_NAMES = {
            'browser_brave': 'Clear Brave Cache',
            'browser_chrome': 'Clear Chrome Cache',
            'browser_chromium': 'Clear Chromium Cache',
            'browser_firefox': 'Clear Firefox Cache',
            'browser_edge': 'Clear Edge Cache',
            'browser_midori': 'Clear Midori Cache',
            'browser_opera': 'Clear Opera Cache',
            'browser_palemoon': 'Clear Pale Moon Cache',
            'browser_vivaldi': 'Clear Vivaldi Cache',
            'thumbnails': 'Clear Thumbnail Cache',
            'trash': 'Empty Trash',
            'taskbar_restore': 'Restore Taskbar',
            'apt_clean': 'Clean APT Cache',
            'clear_mem': 'Clear Memory',
            'log_archives': 'Remove Log Archives',
            'sysd_logs': 'Clean SystemD Logs',
            'resid_config': 'Remove Residual Configs',
            'autoremove': 'Autoremove Packages',
            'fix_apt': 'Repair Package System',
            'fix_bootup': 'Fix Bootup Issues',
        }

        progress_win = ProgressWindow(self, "Running Tasks")
        progress_win.present()

        # Add all tasks to the list
        time.sleep(0.1)  # Brief delay to let window render
        for task_id in tasks:
            task_name = TASK_NAMES.get(task_id, task_id.replace('_', ' ').title())
            progress_win.add_task(task_id, task_name)

        def do_tasks():
            home = os.path.expanduser("~")
            username = os.environ.get('USER', 'user')
            total = len(tasks)
            completed = 0

            # Separate root and non-root tasks
            root_tasks = []

            for task_id in tasks:
                task_name = TASK_NAMES.get(task_id, task_id.replace('_', ' ').title())
                progress_win.set_task_running(task_id)
                progress_win.set_status(f"{task_name}...")
                progress_win.set_progress(completed / total)

                # Non-root tasks
                if task_id.startswith('browser_'):
                    browser = task_id.replace('browser_', '')
                    cache_path = os.path.join(home, BROWSER_CACHES.get(browser, ''))
                    if os.path.exists(cache_path):
                        shutil.rmtree(cache_path, ignore_errors=True)
                    progress_win.set_task_completed(task_id)
                    completed += 1

                elif task_id == 'thumbnails':
                    thumb_path = os.path.join(home, ".cache/thumbnails")
                    if os.path.exists(thumb_path):
                        for item in os.listdir(thumb_path):
                            path = os.path.join(thumb_path, item)
                            if os.path.isfile(path):
                                os.remove(path)
                            else:
                                shutil.rmtree(path, ignore_errors=True)
                    progress_win.set_task_completed(task_id)
                    completed += 1

                elif task_id == 'trash':
                    trash_files = os.path.join(home, ".local/share/Trash/files")
                    trash_info = os.path.join(home, ".local/share/Trash/info")
                    for folder in [trash_files, trash_info]:
                        if os.path.exists(folder):
                            for item in os.listdir(folder):
                                path = os.path.join(folder, item)
                                if os.path.isfile(path):
                                    os.remove(path)
                                else:
                                    shutil.rmtree(path, ignore_errors=True)
                    progress_win.set_task_completed(task_id)
                    completed += 1

                elif task_id == 'taskbar_restore':
                    # Restore the panel from /etc/skel, then re-spawn the
                    # tray clients. On LL 8.0 (xfce4-panel 4.18 + nm-applet
                    # 1.34) tray apps DO NOT auto-re-register against the
                    # new StatusNotifierWatcher when the panel restarts —
                    # nm-applet keeps running but its icon vanishes. So we
                    # kill the well-known tray clients up front and respawn
                    # them after the panel's systray plugin is back.
                    script = (
                        f'xfce4-panel --quit; pkill xfconfd;'
                        f' pkill -TERM nm-applet 2>/dev/null;'
                        f' pkill -TERM blueman-applet 2>/dev/null;'
                        f' pkill -TERM xfce4-clipman 2>/dev/null;'
                        f' pkill -TERM pasystray 2>/dev/null;'
                        f' rm -rf {home}/.config/xfce4/panel;'
                        f' rm -rf {home}/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml;'
                        f' sleep 2;'
                        f' cp -R /etc/skel/.config/xfce4/panel {home}/.config/xfce4/;'
                        f' cp /etc/skel/.config/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml'
                        f' {home}/.config/xfce4/xfconf/xfce-perchannel-xml/;'
                        f' xfce4-panel &'
                        f' sleep 3;'
                        f' for c in nm-applet blueman-applet xfce4-clipman pasystray; do'
                        f'   command -v "$c" >/dev/null 2>&1 &&'
                        f'   nohup "$c" >/dev/null 2>&1 </dev/null &'
                        f' done;'
                        f' disown -a 2>/dev/null || true'
                    )
                    subprocess.Popen(['bash', '-c', script])
                    time.sleep(8)
                    progress_win.set_task_completed(task_id)
                    completed += 1

                # Root tasks - collect for later
                elif task_id == 'apt_clean':
                    root_tasks.append((task_id, 'apt-clean', None, 'Cleaning APT cache...'))
                elif task_id == 'clear_mem':
                    root_tasks.append((task_id, 'clear-mem', None, 'Clearing memory...'))
                elif task_id == 'log_archives':
                    root_tasks.append((task_id, 'log-archives', None, 'Removing log archives...'))
                elif task_id == 'sysd_logs':
                    root_tasks.append((task_id, 'sysd-logs', None, 'Cleaning SystemD logs...'))
                elif task_id == 'resid_config':
                    root_tasks.append((task_id, 'resid-config', None, 'Removing residual configs...'))
                elif task_id == 'autoremove':
                    root_tasks.append((task_id, 'remove-pkgs', username, 'Autoremove packages...'))
                elif task_id == 'fix_apt':
                    root_tasks.append((task_id, 'fix-apt', None, 'Repairing package system...'))
                elif task_id == 'fix_bootup':
                    root_tasks.append((task_id, 'fix-bootup', None, 'Fixing bootup issues...'))

                progress_win.set_progress(completed / total)

            # Run root tasks with individual progress
            for task_id, action, arg, status_msg in root_tasks:
                progress_win.set_task_running(task_id)
                progress_win.set_status(status_msg)

                cmd = ['pkexec', '/usr/bin/lite-tweaks', '--root-action', action]
                if arg:
                    cmd.append(arg)

                # Stage counts for sub-progress within root tasks
                stage_counts = {
                    'apt-clean': 1, 'clear-mem': 2, 'log-archives': 2,
                    'sysd-logs': 2, 'resid-config': 2, 'remove-pkgs': 3,
                    'fix-apt': 7, 'fix-bootup': 5,
                }
                total_stages = stage_counts.get(action, 1)

                proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
                for line in proc.stdout:
                    line = line.strip()
                    if line and line.startswith("Stage"):
                        progress_win.set_status(line)
                        try:
                            stage_num = int(line.split(':')[0].split()[1])
                            frac = (completed + stage_num / total_stages) / total
                            progress_win.set_progress(min(frac, 0.99))
                        except (ValueError, IndexError):
                            pass
                proc.wait()

                progress_win.set_task_completed(task_id)
                completed += 1
                progress_win.set_progress(completed / total)

            progress_win.set_status("All tasks completed!")
            progress_win.set_progress(1.0)

            # Close after a brief delay so user can see completion
            time.sleep(1.5)
            GLib.idle_add(self._on_batch_complete, progress_win)

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

    def _on_batch_complete(self, progress_win):
        """Called on main thread when batch tasks finish."""
        progress_win.close()
        self._refresh_groups()
        self.show_toast("All tasks completed successfully")


def main():
    """Main entry point."""
    # Check if running as root action
    if len(sys.argv) >= 2 and sys.argv[1] == '--root-action':
        sys.exit(handle_root_action())

    # Check if running xauth cleanup
    if len(sys.argv) >= 2 and sys.argv[1] == '--xauth-cleanup':
        run_xauth_cleanup()
        sys.exit(0)

    # Normal GUI mode
    app = LiteTweaksApp()
    sys.exit(app.run(sys.argv))


if __name__ == '__main__':
    main()
