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

import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, Gio, GLib, Gdk

import gettext as _gt, locale as _loc
TEXTDOMAIN = "lite-thememanager"
try:
    _loc.setlocale(_loc.LC_ALL, "")
except _loc.Error:
    pass
_gt.bindtextdomain(TEXTDOMAIN, "/usr/share/locale")
_gt.textdomain(TEXTDOMAIN)
_ = _gt.translation(TEXTDOMAIN, "/usr/share/locale", fallback=True).gettext

import os
import subprocess
import shutil
import tempfile
import threading
import webbrowser
from pathlib import Path


def create_action_row(title, subtitle=None, prefix_icon=None, suffix_icon=None, prefix_widget=None):
    """Create a ListBoxRow with title, subtitle, prefix, and suffix widgets."""
    row = Gtk.ListBoxRow()

    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)

    if prefix_widget:
        prefix_widget.set_valign(Gtk.Align.CENTER)
        hbox.append(prefix_widget)
    elif prefix_icon:
        icon = Gtk.Image.new_from_icon_name(prefix_icon)
        icon.set_valign(Gtk.Align.CENTER)
        hbox.append(icon)

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

    title_label = Gtk.Label(label=title)
    title_label.set_halign(Gtk.Align.START)
    label_box.append(title_label)

    if subtitle:
        sub_label = Gtk.Label(label=subtitle)
        sub_label.set_halign(Gtk.Align.START)
        sub_label.add_css_class("dim-label")
        sub_label.set_wrap(True)
        label_box.append(sub_label)
        row._subtitle_label = sub_label

    hbox.append(label_box)

    if suffix_icon:
        suffix = Gtk.Image.new_from_icon_name(suffix_icon)
        suffix.set_valign(Gtk.Align.CENTER)
        hbox.append(suffix)

    row.set_child(hbox)
    return row


def create_preferences_group(title, description=None):
    """Create a group container with title, optional description, and a ListBox."""
    group_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)

    title_label = Gtk.Label()
    title_label.set_halign(Gtk.Align.START)
    title_label.set_markup(f"<b>{GLib.markup_escape_text(title)}</b>")
    group_box.append(title_label)

    if description:
        desc_label = Gtk.Label(label=description)
        desc_label.set_halign(Gtk.Align.START)
        desc_label.add_css_class("dim-label")
        desc_label.set_wrap(True)
        group_box.append(desc_label)

    listbox = Gtk.ListBox()
    listbox.add_css_class("boxed-list")
    listbox.set_selection_mode(Gtk.SelectionMode.NONE)
    group_box.append(listbox)

    return group_box, listbox


class LiteThemeManager(Gtk.Application):
    def __init__(self):
        super().__init__(
            application_id="com.lite.thememanager",
            flags=Gio.ApplicationFlags.FLAGS_NONE
        )
        self.window = None

        # Directories
        self.home = Path.home()
        self.themes_dir = self.home / ".themes"
        self.icons_dir = self.home / ".icons"
        self.system_themes_dir = Path("/usr/share/themes")
        self.system_icons_dir = Path("/usr/share/icons")

        # Ensure directories exist
        self.themes_dir.mkdir(parents=True, exist_ok=True)
        self.icons_dir.mkdir(parents=True, exist_ok=True)

    def xfconf_get(self, channel, prop):
        """Get a value from xfconf"""
        try:
            result = subprocess.run(
                ['xfconf-query', '-c', channel, '-p', prop],
                capture_output=True, text=True
            )
            if result.returncode == 0:
                return result.stdout.strip()
        except Exception:
            pass
        return ""

    def xfconf_set(self, channel, prop, value):
        """Set a value in xfconf"""
        try:
            subprocess.run(
                ['xfconf-query', '-c', channel, '-p', prop, '-s', value],
                check=True
            )
            return True
        except Exception as e:
            print(f"xfconf-query error: {e}")
            return False

    def do_activate(self):
        # Load custom CSS for boxed list styling
        css_provider = Gtk.CssProvider()
        css_provider.load_from_string("""
            .boxed-list {
                border: 1px solid alpha(currentColor, 0.15);
                border-radius: 12px;
            }
            .boxed-list row {
                min-height: 32px;
                border-bottom: 1px solid alpha(currentColor, 0.08);
            }
            .boxed-list row:last-child {
                border-bottom: none;
            }
            .toast-popup {
                background: rgba(0, 0, 0, 0.75);
                color: white;
                border-radius: 8px;
            }
            .dark-text { color: #1e1e1e; }
            .dim-label { opacity: 0.75; }
        """)
        Gtk.StyleContext.add_provider_for_display(
            Gdk.Display.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        if not self.window:
            self.window = MainWindow(application=self)
        self.window.present()


class MainWindow(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.app = self.get_application()

        self.set_title("Lite Theme Manager")
        self.set_default_size(400, 500)
        self.set_icon_name("lite-thememanager")

        self.setup_ui()

    def setup_ui(self):
        # Header bar
        header = Gtk.HeaderBar()
        self.set_titlebar(header)

        # Main box
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.set_child(main_box)

        # Content box with margins
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        content_box.set_margin_top(24)
        content_box.set_margin_bottom(24)
        content_box.set_margin_start(12)
        content_box.set_margin_end(12)
        main_box.append(content_box)

        # Welcome group
        welcome_box, welcome_list = create_preferences_group(
            _("Welcome to Lite Theme Manager"),
            _("Customize your desktop appearance")
        )
        # Center the welcome title and description
        welcome_title = welcome_box.get_first_child()
        welcome_title.set_halign(Gtk.Align.CENTER)
        welcome_desc = welcome_title.get_next_sibling()
        welcome_desc.set_halign(Gtk.Align.CENTER)
        content_box.append(welcome_box)

        browse_row = create_action_row(
            _("Browse Themes Online"),
            _("Find new themes on Pling.com"),
            prefix_icon="web-browser-symbolic",
            suffix_icon="go-next-symbolic"
        )
        browse_row._callback = self.on_browse_themes
        welcome_list.append(browse_row)
        welcome_list.connect("row-activated", self._on_row_activated)

        # Theme Management group
        manage_box, manage_list = create_preferences_group(_("Theme Management"))
        content_box.append(manage_box)

        install_row = create_action_row(
            _("Install New Theme"),
            _("Install from .tar.gz, .tar.xz, .zip, or .rar"),
            prefix_icon="list-add-symbolic",
            suffix_icon="go-next-symbolic"
        )
        install_row._callback = self.on_install_theme
        manage_list.append(install_row)

        remove_row = create_action_row(
            _("Remove Theme"),
            _("Uninstall user-installed themes"),
            prefix_icon="list-remove-symbolic",
            suffix_icon="go-next-symbolic"
        )
        remove_row._callback = self.on_remove_theme
        manage_list.append(remove_row)
        manage_list.connect("row-activated", self._on_row_activated)

        # Theme Settings group
        settings_box, settings_list = create_preferences_group(_("Theme Settings"))
        content_box.append(settings_box)

        ui_row = create_action_row(
            _("Change UI Theme"),
            _("Change the GTK application theme"),
            prefix_icon="preferences-desktop-theme-symbolic",
            suffix_icon="go-next-symbolic"
        )
        ui_row._callback = self.on_change_ui_theme
        settings_list.append(ui_row)

        cursor_row = create_action_row(
            _("Change Mouse Cursor Theme"),
            _("Change the cursor appearance"),
            prefix_icon="input-mouse-symbolic",
            suffix_icon="go-next-symbolic"
        )
        cursor_row._callback = self.on_change_cursor_theme
        settings_list.append(cursor_row)

        icons_row = create_action_row(
            _("Change Icons Theme"),
            _("Change the icon set"),
            prefix_icon="folder-symbolic",
            suffix_icon="go-next-symbolic"
        )
        icons_row._callback = self.on_change_icons_theme
        settings_list.append(icons_row)

        folder_color_row = create_action_row(
            _("Change Folder Color"),
            _("Change the Papirus folder icon color"),
            prefix_icon="folder-green",
            suffix_icon="go-next-symbolic"
        )
        folder_color_row._callback = self.on_change_folder_color
        settings_list.append(folder_color_row)

        settings_list.connect("row-activated", self._on_row_activated)

        # Toast notification revealer
        self.toast_revealer = Gtk.Revealer()
        self.toast_revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_UP)
        self.toast_revealer.set_reveal_child(False)

        toast_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        toast_box.set_halign(Gtk.Align.CENTER)
        toast_box.set_margin_bottom(12)

        self.toast_label = Gtk.Label()
        self.toast_label.set_margin_top(8)
        self.toast_label.set_margin_bottom(8)
        self.toast_label.set_margin_start(16)
        self.toast_label.set_margin_end(16)
        toast_box.append(self.toast_label)
        toast_box.add_css_class("toast-popup")

        self.toast_revealer.set_child(toast_box)
        main_box.append(self.toast_revealer)

    def _on_row_activated(self, listbox, row):
        if hasattr(row, '_callback') and row._callback:
            row._callback(row)

    def on_browse_themes(self, row):
        webbrowser.open("https://www.pling.com/s/XFCE/browse")

    def on_install_theme(self, row):
        dialog = InstallThemeDialog(self)
        dialog.present()

    def on_remove_theme(self, row):
        dialog = RemoveThemeDialog(self)
        dialog.present()

    def on_change_ui_theme(self, row):
        themes = self.get_ui_themes()
        current = self.app.xfconf_get('xsettings', '/Net/ThemeName')
        dialog = ThemeSelectionDialog(
            self, _("Select UI Theme"), themes, current,
            lambda theme: self.apply_ui_theme(theme)
        )
        dialog.present()

    def on_change_cursor_theme(self, row):
        themes = self.get_cursor_themes()
        current = self.app.xfconf_get('xsettings', '/Gtk/CursorThemeName')
        dialog = ThemeSelectionDialog(
            self, _("Select Cursor Theme"), themes, current,
            lambda theme: self.apply_cursor_theme(theme)
        )
        dialog.present()

    def on_change_icons_theme(self, row):
        themes = self.get_icon_themes()
        current = self.app.xfconf_get('xsettings', '/Net/IconThemeName')
        dialog = ThemeSelectionDialog(
            self, _("Select Icons Theme"), themes, current,
            lambda theme: self.apply_icon_theme(theme)
        )
        dialog.present()

    def on_change_folder_color(self, row):
        dialog = FolderColorDialog(self)
        dialog.present()

    def get_ui_themes(self):
        themes = set()
        for themes_dir in [self.app.themes_dir, self.app.system_themes_dir]:
            if themes_dir.exists():
                for item in themes_dir.iterdir():
                    if item.is_dir() and not item.name.startswith('.'):
                        themes.add(item.name)
        return sorted(themes)

    def get_cursor_themes(self):
        themes = set()
        for icons_dir in [self.app.icons_dir, self.app.system_icons_dir]:
            if icons_dir.exists():
                for item in icons_dir.iterdir():
                    if item.is_dir() and not item.name.startswith('.') and (item / "cursors").exists():
                        themes.add(item.name)
        return sorted(themes)

    def get_icon_themes(self):
        themes = set()
        for icons_dir in [self.app.icons_dir, self.app.system_icons_dir]:
            if icons_dir.exists():
                for item in icons_dir.iterdir():
                    if (item.is_dir() and not item.name.startswith('.')
                            and (item / "index.theme").exists()
                            and not (item / "cursors").exists()):
                        themes.add(item.name)
        return sorted(themes)

    def apply_ui_theme(self, theme):
        # Set GTK theme
        self.app.xfconf_set('xsettings', '/Net/ThemeName', theme)
        # Set window manager theme
        self.app.xfconf_set('xfwm4', '/general/theme', theme)
        self.show_toast(_("UI theme changed to {theme}").format(theme=theme))

    def apply_cursor_theme(self, theme):
        self.app.xfconf_set('xsettings', '/Gtk/CursorThemeName', theme)
        self.show_toast(_("Cursor theme changed to {theme}").format(theme=theme))

    def apply_icon_theme(self, theme):
        self.app.xfconf_set('xsettings', '/Net/IconThemeName', theme)
        self.show_toast(_("Icon theme changed to {theme}").format(theme=theme))

    def show_toast(self, message, persistent=False):
        self.toast_label.set_label(message)
        self.toast_revealer.set_reveal_child(True)
        if not persistent:
            GLib.timeout_add(3000, self._hide_toast)

    def _hide_toast(self):
        self.toast_revealer.set_reveal_child(False)
        return False


class ThemeSelectionDialog(Gtk.Window):
    def __init__(self, parent, title, themes, current_theme, callback):
        super().__init__()
        self.parent_window = parent
        self.callback = callback
        self.selected_theme = current_theme

        self.set_title(title)
        self.set_default_size(400, 650)
        self.set_transient_for(parent)
        self.set_modal(True)

        # Header bar
        header = Gtk.HeaderBar()
        header.set_show_title_buttons(False)

        cancel_btn = Gtk.Button(label=_("Cancel"))
        cancel_btn.connect("clicked", lambda b: self.close())
        header.pack_start(cancel_btn)

        apply_btn = Gtk.Button(label=_("Apply"))
        apply_btn.add_css_class("suggested-action")
        apply_btn.connect("clicked", self.on_apply)
        header.pack_end(apply_btn)

        self.set_titlebar(header)

        # Main content
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.set_child(main_box)

        # Scrolled window for theme list
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        main_box.append(scrolled)

        # Content with margins
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        content_box.set_margin_top(12)
        content_box.set_margin_bottom(12)
        content_box.set_margin_start(12)
        content_box.set_margin_end(12)
        scrolled.set_child(content_box)

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

        # Add themes with radio button behavior
        self.check_buttons = {}
        first_check = None

        if not themes:
            row = create_action_row(_("No themes found"))
            row.set_sensitive(False)
            self.listbox.append(row)
        else:
            for theme in themes:
                check = Gtk.CheckButton()
                if first_check is None:
                    first_check = check
                else:
                    check.set_group(first_check)

                check.set_active(theme == current_theme)
                check.connect("toggled", self.on_check_toggled, theme)
                self.check_buttons[theme] = check

                row = create_action_row(theme, prefix_widget=check)
                row._activatable_widget = check
                self.listbox.append(row)

        self.listbox.connect("row-activated", self._on_row_activated)

    def _on_row_activated(self, listbox, row):
        if hasattr(row, '_activatable_widget'):
            row._activatable_widget.set_active(True)

    def on_check_toggled(self, check_button, theme):
        if check_button.get_active():
            self.selected_theme = theme

    def on_apply(self, button):
        if self.selected_theme:
            self.callback(self.selected_theme)
        self.close()


class InstallThemeDialog(Gtk.Window):
    def __init__(self, parent):
        super().__init__()
        self.parent_window = parent
        self.selected_file = None
        self.selected_type = None

        self.set_title(_("Install Theme"))
        self.set_default_size(400, 400)
        self.set_transient_for(parent)
        self.set_modal(True)

        # Header bar
        header = Gtk.HeaderBar()
        header.set_show_title_buttons(False)

        cancel_btn = Gtk.Button(label=_("Cancel"))
        cancel_btn.connect("clicked", lambda b: self.close())
        header.pack_start(cancel_btn)

        self.install_btn = Gtk.Button(label=_("Install"))
        self.install_btn.add_css_class("suggested-action")
        self.install_btn.set_sensitive(False)
        self.install_btn.connect("clicked", self.on_install)
        header.pack_end(self.install_btn)

        self.set_titlebar(header)

        # Content
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        content_box.set_margin_top(24)
        content_box.set_margin_bottom(24)
        content_box.set_margin_start(12)
        content_box.set_margin_end(12)
        self.set_child(content_box)

        # Info banner
        banner_label = Gtk.Label(label=_("Download themes from the web in .tar.gz, .tar.xz, .zip, or .rar format"))
        banner_label.set_wrap(True)
        banner_label.add_css_class("dim-label")
        content_box.append(banner_label)

        # File selection group
        file_box, file_list = create_preferences_group(_("Theme File"))
        content_box.append(file_box)

        self.file_row = create_action_row(
            _("Select File"),
            _("No file selected"),
            prefix_icon="folder-open-symbolic",
            suffix_icon="go-next-symbolic"
        )
        self.file_row._callback = self.on_select_file
        file_list.append(self.file_row)
        file_list.connect("row-activated", self._on_row_activated)

        # Type selection group
        type_box, type_list = create_preferences_group(_("Theme Type"))
        content_box.append(type_box)

        # Radio buttons for type selection
        self.ui_check = Gtk.CheckButton()
        self.icon_check = Gtk.CheckButton()
        self.cursor_check = Gtk.CheckButton()

        self.icon_check.set_group(self.ui_check)
        self.cursor_check.set_group(self.ui_check)

        self.ui_check.connect("toggled", self.on_type_changed, "UI theme")
        self.icon_check.connect("toggled", self.on_type_changed, "Icons theme")
        self.cursor_check.connect("toggled", self.on_type_changed, "Cursor theme")

        ui_row = create_action_row(
            _("UI Theme"), _("GTK themes for window decoration"),
            prefix_widget=self.ui_check
        )
        ui_row._activatable_widget = self.ui_check
        type_list.append(ui_row)

        icon_row = create_action_row(
            _("Icon Theme"), _("Icon sets for applications"),
            prefix_widget=self.icon_check
        )
        icon_row._activatable_widget = self.icon_check
        type_list.append(icon_row)

        cursor_row = create_action_row(
            _("Cursor Theme"), _("Mouse cursor styles"),
            prefix_widget=self.cursor_check
        )
        cursor_row._activatable_widget = self.cursor_check
        type_list.append(cursor_row)

        type_list.connect("row-activated", self._on_type_row_activated)

    def _on_row_activated(self, listbox, row):
        if hasattr(row, '_callback') and row._callback:
            row._callback(row)

    def _on_type_row_activated(self, listbox, row):
        if hasattr(row, '_activatable_widget'):
            row._activatable_widget.set_active(True)

    def on_select_file(self, row):
        dialog = Gtk.FileDialog()
        dialog.set_title(_("Select Theme File"))

        # File filter
        filter_store = Gio.ListStore.new(Gtk.FileFilter)

        archive_filter = Gtk.FileFilter()
        archive_filter.set_name(_("Archive files"))
        archive_filter.add_pattern("*.tar.gz")
        archive_filter.add_pattern("*.tar.xz")
        archive_filter.add_pattern("*.zip")
        archive_filter.add_pattern("*.rar")
        filter_store.append(archive_filter)

        dialog.set_filters(filter_store)
        dialog.set_default_filter(archive_filter)

        dialog.open(self.parent_window, None, self.on_file_selected)

    def on_file_selected(self, dialog, result):
        try:
            file = dialog.open_finish(result)
            if file:
                self.selected_file = file.get_path()
                if hasattr(self.file_row, '_subtitle_label'):
                    self.file_row._subtitle_label.set_label(Path(self.selected_file).name)
                self.update_install_button()
        except GLib.Error:
            pass

    def on_type_changed(self, button, theme_type):
        if button.get_active():
            self.selected_type = theme_type
            self.update_install_button()

    def update_install_button(self):
        self.install_btn.set_sensitive(
            self.selected_file is not None and self.selected_type is not None
        )

    def on_install(self, button):
        if not self.selected_file or not self.selected_type:
            return

        try:
            temp_dir = tempfile.mkdtemp()
            file_path = self.selected_file

            # Extract archive
            if file_path.endswith('.tar.gz'):
                subprocess.run(['tar', '-xzf', file_path, '-C', temp_dir], check=True)
            elif file_path.endswith('.tar.xz'):
                subprocess.run(['tar', '-xJf', file_path, '-C', temp_dir], check=True)
            elif file_path.endswith('.zip'):
                subprocess.run(['unzip', '-q', file_path, '-d', temp_dir], check=True)
            elif file_path.endswith('.rar'):
                subprocess.run(['unrar', 'x', '-y', file_path, temp_dir], check=True)
            else:
                self.show_error(_("Unsupported file format"))
                shutil.rmtree(temp_dir)
                return

            # Find and move theme
            installed = False
            app = self.parent_window.app

            for root, dirs, files in os.walk(temp_dir):
                root_path = Path(root)

                if self.selected_type == "UI theme":
                    # Look for GTK theme indicators
                    if (root_path / "gtk-3.0").exists() or (root_path / "gtk-4.0").exists() or (root_path / "xfwm4").exists():
                        dest = app.themes_dir / root_path.name
                        if dest.exists():
                            shutil.rmtree(dest)
                        shutil.move(str(root_path), str(app.themes_dir))
                        installed = True
                        break

                elif self.selected_type == "Icons theme":
                    if (root_path / "index.theme").exists() and not (root_path / "cursors").exists():
                        dest = app.icons_dir / root_path.name
                        if dest.exists():
                            shutil.rmtree(dest)
                        shutil.move(str(root_path), str(app.icons_dir))
                        installed = True
                        break

                elif self.selected_type == "Cursor theme":
                    if (root_path / "cursors").exists():
                        dest = app.icons_dir / root_path.name
                        if dest.exists():
                            shutil.rmtree(dest)
                        shutil.move(str(root_path), str(app.icons_dir))
                        installed = True
                        break

            shutil.rmtree(temp_dir, ignore_errors=True)

            if installed:
                self.parent_window.show_toast(_("Theme installed successfully!"))
                self.close()
            else:
                self.show_error(_("Could not find valid theme in archive"))

        except subprocess.CalledProcessError as e:
            self.show_error(_("Extraction failed: {e}").format(e=e))
        except Exception as e:
            self.show_error(_("Installation failed: {e}").format(e=e))

    def show_error(self, message):
        dialog = Gtk.AlertDialog()
        dialog.set_message(_("Error"))
        dialog.set_detail(message)
        dialog.set_buttons(["OK"])
        dialog.show(self.parent_window)


class RemoveThemeDialog(Gtk.Window):
    def __init__(self, parent):
        super().__init__()
        self.parent_window = parent
        self.selected_theme = None
        self.target_dir = None

        self.set_title(_("Remove Theme"))
        self.set_default_size(400, 450)
        self.set_transient_for(parent)
        self.set_modal(True)

        # Header bar
        header = Gtk.HeaderBar()
        header.set_show_title_buttons(False)

        cancel_btn = Gtk.Button(label=_("Cancel"))
        cancel_btn.connect("clicked", lambda b: self.close())
        header.pack_start(cancel_btn)

        self.remove_btn = Gtk.Button(label=_("Remove"))
        self.remove_btn.add_css_class("destructive-action")
        self.remove_btn.set_sensitive(False)
        self.remove_btn.connect("clicked", self.on_remove)
        header.pack_end(self.remove_btn)

        self.set_titlebar(header)

        # Content
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        content_box.set_margin_top(12)
        content_box.set_margin_bottom(12)
        content_box.set_margin_start(12)
        content_box.set_margin_end(12)
        self.set_child(content_box)

        # Type selection
        type_label = Gtk.Label()
        type_label.set_halign(Gtk.Align.START)
        type_label.set_markup(_("<b>Select Theme Type</b>"))
        content_box.append(type_label)

        type_model = Gtk.StringList.new([_("UI Theme"), _("Icon Theme"), _("Cursor Theme")])
        self.type_dropdown = Gtk.DropDown(model=type_model)
        self.type_dropdown.connect("notify::selected", self.on_type_selected)
        content_box.append(self.type_dropdown)

        # Theme list header
        themes_label = Gtk.Label()
        themes_label.set_halign(Gtk.Align.START)
        themes_label.set_markup(_("<b>User Themes</b>"))
        themes_label.set_margin_top(12)
        content_box.append(themes_label)

        themes_desc = Gtk.Label(label=_("Only user-installed themes can be removed"))
        themes_desc.set_halign(Gtk.Align.START)
        themes_desc.add_css_class("dim-label")
        content_box.append(themes_desc)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)
        scrolled.set_min_content_height(200)
        content_box.append(scrolled)

        self.listbox = Gtk.ListBox()
        self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.listbox.add_css_class("boxed-list")
        self.listbox.connect("row-selected", self.on_theme_selected)
        scrolled.set_child(self.listbox)

        # Initial population
        self.populate_themes()

    def on_type_selected(self, dropdown, param):
        self.populate_themes()

    def populate_themes(self):
        # Clear existing
        while True:
            row = self.listbox.get_row_at_index(0)
            if row:
                self.listbox.remove(row)
            else:
                break

        self.selected_theme = None
        self.remove_btn.set_sensitive(False)

        app = self.parent_window.app
        selected_type = self.type_dropdown.get_selected()

        themes = []
        if selected_type == 0:  # UI Theme
            self.target_dir = app.themes_dir
            if app.themes_dir.exists():
                themes = [d.name for d in app.themes_dir.iterdir() if d.is_dir()]
        elif selected_type == 1:  # Icon Theme
            self.target_dir = app.icons_dir
            if app.icons_dir.exists():
                themes = [d.name for d in app.icons_dir.iterdir()
                         if d.is_dir() and not (d / "cursors").exists()]
        elif selected_type == 2:  # Cursor Theme
            self.target_dir = app.icons_dir
            if app.icons_dir.exists():
                themes = [d.name for d in app.icons_dir.iterdir()
                         if d.is_dir() and (d / "cursors").exists()]

        themes.sort()

        if not themes:
            row = create_action_row(_("No user themes found"))
            row.set_sensitive(False)
            self.listbox.append(row)
        else:
            for theme in themes:
                row = create_action_row(theme, prefix_icon="user-trash-symbolic")
                row.theme_name = theme
                self.listbox.append(row)

    def on_theme_selected(self, listbox, row):
        if row and hasattr(row, 'theme_name'):
            self.selected_theme = row.theme_name
            self.remove_btn.set_sensitive(True)
        else:
            self.selected_theme = None
            self.remove_btn.set_sensitive(False)

    def on_remove(self, button):
        if not self.selected_theme or not self.target_dir:
            return

        # Confirmation dialog
        dialog = Gtk.AlertDialog()
        dialog.set_message(_("Remove Theme?"))
        dialog.set_detail(_("Are you sure you want to remove '{theme}'? This cannot be undone.").format(theme=self.selected_theme))
        dialog.set_buttons(["Cancel", "Remove"])
        dialog.set_cancel_button(0)
        dialog.set_default_button(0)
        dialog.choose(self.parent_window, None, self.on_confirm_remove)

    def on_confirm_remove(self, dialog, result):
        try:
            response = dialog.choose_finish(result)
        except GLib.Error:
            return
        if response == 1:  # "Remove" button
            try:
                theme_path = self.target_dir / self.selected_theme
                shutil.rmtree(theme_path)
                self.parent_window.show_toast(_("Theme '{theme}' removed").format(theme=self.selected_theme))
                self.populate_themes()
            except Exception as e:
                error_dialog = Gtk.AlertDialog()
                error_dialog.set_message(_("Error"))
                error_dialog.set_detail(_("Failed to remove theme: {e}").format(e=e))
                error_dialog.set_buttons(["OK"])
                error_dialog.show(self.parent_window)


class FolderColorDialog(Gtk.Window):
    """Dialog for changing Papirus folder icon colors."""

    # Base colors available in Papirus (display name -> folder suffix)
    COLORS = [
        (_("Blue (Default)"), "blue"),
        (_("Black"), "black"),
        (_("Brown"), "brown"),
        (_("Cyan"), "cyan"),
        (_("Green"), "green"),
        (_("Grey"), "grey"),
        (_("Magenta"), "magenta"),
        (_("Orange"), "orange"),
        (_("Pink"), "pink"),
        (_("Purple"), "purple"),
        ("Red", "red"),
        (_("Violet"), "violet"),
        (_("White"), "white"),
        (_("Yellow"), "yellow"),
    ]

    # Papirus theme variants to update
    PAPIRUS_VARIANTS = ["Papirus", "Papirus-Adapta", "Papirus-Adapta-Nokto", "Papirus-Dark", "Papirus-Light", "ePapirus", "ePapirus-Dark"]

    # Icon sizes that contain places
    ICON_SIZES = ["16x16", "22x22", "24x24", "32x32", "48x48", "64x64"]

    def __init__(self, parent):
        super().__init__()
        self.parent_window = parent
        self.selected_color = None

        self.set_title(_("Change Folder Color"))
        self.set_default_size(400, 650)
        self.set_transient_for(parent)
        self.set_modal(True)

        # Detect current color
        self.current_color = self._detect_current_color()

        # Header bar
        header = Gtk.HeaderBar()
        header.set_show_title_buttons(False)

        cancel_btn = Gtk.Button(label=_("Cancel"))
        cancel_btn.connect("clicked", lambda b: self.close())
        header.pack_start(cancel_btn)

        apply_btn = Gtk.Button(label=_("Apply"))
        apply_btn.add_css_class("suggested-action")
        apply_btn.connect("clicked", self.on_apply)
        header.pack_end(apply_btn)

        self.set_titlebar(header)

        # Main content
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.set_child(main_box)

        # Info banner
        info_label = Gtk.Label(label="Select a folder color for Papirus icons. This changes the default folder color system-wide and requires your password.")
        info_label.set_wrap(True)
        info_label.add_css_class("dim-label")
        info_label.set_margin_top(12)
        info_label.set_margin_start(12)
        info_label.set_margin_end(12)
        info_label.set_margin_bottom(6)
        main_box.append(info_label)

        # Scrolled window for color list
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_vexpand(True)
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        main_box.append(scrolled)

        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        content_box.set_margin_top(12)
        content_box.set_margin_bottom(12)
        content_box.set_margin_start(12)
        content_box.set_margin_end(12)
        scrolled.set_child(content_box)

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

        # Add color options with radio buttons and folder icon preview
        first_check = None
        for display_name, color_key in self.COLORS:
            check = Gtk.CheckButton()
            if first_check is None:
                first_check = check
            else:
                check.set_group(first_check)

            is_current = (color_key == self.current_color)
            check.set_active(is_current)
            if is_current:
                self.selected_color = color_key
            check.connect("toggled", self._on_color_toggled, color_key)

            # Show a folder icon preview from the file directly
            icon_file = f"/usr/share/icons/Papirus/48x48/places/folder-{color_key}.svg"
            if os.path.exists(icon_file):
                preview = Gtk.Image.new_from_file(icon_file)
            else:
                preview = Gtk.Image.new_from_icon_name(f"folder-{color_key}")
            preview.set_pixel_size(24)
            preview.set_margin_end(8)

            prefix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
            prefix_box.append(check)
            prefix_box.append(preview)

            row = create_action_row(display_name, prefix_widget=prefix_box)
            row._activatable_widget = check
            self.listbox.append(row)

        self.listbox.connect("row-activated", self._on_row_activated)

    def _detect_current_color(self):
        """Detect the current folder color by reading the folder.svg symlink from the active icon theme."""
        # Try the active icon theme first, then fall back to Papirus
        try:
            result = subprocess.run(
                ["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName"],
                capture_output=True, text=True
            )
            active_theme = result.stdout.strip() if result.returncode == 0 else "Papirus"
        except Exception:
            active_theme = "Papirus"
        for theme in [active_theme, "Papirus"]:
            try:
                link = Path(f"/usr/share/icons/{theme}/48x48/places/folder.svg")
                if link.is_symlink():
                    target = os.readlink(str(link))
                    if target.startswith("folder-") and target.endswith(".svg"):
                        return target[7:-4]
            except Exception:
                continue
        return "blue"

    def _on_row_activated(self, listbox, row):
        if hasattr(row, '_activatable_widget'):
            row._activatable_widget.set_active(True)

    def _on_color_toggled(self, check_button, color_key):
        if check_button.get_active():
            self.selected_color = color_key

    def on_apply(self, button):
        if not self.selected_color or self.selected_color == self.current_color:
            self.close()
            return

        color = self.selected_color

        # Build the shell script to re-point symlinks (runs as root via pkexec)
        script = f'''#!/bin/bash
ICONS_DIR="/usr/share/icons"
COLOR="{color}"

for variant in Papirus Papirus-Adapta Papirus-Adapta-Nokto Papirus-Dark Papirus-Light ePapirus ePapirus-Dark; do
    for size in 16x16 22x22 24x24 32x32 48x48 64x64; do
        places_dir="$ICONS_DIR/$variant/$size/places"
        [ -d "$places_dir" ] || continue

        cd "$places_dir" || continue

        # Re-point the main folder.svg symlink
        if [ -L "folder.svg" ]; then
            ln -sf "folder-$COLOR.svg" "folder.svg"
        fi

        # Re-point all folder-*.svg symlinks that point to a colored variant
        for link in folder-*.svg; do
            [ -L "$link" ] || continue
            target=$(readlink "$link")

            suffix="${{link#folder-}}"
            suffix="${{suffix%.svg}}"

            case "$target" in
                folder-*-"$suffix".svg)
                    ln -sf "folder-$COLOR-$suffix.svg" "$link"
                    ;;
            esac
        done
    done
done
'''

        # Write script to temp file
        script_path = '/tmp/papirus-folder-color.sh'
        try:
            with open(script_path, 'w') as f:
                f.write(script)
            os.chmod(script_path, 0o755)
        except Exception as e:
            error_dialog = Gtk.AlertDialog()
            error_dialog.set_message(_("Error"))
            error_dialog.set_detail(_("Failed to change folder color:\n{detail}").format(detail=e))
            error_dialog.set_buttons(["OK"])
            error_dialog.show(self)
            return

        # Close dialog and show progress notification
        parent = self.parent_window
        self.close()
        parent.show_toast(_("Applying folder color change..."), persistent=True)

        # Run pkexec in a background thread to avoid freezing the UI
        def run_pkexec():
            try:
                result = subprocess.run(
                    ['pkexec', '/usr/bin/bash', script_path],
                    capture_output=True, text=True
                )
                # Cleanup
                try:
                    os.remove(script_path)
                except Exception:
                    pass
                # Update UI on the main thread
                if result.returncode == 0:
                    # Toggle icon theme to force a clean refresh (runs as user)
                    self._refresh_icons()
                    GLib.idle_add(parent.show_toast, _("Folder color changed to {color}").format(color=color))
                else:
                    GLib.idle_add(self._show_error, parent,
                                 _("Failed to change folder color:\n{detail}").format(detail=result.stderr))
            except Exception as e:
                GLib.idle_add(self._show_error, parent,
                             _("Failed to change folder color:\n{detail}").format(detail=e))

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

    @staticmethod
    def _refresh_icons():
        """Toggle the icon theme off and back to flush cached icons cleanly."""
        try:
            result = subprocess.run(
                ["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName"],
                capture_output=True, text=True
            )
            theme = result.stdout.strip() if result.returncode == 0 else "Papirus"
            subprocess.run(
                ["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName", "-s", "hicolor"],
                capture_output=True
            )
            import time
            time.sleep(0.3)
            subprocess.run(
                ["xfconf-query", "-c", "xsettings", "-p", "/Net/IconThemeName", "-s", theme],
                capture_output=True
            )
        except Exception:
            pass

    @staticmethod
    def _show_error(parent, detail):
        error_dialog = Gtk.AlertDialog()
        error_dialog.set_message(_("Error"))
        error_dialog.set_detail(detail)
        error_dialog.set_buttons(["OK"])
        error_dialog.show(parent)


def main():
    app = LiteThemeManager()
    app.run(None)


if __name__ == "__main__":
    main()
