/* Desktop Icons GNOME Shell extension
 *
 * Copyright (C) 2023 Sundeep Mediratta (smedius@gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import {Gtk, Gdk, Gio, GLib, GObject, Adw} from '../dependencies/gi.js';
import {DesktopWidgetCapability} from '../dependencies/gi.js';
import {_} from '../dependencies/gettext.js';
import {DesktopFolderUtils} from '../dependencies/localFiles.js';

export {AdwPreferencesWindow};

const appID = 'com.desktop.ding';
const appPath = GLib.build_filenamev(['/', ...appID.split('.')]);

const ListObject = GObject.registerClass({
    GTypeName: 'peferences-list',
    Properties: {
        'indexkey': GObject.ParamSpec.string(
            'indexkey',
            'Indexkey',
            'A read-write string property',
            GObject.ParamFlags.READWRITE,
            ''
        ),
        'description': GObject.ParamSpec.string(
            'description',
            'Description',
            'A read-write string property',
            GObject.ParamFlags.READWRITE,
            ''
        ),
    },
}, class listObject extends GObject.Object {
    constructor(constructProperties = {}) {
        super(constructProperties);
    }

    get indexkey() {
        if (this._indexkey === undefined)
            this._indexkey = '';

        return this._indexkey;
    }

    set indexkey(value) {
        if (this.indexkey === value)
            return;

        this._indexkey = value;
        this.notify('indexkey');
    }

    get description() {
        if (this._description === undefined)
            this._description = '';

        return this._description;
    }

    set description(value) {
        if (this.description === value)
            return;

        this._description = value;
        this.notify('description');
    }
});

const ComboRowWithKey = GObject.registerClass({
    GTypeName: 'ComboRowWithKey',
    Properties: {
        'indexkey': GObject.ParamSpec.string(
            'indexkey',
            'Indexkey',
            'A read-write string property',
            GObject.ParamFlags.READWRITE,
            ''
        ),
    },
}, class ComboRowWithKey extends Adw.ComboRow {
    constructor(constructProperties = {}) {
        super(constructProperties);
        this._indexKey = '';
        this.connect('notify::selected-item', () => {
            let item = this.get_selected_item();
            this.indexkey = item.indexkey;
        });
    }

    makeEnumn(enumexpression) {
        const listStore = new Gio.ListStore(ListObject._$gtype);
        this.enumExpression = {};
        let i = 0;
        for (let key in enumexpression) {
            this.enumExpression[key] = parseInt(i);
            let listObject = new ListObject();
            listObject.indexkey = key;
            listObject.description = enumexpression[key];
            listStore.append(listObject);
            i += 1;
        }

        this.set_model(listStore);

        const listFactory = new Gtk.SignalListItemFactory();
        listFactory.connect('setup', (_actor, listitem) => {
            let label = new Gtk.Label();
            listitem.set_child(label);
        });
        listFactory.connect('bind', (_actor, listitem) => {
            let label = listitem.get_child();
            let item = listitem.get_item();
            label.set_text(item.description);
        });
        this.set_factory(listFactory);

        const expression = new Gtk.PropertyExpression(ListObject,
            null,
            'description'
        );
        this.set_expression(expression);
    }

    get indexkey() {
        if (this._indexkey === undefined)
            this._indexkey = '';

        return this._indexkey;
    }

    set indexkey(value) {
        if (this.indexkey === value)
            return;

        this._indexkey = value;

        if (this.get_selected !== this.enumExpression[value])
            this.set_selected(this.enumExpression[value]);

        this.notify('indexkey');
    }
});

const CssOverrideGroup = GObject.registerClass(
class CssOverrideGroup extends Adw.PreferencesGroup {
    constructor(params = {}) {
        super({});
        this.set_title(_('CSS Override'));
        this.set_description(_('Customise the appearance of desktop icons with CSS'));
        const warningLabel = new Gtk.Label();
        warningLabel.set_markup(
            `<span style="italic" foreground="red">${
                _('Warning: This can break the extension if done incorrectly')
            }</span>`
        );
        this.add(warningLabel);
        const icon = Gtk.Image.new_from_icon_name('ding-window-pop-out-symbolic');
        this.cssOverrideButton = new Adw.ActionRow({
            title: _('Edit CSS Override File...'),
        });
        this.cssOverrideButton.add_suffix(icon);
        this.cssOverrideButton.set_activatable_widget(icon);
        this.cssOverrideButton.connect('activated', this.openUserCssOverrideFile.bind(this));
        this.add(this.cssOverrideButton);

        this.reloadButtonRow = new Adw.ActionRow({
            title: _('Apply CSS Changes Now'),
            subtitle: _('Reload the CSS to apply changes immediately'),

        });
        const button = Gtk.Button.new_with_label('Reload');
        button.set_size_request(120, -1);
        button.set_halign(Gtk.Align.END);
        button.set_valign(Gtk.Align.CENTER);
        button.set_hexpand(true);
        button.set_vexpand(false);

        button.connect('clicked', () => {
            this.reloadCSS();
        });
        this.reloadButtonRow.add_suffix(button);
        this.reloadButtonRow.set_activatable_widget(button);
        this.add(this.reloadButtonRow);
        this.update(params.remoteActions);
    }

    reloadCSS() {
        try {
            this.remoteActions.activate_action('reloadCSS', null);
            console.info('CSS reload requested');
        } catch (e) {
            console.error(`Failed to reload CSS: ${e}`);
        }
    }

    update(remoteActions) {
        this.remoteActions = remoteActions;
        if (this.remoteActions?.list_actions())
            this.reloadButtonRow.set_sensitive(true);
        else
            this.reloadButtonRow.set_sensitive(false);
    }


    openUserCssOverrideFile() {
        const configDir = GLib.get_user_config_dir();
        const cssFile = Gio.File.new_for_path(
            GLib.build_filenamev([configDir, appID, 'stylesheet-override.css'])
        );

        // Create directory if it doesn't exist
        const cssDir = cssFile.get_parent();
        try {
            cssDir.make_directory_with_parents(null);
        } catch (e) {
            // Directory already exists
        }

        // Create file if it doesn't exist
        if (!cssFile.query_exists(null))
            cssFile.create(Gio.FileCreateFlags.NONE, null);


        // Open with default text editor
        const context = Gdk.Display.get_default().get_app_launch_context();
        context.set_timestamp(Gdk.CURRENT_TIME);
        Gio.AppInfo.launch_default_for_uri(cssFile.get_uri(), context);
    }
});

const ShortcutGroup = GObject.registerClass(
class ShortcutGroup extends Adw.PreferencesGroup {
    constructor(params = {}) {
        super({});
        this.set_title(_('Shortcuts'));

        this.shortcutButton = new Adw.ActionRow({
            title: _('Edit Shortcuts...'),
        });
        const icon = Gtk.Image.new_from_icon_name('ding-window-pop-out-symbolic');
        this.shortcutButton.add_suffix(icon);
        this.shortcutButton.set_activatable_widget(icon);

        this.shortcutButton.connect('activated', this.showShortcuts.bind(this));
        this.add(this.shortcutButton);
        this.update(params.remoteActions);
    }

    update(remoteActions) {
        this.remoteActions = remoteActions;
        if (this.remoteActions?.list_actions()) {
            this.shortcutButton.set_sensitive(true);
            this.set_description(_('Edit Application Shortcuts'));
        } else {
            this.shortcutButton.set_sensitive(false);

            this.set_description(
                _('Shortcuts Editable only when Extension Enabled...')
            );
        }
    }

    showShortcuts() {
        this.remoteActions.activate_action('showShortcutViewer', null);
    }
});

const aboutApp = class AboutDialog {
    constructor(params = {}) {
        this.version = params.version;
        this.appID = appID;
        const aboutDialog = Adw.AboutDialog.new();
        this.init(aboutDialog);
        return aboutDialog;
    }

    init(aboutDialog) {
        aboutDialog.modal = true;
        aboutDialog.set_application_icon(this.appID);
        aboutDialog.set_application_name('Adw. Desktop Icons');

        aboutDialog.set_comments(
            'An application to show Icons on the Gnome Desktop'
        );

        aboutDialog.set_copyright('© 2025 Sundeep Mediratta');
        aboutDialog.set_developer_name('Sundeep Mediratta');

        aboutDialog.set_comments(
            'Adw. Desktop Icons is an extension and a program together for ' +
            'the GNOME Shell that renders icons on the desktop. It is a fork ' +
            'from Desktop Icons NG (DING), by Sergio Costas, which itself ' +
            'is a fork/rewrite of the official "Desktop Icons" extension, ' +
            'originally by Carlos Soriano.' +
            '\n\n' +
            'All these came into existence when Nautilus and Gnome decided ' +
            'to drop showing a "Desktop" with Icons!' +
            '\n\n' +
            'Many thanks to the original developers of Desktop Icons NG, ' +
            'specially Sergio Costas for his work on ' +
            'Meta.WaylandClient that makes this privileged window possible in' +
            'the first place and to Florian Müllner for implementing ' +
            'Meta.Windotype.DESKTOP through Meta.WaylandClient, which makes ' +
            'this so much easier!'
        );

        aboutDialog.add_credit_section(
            'Originally developed by',
            [
                'Sergio Costas',
                'Carlos Soriano',
            ]
        );

        aboutDialog.add_acknowledgement_section(
            'For coding Meta.WaylandClient in mutter',
            ['Sergio Costas']
        );

        aboutDialog.add_acknowledgement_section(
            'Enabling Meta.Windowtype.DESKTOP\nthrough Meta.Waylandclient',
            ['Florian Müllner']
        );

        aboutDialog.add_acknowledgement_section(
            'Async code contribution',
            ['Marco Trevisan']
        );

        aboutDialog.add_acknowledgement_section(
            'Gnome Extensions Matrix Channel support',
            [
                'Andy Holmes',
                'Just Perfection',
                'And Others..',
            ]
        );

        aboutDialog.add_acknowledgement_section(
            'GJS Maintainers for GJS\n@ptomato for answering',
            ['@ptomato']
        );

        aboutDialog.set_license_type(Gtk.License.GPL_3_0);

        aboutDialog.set_issue_url(
            'https://gitlab.com/smedius/desktop-icons-ng/-/issues'
        );

        aboutDialog.set_support_url(
            'https://gitlab.com/smedius/desktop-icons-ng/-/blob/main/ISSUES.md?ref_type=heads'
        );

        aboutDialog.set_translator_credits(
            `Weblate Translators, See History.MD on website. Translated using machine translation with LibreTranslate. Unverified strings, translation may contain errors. Corrections, verification and additional translation can be done on Weblate.
            Translations available in- ar,az,be,bg,bn,ca,cs,da,de,el,eo,es,et,eu,fa,fi,fr,fur,ga,gl,he,hi,hr,hu,id,it,ja,ka,ko,ky,lv,lt,ms,nb,nb_NO,nl,oc,pl,pt_BR,pt,ro,ru,sk,sl,sq,sv,ta,tl,tr,th,uk,ur,zh-Hans,zh-Hant,zh_CN,zh_TW.`
        );

        aboutDialog.set_version(this.version);
        aboutDialog.set_website('https://gitlab.com/smedius/desktop-icons-ng');

        aboutDialog.add_link(
            _('Help translate in your web browser'),
            'https://hosted.weblate.org/engage/gtk4-desktop-icons-ng'
        );

        aboutDialog.set_release_notes(
            `<p>* Adw version 100.28 for Gnome 50</p>
<ul><li>Serialize desktop-state mutations through a FIFO async queue with independent file and geometry coalescing, preventing race conditions and stale object retention, and stabilize GTK startup layout and snapshot handling.</li></ul>
            <p>* Adw version 100.27 for Gnome 50</p>
<ul><li>Refine AppImage launch fallback and final AppImage launch failure handling, tighten URL validation with GLib.Uri, clean up desktopIconItem removing invalidate-size signals and dragIcon and lazy Gtk widget tree creation to conserve memory</li></ul>
            <p>* Adw version 100.26 for Gnome 50</p>
<ul><li>Fix memory leak with heavy disc activity and long Adw.Ding uptimes by serializing desktop refreshes so directory enumeration and the matching draw finish before the next refresh is queued.</li></ul>
            <p>* Adw version 100.25 for Gnome 50</p>
<ul><li>Refactor the metrics widgets to use libgtop for CPU, memory, and network sampling, and reuse shared decoders and samplers to reduce overhead.</li>
<li>Tighten widget backend lifecycle handling by improving systemd scope error reporting and explicitly tearing down the shared WebKit network session when widgets are turned off.</li>
<li>Weblate translation update for Thai, Chinese (Simplified Han script), Polish, German, Russian, Slovak, and Kyrgyz.</li></ul>
            <p>* Adw version 100.24 for Gnome 50</p>
<ul><li>Add support for downloading and installing the latest widgets from the Add Widget dialog, with a refreshed chooser and widget registry parsing.</li>
<li>Run the main app and HTML widget backend processes in dedicated systemd cgroup scopes under user.app, with app-prefixed subprocess names, independent of GNOME Shell.</li>
<li>Improve media player widgets by keeping the last active player selected when multiple players are playing.</li>
<li>Persist pinned widget global coordinates so pinned widgets keep their positions across grid and margin changes.</li>
<li>Add retry handling for temporary Open-Meteo 502/504 failures in weather widgets.</li>
<li>Weblate translation update.</li></ul>
            <p>* Adw version 100.23 for Gnome 50</p>
<ul><li>Refreshes GNOME 50 support with a Mutter cursor fix, Nautilus Previewer 2 / Sushi D-Bus support, improved Today widget dragging and timezone handling, plus packaging and translation updates.</li></ul>
            <p>* Adw version 100.22 for Gnome 49, 50</p>
<ul><li>Merge the latest window-tracking and draggable-widget fixes: clock, metrics, weather, media, and pinned windows now drag and track positions more reliably, while weather animations stay off by default and media widgets use a lighter DOM to reduce CPU use.</li></ul>
            <p>* Adw version 100.21 for Gnome 49, 50</p>
<ul><li>Short stability release: hardens the extension lifecycle, fixes window-type manager cleanup and stacking behavior, and adds GLibUnix signal compatibility for older systems.</li></ul>
            <p>* Adw version 100.20 for Gnome 49, 50</p>
<ul><li>Hardened widget host and WebKit integration, improved floating and pinned widget behavior, added media widget persistence and controls, refined geometry handling, and refreshed compatibility fixes.</li></ul>
            <p>* Adw version 100.19 for Gnome 49, 50</p>
<ul><li>Extends and hardens the widget API, adds floating and pinned HTML widgets, introduces Sticky Notes, makes Today and media widgets pinnable, updates world clocks, and polishes pinned widget interaction and UI behavior.</li></ul>
            <p>* Adw version 100.18 for Gnome 49, 50</p>
<ul><li>Wayland-only release: removes X11 support, improves dock/widget-layer focus and display behavior, and updates shell compatibility.</li></ul>
            <p>* Adw version 100.17 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Widget polish: grid controls, media widgets, webview reattach, redisplay, and animation.</li></ul>
            <p>* Adw version 100.16 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Fixes -Empty window not mapping, icon placement with monitor hotplug</li></ul>
            <p>* Adw version 100.15 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Uses GSK instead of Cairo to draw, optimizing GPU, minimizing CPU</li></ul>
<ul><li>Fixes widget positioning and loadstate race</li></ul>
<ul><li>Fixes widget visibility on mapping visibility changes</li></ul>
<ul><li>Fixes icon selection with shift/ctrl and keyboard arrow navigation</li></ul>
<ul><li>Fixes overrview animation</li></ul>
            <p>* Adw version 100.14 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Adds html widgets that can launch and communicate with a local backend</li></ul>
<ul><li>Reverts multiple selection with arrows as it breaks mouse drag and drop</li></ul>
<ul><li>Added a Today(Calendar) widget and system Metrics widget for desktop</li></ul>
<ul><li>Improves preferences for widgets</li></ul>
            <p>* Adw version 100.13 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Adds widgets that can be displayed on the desktop under the icon layer</li></ul>
<ul><li>Users can apply their own CSS</li></ul>
            <p>* Adw version 100.11 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Fix margins in RTL layout under dock</li></ul>
<ul><li>Adapt to X11 removal in mutter</li></ul>
            <p>* Adw version 100.9 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Machine Translation with LibreTranslate to all supported languages</li></ul>
            <p>* Adw version 100.8-2 for Gnome 45, 46, 47, 48 49</p>
<ul><li>Bug fix for older gnome versions with no GioUnix namespace</li></ul>
            <p>* Adw version 100.8 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Animate margin changes. Respects global Gtk4/Gnome animation settings</li></ul>
<ul><li>Right long-click brings up gnome shell background menu directly</li></ul>
<ul><li>Improve search UI, unselected items are now properly dimmed to highlight the selected</li></ul>
            <p>* Adw version 100.7 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Fix Gnome 49 compatibility issues</li></ul>
<ul><li>Fix xdg-terminal-exec directory detection in system data dirs</li></ul>
            <p>* Adw version 100.6 for Gnome 45, 46, 47, 48, 49</p>
<ul><li>Adapt to new Gnome 49 Meta.Window and Meta.WaylandClient API</li></ul>
<ul><li>Fix missing app icon if no parent icon folder</li></ul>
            <p>* Adw version 100.5 for Gnome 45, 46, 47, 48</p>
<ul><li>Set localized default desktop name</li></ul>
<ul><li>Resizable open with dialog</li></ul>
<ul><li>Fix custom icons size</li></ul>
<ul><li>Update to more direct error message</li></ul>
            <p>* Adw version 100.3 for Gnome 45, 46, 47, 48</p>
<ul><li>Draw proper selection rectangle at small sizes</li></ul>
            <p>* Adw version 100.2 for Gnome 45, 46, 47, 48</p>
<ul><li>Remove dependency on xdg-user-dirs</li></ul>
            <p>* Adw version 100.1 for Gnome 45, 46, 47, 48</p>
<p>Minor bug fixes to run on older Adw 1.5, errors on connecting second monitor, fix open terminal shortcut</p>
<p>Yay! Version 100!
Actually version 1.0, started with 0.01, but got tired of writing a 0 before every version.
I believe mostly feature complete, except DBus Activation and packaging as a GJS app.
Change Name to Adw. Desktop Icons :)
</p>
<ul><li> Add a complete shortcut manager with editable local and global shortcuts.</li>
<li> Add Adw.AboutDialog for the application with proper credits and acknowledgements.</li>
<li> Add more actions, to arrange icons directly, that can then have proper keybindings in the shortcut manager.</li>
<li> Redesign the preferences to open the shortcut manger. When opened through gnome extensions settings, shortcut manager is activated over DBus, and only works if the extension/app is enabled. </li>
<li> Add an easily editable boolean constant to gnome shell override so Users wanting to show icons on window picker overview or on thumbnails can choose to do so.</li>
<li> .desktop files on the desktop now show their actions in the right click context menus. All these actions are shown and can be activated if the file is trusted.</li></ul>`
        );
    }
};


const DingPreferencesWindow = class extends DesktopFolderUtils {
    constructor(params) {
        super(params);
        this.iconTheme =
            Gtk.IconTheme.get_for_display(Gdk.Display.get_default());

        this.iconTheme.add_resource_path(`${appPath}/icons`);
    }

    addActionRowSwitch(settings, key, labelText, bindFlags = null) {
        const actionRow = Adw.ActionRow.new();
        const switcher = new Gtk.Switch({active: settings.get_boolean(key)});

        switcher.set_halign(Gtk.Align.END);
        switcher.set_valign(Gtk.Align.CENTER);
        switcher.set_hexpand(false);
        switcher.set_vexpand(false);
        actionRow.set_title(labelText);
        actionRow.add_suffix(switcher);

        if (!bindFlags)
            bindFlags = Gio.SettingsBindFlags.DEFAULT;

        settings.bind(key, switcher, 'active', bindFlags);
        actionRow.set_activatable_widget(switcher);

        return actionRow;
    }

    addActionRowSelector(settings, key, labelText, elements) {
        const actionRow = new ComboRowWithKey();

        actionRow.set_title(labelText);
        actionRow.set_use_subtitle(false);
        actionRow.makeEnumn(elements);
        actionRow.set_selected(settings.get_enum(key));

        settings.bind(key, actionRow, 'indexkey',
            Gio.SettingsBindFlags.DEFAULT);

        return actionRow;
    }

    addActionRowButton(title, subtitle, buttonLabel, action, key = null) {
        const actionRow = Adw.ActionRow.new();

        actionRow.set_title(title);

        if (subtitle) {
            actionRow.use_markup = false;
            actionRow.set_subtitle(subtitle);
            if (Adw.get_minor_version() > 2)
                actionRow.set_subtitle_selectable(true);
        }

        if (buttonLabel && action) {
            const button = Gtk.Button.new_with_label(buttonLabel);

            button.set_size_request(120, -1);
            button.set_halign(Gtk.Align.END);
            button.set_valign(Gtk.Align.CENTER);
            button.set_hexpand(true);
            button.set_vexpand(false);
            button.connect('clicked', action.bind(this, actionRow, button, key));

            actionRow.add_suffix(button);
            actionRow.set_activatable_widget(button);
        }

        return actionRow;
    }

    launchUri(uri) {
        const context = Gdk.Display.get_default().get_app_launch_context();
        context.set_timestamp(Gdk.CURRENT_TIME);

        Gio.AppInfo.launch_default_for_uri(uri, context);
    }
};

const AdwPreferencesWindow = class extends DingPreferencesWindow {
    constructor(
        desktopSettings,
        nautilusSettings,
        gtkSettings,
        version,
        actiongroup = null
    ) {
        super();
        this.desktopSettings = desktopSettings;
        this.nautilusSettings = nautilusSettings;
        this.gtkSettings = gtkSettings;

        if (!actiongroup)
            this.getRemoteActions();
        else
            this.remoteActions = actiongroup;

        this.version = version;
    }

    destroy() {
        if (this.watchNameID)
            Gio.DBus.unwatch_name(this.watchNameID);

        this.watchNameID = 0;
    }

    getRemoteActions() {
        this.watchNameID = Gio.DBus.watch_name(
            Gio.BusType.SESSION,
            appID,
            Gio.BusNameWatcherFlags.NONE,
            (_conn, _name, _nameOwner) => {
                try {
                    this.remoteActions = Gio.DBusActionGroup.get(
                        Gio.DBus.session,
                        appID,
                        appPath
                    );
                    this.shortcutGroup?.update(this.remoteActions);
                    this.cssOverrideGroup?.update(this.remoteActions);
                } catch (e) {
                    logError(e, 'Error getting action group');
                }
            },
            (_conn, _name) => {
                this.remoteActions = null;
                this.shortcutGroup?.update(this.remoteActions);
                this.cssOverrideGroup?.update(this.remoteActions);
            }
        );
    }

    getAdwPreferencesWindow(window = null, parentWindow = null) {
        var prefsWindow;

        if (window) {
            prefsWindow = window;
        } else {
            prefsWindow = new Adw.PreferencesWindow();
            const app = Gtk.Application.get_default();

            if (app)
                prefsWindow.set_application(app);
        }
        if (parentWindow)
            prefsWindow.set_transient_for(parentWindow);
        prefsWindow.set_can_navigate_back(true);
        prefsWindow.set_search_enabled(true);

        this.prefsWindow = prefsWindow;
        this.activeWindow = prefsWindow;

        const prefsFrame = new Adw.PreferencesPage();

        prefsFrame.set_name(_('Desktop'));
        prefsFrame.set_title(_('Desktop'));
        prefsFrame.set_icon_name('ding-prefs-desktop-symbolic');

        const filesPrefsFrame = new Adw.PreferencesPage();

        filesPrefsFrame.set_name(_('Files'));
        filesPrefsFrame.set_title(_('Files'));
        filesPrefsFrame.set_icon_name('ding-prefs-files-symbolic');

        const tweaksFrame = new Adw.PreferencesPage();

        tweaksFrame.set_name(_('Tweaks'));
        tweaksFrame.set_title(_('Tweaks'));
        tweaksFrame.set_icon_name('ding-prefs-tweaks-symbolic');

        const aboutFrame = new Adw.PreferencesPage();

        aboutFrame.set_name(_('More'));
        aboutFrame.set_title(_('More'));
        aboutFrame.set_icon_name('ding-prefs-more-symbolic');

        prefsWindow.add(prefsFrame);
        prefsWindow.add(filesPrefsFrame);
        prefsWindow.add(tweaksFrame);
        prefsWindow.add(aboutFrame);
        prefsWindow.set_visible(prefsFrame);

        const desktopGroup = new Adw.PreferencesGroup();

        desktopGroup.set_title(_('Desktop Settings'));
        desktopGroup.set_description(_('Settings for the Desktop Program'));

        prefsFrame.add(desktopGroup);

        this.desktopFolderGroup = new Adw.PreferencesGroup();

        this.desktopFolderGroup.set_title(_('Desktop Folder'));
        this.FolderGroupDescription = _('Current Desktop: ');
        const desktoPath = this.getDesktopDir().get_path();
        this.desktopFolderGroup.set_description(
            `${this.FolderGroupDescription} ${desktoPath}`
        );

        prefsFrame.add(this.desktopFolderGroup);

        const filesGroup = new Adw.PreferencesGroup();

        filesGroup.set_title(_('Files Settings'));
        filesGroup.set_description(_('Settings shared with Gnome Files'));

        filesPrefsFrame.add(filesGroup);

        const tweaksGroup = new Adw.PreferencesGroup();

        tweaksGroup.set_title(_('Tweaks'));
        tweaksGroup.set_description(_('Miscellaneous Tweaks'));
        tweaksFrame.add(tweaksGroup);

        this.shortcutGroup =
            new ShortcutGroup({remoteActions: this.remoteActions});

        aboutFrame.add(this.shortcutGroup);

        this.cssOverrideGroup = new CssOverrideGroup({
            remoteActions: this.remoteActions,
        });
        this.cssOverrideGroup.set_visible(false); // Initially hidden
        aboutFrame.add(this.cssOverrideGroup);

        const aboutGroup = new Adw.PreferencesGroup();

        aboutGroup.set_title('About Adw. Desktop Icons');
        let versiontitle = _(`Version ${this.version}`);
        aboutGroup.set_description(versiontitle);

        aboutFrame.add(aboutGroup);

        desktopGroup.add(this.addActionRowSelector(this.desktopSettings,
            'icon-size',
            _('Size for the desktop icons'),
            {
                'tiny': _('Tiny'),
                'small': _('Small'),
                'standard': _('Standard'),
                'large': _('Large'),
            }
        ));

        desktopGroup.add(this.addActionRowSelector(this.desktopSettings,
            'start-corner',
            _('New icons alignment'),
            {
                'top-left': _('Top left corner'),
                'top-right': _('Top right corner'),
                'bottom-left': _('Bottom left corner'),
                'bottom-right': _('Bottom right corner'),
            }
        ));

        desktopGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-second-monitor',
            _('Add new icons to Secondary Monitors first, if available')));

        desktopGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'free-position-icons',
            _('Snap icons to grid'),
            Gio.SettingsBindFlags.INVERT_BOOLEAN
        ));

        const showWidgets = this.addActionRowSwitch(this.desktopSettings,
            'show-desktop-widgets',
            _('Show desktop widgets'));
        showWidgets.set_sensitive(DesktopWidgetCapability);
        desktopGroup.add(showWidgets);

        this.desktopFolderGroup
            .add(this.addActionRowButton(_('New Desktop Folder'),
                _('Set a new folder for the desktop'),
                _('Choose'),
                this.changeDesktop.bind(this)
            ));

        const defaultDesktopPath = this.getSystemLocalizedDesktopDir();
        const secondarytext = _('Set Desktop back to ~/');
        this.defaultDesktopRow =
            this.addActionRowButton(_('Restore Default Desktop Folder'),
                `${secondarytext}${defaultDesktopPath}`,
                _('Restore'),
                this.restoreDefaultDesktop.bind(this)
            );

        this.desktopFolderGroup.add(this.defaultDesktopRow);
        this.defaultDesktopRow.set_sensitive(!this.isDefaultDesktop);

        const dropPlaceRow = this.addActionRowSwitch(this.desktopSettings,
            'show-drop-place',
            _('Highlight the drop grid'));

        this.desktopSettings.bind('free-position-icons', dropPlaceRow,
            'sensitive',
            Gio.SettingsBindFlags.INVERT_BOOLEAN);

        tweaksGroup.add(dropPlaceRow);

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-link-emblem',
            _('Add information emblems for links, encryption')));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'dark-text-in-labels',
            _('Use dark text in icon labels')
        ));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-home',
            _('Show the personal folder on the desktop')
        ));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-trash',
            _('Show the trash icon on the desktop')
        ));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-volumes',
            _('Show external drives on the desktop')
        ));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'show-network-volumes',
            _('Show network drives on the desktop')
        ));

        tweaksGroup.add(this.addActionRowSwitch(this.desktopSettings,
            'add-volumes-opposite',
            _('Add new drives to the opposite side of the desktop')
        ));

        filesGroup.add(this.addActionRowSelector(this.nautilusSettings,
            'click-policy',
            _('Action to Open Items'),
            {
                'single': _('Single click'),
                'double': _('Double click'),
            }));

        filesGroup.add(this.addActionRowSelector(this.nautilusSettings,
            'show-image-thumbnails',
            _('Show image thumbnails'),
            {
                'always': _('Always'),
                'local-only': _('On this computer only'),
                'never': _('Never'),
            }));

        filesGroup.add(this.addActionRowSwitch(this.nautilusSettings,
            'show-delete-permanently',
            _('Show a context menu item to delete permanently')
        ));

        filesGroup.add(this.addActionRowSwitch(this.gtkSettings,
            'show-hidden',
            _('Show hidden files')
        ));

        filesGroup.add(this.addActionRowSwitch(this.nautilusSettings,
            'open-folder-on-dnd-hover',
            _('Open folders on drag hover')
        ));

        const aboutButton = new Adw.ActionRow();
        aboutButton.set_title(_('About...'));
        const icon = Gtk.Image.new_from_icon_name('ding-window-pop-out-symbolic');
        aboutButton.add_suffix(icon);
        aboutButton.set_activatable_widget(icon);

        aboutButton.connect('activated', () => {
            const aboutDialog = new aboutApp({version: this.version});
            aboutDialog.present(prefsWindow);
        });

        aboutGroup.add(aboutButton);

        const tranlationGroup = new Adw.PreferencesGroup({
            title: _('Translation'),
            description: _('Machine translated using LibreTranslate. User verified and edited on Weblate.'),
        });

        aboutFrame.add(tranlationGroup);

        tranlationGroup.add(this.addActionRowButton(_('Edit Translations'),
            _('Verify, add or correct translation in your web browser'),
            _('Translate'),
            this.launchWebTranslation.bind(this)
        ));

        // Track Alt key state using event controllers
        this._altKeyPressed = false;

        // Add key event controller to track Alt key
        const keyController = new Gtk.EventControllerKey();
        keyController.connect('key-pressed', (controller, keyval, _keycode, _state) => {
            if (keyval === Gdk.KEY_Alt_L || keyval === Gdk.KEY_Alt_R)
                this._altKeyPressed = true;

            return false;
        });

        keyController.connect('key-released', (controller, keyval, _keycode, _state) => {
            if (keyval === Gdk.KEY_Alt_L || keyval === Gdk.KEY_Alt_R)
                this._altKeyPressed = false;
        });

        prefsWindow.add_controller(keyController);

        // Add click gesture controller to detect Alt+Click on tab
        const clickController = new Gtk.GestureClick();
        clickController.connect('pressed', (gesture, _nPress, _x, _y) => {
            const state = gesture.get_current_event().get_modifier_state();
            this._altKeyPressed = (state & Gdk.ModifierType.ALT_MASK) !== 0;
        });
        prefsWindow.add_controller(clickController);

        // Show CSS Override group only when navigating to More tab with Alt held
        prefsWindow.connect('notify::visible-page', () => {
            if (prefsWindow.get_visible_page() === aboutFrame)
                this.cssOverrideGroup.set_visible(this._altKeyPressed);
        });

        prefsWindow.set_default_size(600, 650);

        this._monitorDesktopDirChanges();

        prefsWindow.connect(
            'close-request',
            () => {
                this._stopMonitoring();
                this.activeWindow = null;
            }
        );

        if (!window)
            return prefsWindow;
        else
            return true;
    }

    onDesktopFolderChanged(newDesktopDir) {
        super.onDesktopFolderChanged(newDesktopDir);
        const desktopPath = this._desktopDir.get_path();
        this.desktopFolderGroup.set_description(
            `${this.FolderGroupDescription} ${desktopPath}`
        );

        this.defaultDesktopRow.set_sensitive(!this.isDefaultDesktop);
    }

    launchWebTranslation() {
        const translationUri =
        'https://hosted.weblate.org/engage/gtk4-desktop-icons-ng';
        this.launchUri(translationUri);
    }
};
