/* DING: Desktop Icons New Generation for GNOME Shell
 *
 * Gtk4 Port Copyright (C) 2022 - 2025 Sundeep Mediratta (smedius@gmail.com)
 * Copyright (C) 2021 Sergio Costas (rastersoft@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, version 3 of the License.
 *
 * 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} from '../dependencies/gi.js';
import {_, Gettext} from '../dependencies/gettext.js';

export {FileItemMenu};
export {FileItemActions};

const FileItemMenu = class {
    constructor(desktopManager) {
        this._desktopManager = desktopManager;
        this._codePath = this._desktopManager.codePath;
        this._mainApp = this._desktopManager.mainApp;
        this._Prefs = this._desktopManager.Prefs;
        this._Enums = this._desktopManager.Enums;
        this._DesktopIconsUtil = this._desktopManager.DesktopIconsUtil;
        this._DBusUtils = desktopManager.DBusUtils;
        this._Enums = desktopManager.Enums;
        this._dragManager = desktopManager.dragManager;

        this._templatesScriptsManager =
            this._desktopManager.templatesScriptsManager;

        this._monitorScripts();
        this._activeFileItem = null;
    }

    _monitorScripts() {
        this.scriptsMonitor =
        new this._templatesScriptsManager.TemplatesScriptsManager(
            this._DesktopIconsUtil.getScriptsDir(),
            this._scriptsDirSelectionFilter.bind(this),
            {
                appName: 'app.onScriptClicked',
                FileUtils: this._desktopManager.FileUtils,
                Enums: this._Enums,
            }
        );
    }

    _scriptsDirSelectionFilter(fileinfo) {
        let name = fileinfo.get_name();
        let hidden = name.substring(0, 1) === '.';
        let executable = fileinfo.get_attribute_boolean('access::can-execute');
        if (!hidden && executable)
            return name;
        else
            return null;
    }

    /* Shows all possible values that can be assigned to this function */
    showMenu(
        fileItem,
        _button = null,
        X = null,
        _Y = null,
        x = null,
        y = null,
        _shiftSelected = false,
        _controlSelected = false,
        timestamp = Gdk.CURRENT_TIME
    ) {
        this.activeFileItem = fileItem;
        this._menuTimestamp = timestamp;
        const selectedItemsNum =
            this._desktopManager.getNumberOfSelectedItems();
        const scriptsSubmenu = this.scriptsMonitor.getGioMenu();
        const menulocation = X
            ? new Gdk.Rectangle({x, y, width: 1, height: 1})
            : fileItem._grid.getGlobaltoLocalRectangle(fileItem.iconRectangle);

        this._menu = Gio.Menu.new();
        const makeFolderMenu = Gio.Menu.new();
        const openMenu = Gio.Menu.new();
        const runAsProgram = Gio.Menu.new();
        const cutCopyPasteMenu = Gio.Menu.new();
        const trashMenu = Gio.Menu.new();
        const allowLaunchingMenu = Gio.Menu.new();
        const emptyTrashMenu = Gio.Menu.new();
        const driveMenu = Gio.Menu.new();
        const propertiesMenu = Gio.Menu.new();
        const showInFilesMenu = Gio.Menu.new();
        const openInTerminalMenu = Gio.Menu.new();

        if (fileItem.isAllSelectable &&
            !this._desktopManager.checkIfSpecialFilesAreSelected() &&
            (selectedItemsNum >= 2)) {
            makeFolderMenu.append(
                Gettext
                    .ngettext(
                        'New Folder with {0} item',
                        'New Folder with {0} items',
                        selectedItemsNum)
                    .replace('{0}', selectedItemsNum),
                'app.newfolderfromselection'
            );
        }

        if (!this.activeFileItem.isStackMarker) {
            if (selectedItemsNum > 1) {
                openMenu.append(_('Open All...'), 'app.openMultipleFileAction');
            } else {
                let app;
                let menuLabel;

                if (this.activeFileItem.executableContentType &&
                    this.activeFileItem.isExecutable &&
                    this.activeFileItem.trustedDesktopFile)
                    menuLabel = _('Launch');

                const canOpenUri = true;
                const type = this.activeFileItem.attributeContentType;
                const defaultApp = Gio.AppInfo.get_default_for_type;
                const recommendedApp = Gio.AppInfo.get_recommended_for_type;
                app = defaultApp(type, !canOpenUri)?.get_name();
                if (!app)
                    app = recommendedApp(type)[0]?.get_name();
                if (!app)
                    app = defaultApp(type, canOpenUri)?.get_name();

                if (!this.activeFileItem.isDesktopFile && app)
                    menuLabel = _('Open with {foo}');

                if (!menuLabel || this.activeFileItem.isAppImageFile)
                    menuLabel = _('Open');

                if (menuLabel) {
                    openMenu.append(
                        menuLabel.replace('{foo}', app),
                        'app.openOneFileAction'
                    );
                }
            }
        }

        if (fileItem.isAllSelectable &&
            !this._desktopManager.checkIfSpecialFilesAreSelected() &&
            (selectedItemsNum >= 1)
        ) {
            let addedExtractHere = false;
            if (this._getExtractableAutoAr()) {
                addedExtractHere = true;
                openMenu.append(_('Extract Here'), 'app.extractautoar');
            }
            if (selectedItemsNum === 1 && this._getExtractable()) {
                if (!addedExtractHere)
                    openMenu.append(_('Extract Here'), 'app.extracthere');

                openMenu.append(_('Extract To...'), 'app.extractto');
            }
        }

        if (fileItem.isDirectory &&
            selectedItemsNum === 1 &&
            !fileItem.isDrive &&
            !fileItem.isTrash)
            openMenu.append(_('Open With...'), 'app.doopenwith');

        if (!this.activeFileItem.isStackMarker &&
            !fileItem.isDirectory) {
            openMenu.append(
                selectedItemsNum > 1
                    ? _('Open All With Other Application...')
                    : _('Open With...'),
                'app.doopenwith'
            );

            if (this._DBusUtils.discreteGpuAvailable &&
                fileItem.trustedDesktopFile) {
                openMenu.append(
                    _('Launch using Integrated Graphics Card'),
                    'app.graphicslaunch'
                );
            }
        }

        const keepStacked =
            this._Prefs.desktopSettings.get_boolean('keep-stacked');

        if (keepStacked &&
            !fileItem.stackUnique) {
            if (!fileItem.isSpecial &&
                !fileItem.isDirectory &&
                !fileItem.isValidDesktopFile
            ) {
                const contentType = fileItem.attributeContentType;
                const typeInList =
                    this._Prefs.UnstackList.includes(contentType);

                const menuitem =
                    Gio.MenuItem.new(
                        typeInList
                            ? _('Stack This Type')
                            : _('Unstack This Type'),
                        null
                    );

                const variant = GLib.Variant.new('s', contentType);

                menuitem.set_action_and_target_value(
                    'app.stackunstack',
                    variant
                );
                openMenu.append_item(menuitem);
            }
        }

        // fileExtra == NONE

        if (fileItem.isAllSelectable &&  !fileItem.isStackMarker) {
            const contentType = fileItem.attributeContentType;
            if (fileItem.attributeCanExecute &&
                !fileItem.isDirectory &&
                !fileItem.isDesktopFile &&
                !fileItem.isAppImageFile &&
                fileItem.execLine &&
                Gio.content_type_can_be_executable(contentType))
                runAsProgram.append(_('Run as a Program'), 'app.runasaprogram');

            if (scriptsSubmenu !== null)
                openMenu.append_submenu(_('Scripts'), scriptsSubmenu);

            const allowCutCopyTrash =
                !this._desktopManager.checkIfSpecialFilesAreSelected();
            if (allowCutCopyTrash) {
                cutCopyPasteMenu.append(_('Cut'), 'app.docut');
                cutCopyPasteMenu.append(_('Copy'), 'app.docopy');
            }

            if (!this._desktopManager.checkIfSpecialFilesAreSelected()) {
                cutCopyPasteMenu.append(_('Move to...'), 'app.bulkMove');
                cutCopyPasteMenu.append(_('Copy to...'), 'app.bulkCopy');
            }

            if (fileItem.canRename && (selectedItemsNum === 1))
                trashMenu.append(_('Rename…'), 'app.dorename');

            if (fileItem.isAllSelectable &&
                !this._desktopManager.checkIfSpecialFilesAreSelected() &&
                (selectedItemsNum >= 1)) {
                trashMenu.append(_('Create Link...'), 'app.makeLinks');

                if (this._desktopManager
                    .getCurrentSelection()
                    ?.every(f => f.isDirectory)) {
                    trashMenu.append(
                        Gettext.ngettext(
                            'Compress {0} folder',
                            'Compress {0} folders', selectedItemsNum)
                            .replace('{0}', selectedItemsNum),
                        'app.compressfiles'
                    );
                } else {
                    trashMenu.append(
                        Gettext.ngettext(
                            'Compress {0} file',
                            'Compress {0} files', selectedItemsNum)
                            .replace('{0}', selectedItemsNum),
                        'app.compressfiles'
                    );
                }

                trashMenu.append(_('Email to...'), 'app.sendto');

                if (!this._desktopManager.checkIfDirectoryIsSelected()) {
                    const sel = this._desktopManager.getCurrentSelection();
                    const remoteoperation =
                        this._DBusUtils.RemoteSendFileOperations;
                    const gsconnectsubmenu =
                        remoteoperation.create_gsconnect_menu(sel);
                    if (gsconnectsubmenu) {
                        trashMenu.append_submenu(
                            _('Send to Mobile Device'),
                            gsconnectsubmenu
                        );
                    }
                }
            }

            if (allowCutCopyTrash) {
                trashMenu.append(_('Move to Trash'), 'app.movetotrash');

                const showDeletePermanently =
                    this._Prefs.nautilusSettings
                        .get_boolean('show-delete-permanently');

                if (showDeletePermanently) {
                    trashMenu.append(
                        _('Delete permanently'),
                        'app.deletepermanantly'
                    );
                }
            }

            if ((fileItem.isValidDesktopFile || fileItem.isAppImageFile) &&
                !this._desktopManager.writableByOthers &&
                !fileItem.writableByOthers &&
                (selectedItemsNum === 1)) {
                if (fileItem.isDesktopFile) {
                    allowLaunchingMenu.append(
                        fileItem.trustedDesktopFile
                            ? _("Don't Allow Launching")
                            : _('Allow Launching'),
                        'app.allowdisallowlaunching');
                    if (fileItem.hasActions) {
                        const actionItem =
                            Gio.MenuItem.new_section(
                                null,
                                fileItem.getMenu()
                            );
                        allowLaunchingMenu.append_item(actionItem);
                    }
                } else if (fileItem.isAppImageFile) {
                    allowLaunchingMenu.append(
                        fileItem.trustedAppImageFile
                            ? _("Don't Allow Launching")
                            : _('Allow Launching'),
                        'app.allowdisallowlaunching');
                }
            }
        }

        // fileExtra == TRASH

        if (fileItem.isTrash)
            emptyTrashMenu.append(_('Empty Trash'), 'app.emptytrash');

        // fileExtra == EXTERNAL_DRIVE

        if (fileItem.isDrive) {
            if (fileItem.canEject)
                driveMenu.append(_('Eject'), 'app.eject');

            if (fileItem.canUnmount)
                driveMenu.append(_('Unmount'), 'app.unmount');
        }

        if (!fileItem.isStackMarker) {
            propertiesMenu.append(
                selectedItemsNum > 1
                    ? _('Common Properties')
                    : _('Properties'),
                'app.properties'
            );

            const nautilusName = this._Prefs.NautilusName;
            showInFilesMenu.append(
                selectedItemsNum > 1
                    ? _('Show All in {0}').replace('{0}', nautilusName)
                    : _('Show in {0}').replace('{0}', nautilusName),
                'app.showinfiles'
            );
        }

        if (fileItem.isDirectory &&
            (fileItem.path !== null) &&
            (selectedItemsNum === 1)) {
            const terminalstring = this._Prefs.TerminalName;

            const menuitem = Gio.MenuItem.new(
                _('Open in {0}').replace('{0}', terminalstring),
                null
            );

            menuitem.set_action_and_target_value(
                'app.openinterminal',
                null
            );

            openInTerminalMenu.append_item(menuitem);
        }

        this._menu.append_section(null, makeFolderMenu);
        this._menu.append_section(null, openMenu);
        this._menu.append_section(null, runAsProgram);
        this._menu.append_section(null, cutCopyPasteMenu);
        this._menu.append_section(null, trashMenu);
        this._menu.append_section(null, allowLaunchingMenu);
        this._menu.append_section(null, emptyTrashMenu);
        if (fileItem.canEject || fileItem.canUnmount)
            this._menu.append_section(null, driveMenu);
        this._menu.append_section(null, showInFilesMenu);
        this._menu.append_section(null, openInTerminalMenu);
        this._menu.append_section(null, propertiesMenu);

        this.popupmenu = Gtk.PopoverMenu.new_from_model(this._menu);
        this.popupmenu.set_parent(fileItem._grid._container);
        this.popupmenu.set_pointing_to(menulocation);
        const menuGtkPosition =
            fileItem._grid.getIntelligentPosition(menulocation);
        if (menuGtkPosition !== null)
            this.popupmenu.set_position(menuGtkPosition);
        this.popupmenu.popup();
        this.popupmenu.connect('closed', () => {
            GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
                this.popupmenu.unparent();
                this.popupmenu = null;
                if (this.popupmenuclosed)
                    this.popupmenuclosed(true);
                this.popupmenuclosed = null;
                return GLib.SOURCE_REMOVE;
            });
        });
    }

    menuclosed = () => {
        return new Promise(resolve => {
            this.popupmenuclosed = resolve;
        });
    };

    showToolTip(fileItem) {
        if (this._toolTipPopup)
            return;
        if (this.popupmenu && (fileItem.uri === this.activeFileItem.uri))
            return;
        this._toolTipPopup = Gtk.Popover.new();
        this._toolTipPopup.set_pointing_to(fileItem.iconLocalWindowRectangle);
        this._toolTipPopup.set_autohide(false);
        this._toolTipLabel = Gtk.Label.new(fileItem._currentFileName);
        this._toolTipPopup.set_child(this._toolTipLabel);
        this._toolTipPopup.set_parent(fileItem._grid._window);
        const popupLocation =
            new Gdk.Rectangle({
                x: fileItem.iconLocalWindowRectangle.x,
                y: fileItem.iconLocalWindowRectangle.y,
                width: 1,
                height: 1,
            });
        const popupGtkPosition =
            fileItem._grid.getIntelligentPosition(popupLocation);
        if (popupGtkPosition !== null)
            this._toolTipPopup.set_position(popupGtkPosition);
        this._toolTipPopup.popup();
        this._toolTipPopup.connect(
            'closed',
            () => {
                this._toolTipPopup.unparent();
                this._toolTipPopup = null;
            }
        );
    }

    hideToolTip() {
        if (this._toolTipPopup)
            this._toolTipPopup.popdown();
    }

    _getExtractableAutoAr() {
        const fileList = this._desktopManager.getCurrentSelection();
        const remotecompress = this._DBusUtils.GnomeArchiveManager;
        if (remotecompress.isAvailable && (fileList.length === 1))
            return false;

        for (let item of fileList) {
            if (!this._desktopManager.autoAr.fileIsCompressed(item.fileName))
                return false;
        }
        return true;
    }

    _getExtractable() {
        const item = this._desktopManager.getCurrentSelection()[0];

        if (!item)
            return false;

        const contentType = item.attributeContentType;

        const decompressibleTypes =
            this._DBusUtils.RemoteFileOperations.decompressibleTypes;

        return decompressibleTypes.includes(contentType);
    }

    set activeFileItem(fileItem) {
        this._activeFileItem = fileItem;
    }

    get activeFileItem() {
        return this._activeFileItem;
    }
};


const FileItemActions = class {
    constructor(desktopManager) {
        this._desktopManager = desktopManager;
        this._mainApp = this._desktopManager.mainApp;
        this._codePath = this._desktopManager.codePath;
        this._Prefs = this._desktopManager.Prefs;
        this._DBusUtils = this._desktopManager.DBusUtils;
        this._DesktopIconsUtil = this._desktopManager.DesktopIconsUtil;
        this._Enums = this._desktopManager.Enums;
        this._appChooser = this._desktopManager.appChooser;
        this._dbusManager = this._desktopManager.dbusManager;
        this._dragManager = this._desktopManager.dragManager;
        this._createFileItemMenuActions();
    }

    _createFileItemMenuActions() {
        const openMultipleFileAction =
            Gio.SimpleAction.new('openMultipleFileAction', null);
        openMultipleFileAction.connect(
            'activate',
            this._doMultiOpen.bind(this)
        );
        this._mainApp.add_action(openMultipleFileAction);

        const openOneFileAction =
            Gio.SimpleAction.new('openOneFileAction', null);
        openOneFileAction.connect(
            'activate',
            () => {
                if (this.activeFileItem) {
                    if (this.activeFileItem.isStackMarker) {
                        this._onToggleStackUnstackThisTypeClicked(
                            this.activeFileItem.attributeContentType);
                    } else {
                        this.activeFileItem.doOpen();
                    }
                }
            }
        );
        openOneFileAction.set_state_hint(GLib.Variant.new('s', _('Open Item')));
        this._mainApp.add_action(openOneFileAction);

        const stackunstack =
            Gio.SimpleAction.new('stackunstack', GLib.VariantType.new('s'));
        stackunstack.connect(
            'activate',
            (_action, paramenter) => {
                this._onToggleStackUnstackThisTypeClicked(paramenter.unpack());
            });
        this._mainApp.add_action(stackunstack);

        const doopenwith = Gio.SimpleAction.new('doopenwith', null);
        doopenwith.connect('activate', () => {
            this._doOpenWith().catch(e => logError(e));
        });
        this._mainApp.add_action(doopenwith);

        const graphicslaunch = Gio.SimpleAction.new('graphicslaunch', null);
        graphicslaunch.connect('activate', () => {
            if (!this.activeFileItem)
                return;
            this.activeFileItem._doDiscreteGpu();
        });
        this._mainApp.add_action(graphicslaunch);

        const runasaprogram = Gio.SimpleAction.new('runasaprogram', null);
        runasaprogram.connect(
            'activate',
            this._runExecutableScript.bind(this)
        );
        this._mainApp.add_action(runasaprogram);

        this._docut = Gio.SimpleAction.new('docut', null);
        this._docut.connect(
            'activate',
            this._doCut.bind(this)
        );
        this._mainApp.add_action(this._docut);

        this._docopy = Gio.SimpleAction.new('docopy', null);
        this._docopy.connect(
            'activate',
            this._doCopy.bind(this)
        );
        this._mainApp.add_action(this._docopy);

        const dorename = Gio.SimpleAction.new('dorename', null);
        dorename.connect('activate', () => {
            this._desktopManager
                .doRename(this.activeFileItem, false)
                .catch(e => logError(e));
        });
        this._mainApp.add_action(dorename);

        this.moveToTrash = Gio.SimpleAction.new('movetotrash', null);
        this.moveToTrash.connect(
            'activate',
            () => this.doTrash()
        );

        this._mainApp.add_action(this.moveToTrash);

        this.deletePermanantly =
            Gio.SimpleAction.new('deletepermanantly', null);
        this.deletePermanantly.connect(
            'activate',
            () => this.doDeletePermanently()
        );

        this._mainApp.add_action(this.deletePermanantly);

        const emptytrash = Gio.SimpleAction.new('emptytrash', null);
        emptytrash.connect(
            'activate',
            () => this.doEmptyTrash()
        );
        this._mainApp.add_action(emptytrash);

        const allowdisallowlaunching = Gio.SimpleAction.new(
            'allowdisallowlaunching', null);
        allowdisallowlaunching.connect('activate', () => {
            if (!this.activeFileItem)
                return;

            this.activeFileItem
                .onAllowDisallowLaunchingClicked()
                .catch(e => console.error(e));
        });
        this._mainApp.add_action(allowdisallowlaunching);

        const eject = Gio.SimpleAction.new('eject', null);
        eject.connect('activate', () => {
            this.activeFileItem.eject().catch(e => console.error(e));
        });
        this._mainApp.add_action(eject);

        const unmount = Gio.SimpleAction.new('unmount', null);
        unmount.connect('activate', () => {
            this.activeFileItem.unmount().catch(e => console.error(e));
        });
        this._mainApp.add_action(unmount);

        const extractautoar = Gio.SimpleAction.new('extractautoar', null);
        extractautoar.connect(
            'activate',
            () => {
                this._desktopManager.getCurrentSelection()
                ?.forEach(
                    f => this._desktopManager.autoAr.extractFile(f.fileName));
            }
        );
        this._mainApp.add_action(extractautoar);

        const extracthere = Gio.SimpleAction.new('extracthere', null);
        extracthere.connect(
            'activate',
            this._extractFileFromSelection.bind(this, true)
        );
        this._mainApp.add_action(extracthere);

        const extractto = Gio.SimpleAction.new('extractto', null);
        extractto.connect(
            'activate',
            this._extractFileFromSelection.bind(this, false)
        );
        this._mainApp.add_action(extractto);

        const sendto = Gio.SimpleAction.new('sendto', null);
        sendto.connect(
            'activate',
            this._mailFilesFromSelection.bind(this, null)
        );
        this._mainApp.add_action(sendto);

        const compressfiles = Gio.SimpleAction.new('compressfiles', null);
        compressfiles.connect(
            'activate',
            this._doCompressFilesFromSelection.bind(this, null)
        );
        this._mainApp.add_action(compressfiles);

        const newfolderfromselection =
            Gio.SimpleAction.new('newfolderfromselection', null);
        newfolderfromselection.connect(
            'activate',
            this._newFolderFromSelection.bind(this)
        );
        this._mainApp.add_action(newfolderfromselection);

        const properties = Gio.SimpleAction.new('properties', null);
        properties.connect(
            'activate',
            this._onPropertiesClicked.bind(this)
        );
        this._mainApp.add_action(properties);

        const showinfiles = Gio.SimpleAction.new('showinfiles', null);
        showinfiles.connect(
            'activate',
            this._onShowInFilesClicked.bind(this)
        );
        this._mainApp.add_action(showinfiles);

        const openinterminal = Gio.SimpleAction.new('openinterminal', null);
        openinterminal.connect(
            'activate',
            this._openInTerminal.bind(this)
        );
        this._mainApp.add_action(openinterminal);

        const makeLinks = Gio.SimpleAction.new('makeLinks', null);
        makeLinks.connect(
            'activate',
            this._makeLinks.bind(this)
        );

        this._mainApp.add_action(makeLinks);

        const bulkCopy = Gio.SimpleAction.new('bulkCopy', null);
        bulkCopy.connect(
            'activate',
            this._bulkCopy.bind(this)
        );
        this._mainApp.add_action(bulkCopy);

        const bulkMove = Gio.SimpleAction.new('bulkMove', null);
        bulkMove.connect(
            'activate',
            this._bulkMove.bind(this)
        );
        this._mainApp.add_action(bulkMove);

        const onScriptClicked =
            Gio.SimpleAction.new('onScriptClicked', GLib.VariantType.new('s'));
        onScriptClicked.connect(
            'activate',
            (_action, parameter) => {
                this._onScriptClicked(parameter.unpack());
            }
        );
        this._mainApp.add_action(onScriptClicked);

        const desktopAction =
            Gio.SimpleAction.new('desktopAction', GLib.VariantType.new('as'));
        desktopAction.connect(
            'activate',
            (_action, parameter) => {
                const [path, actionName, action] = parameter.deepUnpack();
                this._desktopFileAction(path, actionName, action);
            }
        );
        this._mainApp.add_action(desktopAction);
    }

    _doMultiOpen() {
        for (let fileItem of this._desktopManager.getCurrentSelection()) {
            fileItem.unsetSelected();
            fileItem.doOpen();
        }
    }

    _onToggleStackUnstackThisTypeClicked(
        type, typeInList = null, unstackList = null) {
        if (!unstackList) {
            unstackList = this._Prefs.UnstackList;
            typeInList = unstackList.includes(type);
        }
        if (typeInList) {
            let index = unstackList.indexOf(type);
            unstackList.splice(index, 1);
        } else {
            unstackList.push(type);
        }
        this._Prefs.UnstackList = unstackList;
    }

    async _doOpenWith() {
        const fileItems = this._desktopManager.getCurrentSelection();
        if (!this.activeFileItem)
            this.activeFileItem = fileItems[0];
        if (fileItems) {
            const context = Gdk.Display.get_default().get_app_launch_context();
            context.set_timestamp(Gdk.CURRENT_TIME);
            let chooser =
                new this._appChooser.AppChooserDialog(
                    fileItems,
                    this.activeFileItem,
                    this._dbusManager,
                    this._DesktopIconsUtil,
                    this._desktopManager.getDialogParentWindow()
                );
            this._mainApp.activate_action('textEntryAccelsTurnOff', null);
            chooser.show();
            const appInfo =
                await chooser.getApplicationSelected()
                    .catch(e => console.error(e));
            if (appInfo) {
                const fileList = [];
                for (let item of fileItems)
                    fileList.push(item.file);

                appInfo.launch(fileList, context);
            }
            this._mainApp.activate_action('textEntryAccelsTurnOn', null);
            chooser.hide();
            chooser.finalize();
            chooser = null;
        }
    }

    _runExecutableScript() {
        if (!this.activeFileItem)
            return;

        this._DesktopIconsUtil.trySpawn(this._desktopDir.get_path(),
            [this.activeFileItem.path], null);
    }

    async _extractFileFromSelection(extractHere) {
        let extractFileItemURI;
        let extractFolderName;
        let position;
        const header = _('Extraction Cancelled');
        const text = _('Unable to extract File, no destination folder');
        const remOp = this._DBusUtils.RemoteFileOperations;

        for (let fileItem of this._desktopManager.getCurrentSelection()) {
            extractFileItemURI = fileItem.file.get_uri();
            extractFolderName = fileItem.fileName;
            position = fileItem.getCoordinates().slice(0, 2);
            fileItem.unsetSelected();
        }

        if (extractHere) {
            extractFolderName =
                this._DesktopIconsUtil
                    .getFileExtensionOffset(extractFolderName).basename;
            const targetURI =
                await this._desktopManager
                    .doNewFolder(position, extractFolderName, {rename: false});
            if (targetURI)
                remOp.ExtractRemote(extractFileItemURI, targetURI, true);
            else
                this._desktopManager.dbusManager.doNotify(header, text);

            return;
        }

        const folder = await this._getSelectedFolderGio()
            .catch(e => console.error(e));

        if (folder)
            remOp.ExtractRemote(extractFileItemURI, folder.get_uri(), true);
        else
            this._desktopManager.dbusManager.doNotify(header, text);
    }

    async _getSelectedFolderGio(dialogTitle = null, selectionText = null) {
        let result;
        try {
            result =
                await this._getSelectedFolderGioNewMethod(
                    dialogTitle,
                    selectionText
                );
        } catch (e) {
            console.log('Reverting to old method of selecting');
            result =
                await this._getSelectedFolderGioOldMethod(
                    dialogTitle,
                    selectionText
                );
        }
        return result;
    }

    _getSelectedFolderGioNewMethod(dialogTitle = null, selectionText = null) {
        return new Promise(resolve => {
            if (!dialogTitle)
                dialogTitle =  _('Select Destination');
            const window = this._desktopManager.getDialogParentWindow();
            if (!selectionText)
                selectionText = _('Select');
            const dialog = new Gtk.FileDialog({
                title: dialogTitle,
                accept_label: selectionText,
                modal: true,
                initial_folder: this._desktopDir,
            });
            dialog.select_folder(window, null, (actor, gioasyncresponse) => {
                let folder;
                try {
                    folder = actor.select_folder_finish(gioasyncresponse);
                } catch (e) {
                    resolve(false);
                }
                if (folder)
                    resolve(folder);
                else
                    resolve(false);
            });
        });
    }


    _getSelectedFolderGioOldMethod(dialogTitle = null, selectionText = null) {
        return new Promise(resolve => {
            if (!dialogTitle)
                dialogTitle =  _('Select Destination');
            if (!selectionText)
                selectionText = _('Select');
            let returnValue = null;
            const window = this._desktopManager.getDialogParentWindow();
            const dialog = new Gtk.FileChooserDialog({title: dialogTitle});
            dialog.set_action(Gtk.FileChooserAction.SELECT_FOLDER);
            dialog.set_create_folders(true);
            dialog.set_current_folder(this._desktopDir);
            dialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL);
            dialog.add_button(selectionText, Gtk.ResponseType.ACCEPT);
            dialog.set_transient_for(window);
            const modal = true;
            dialog.set_modal(modal);
            this._DesktopIconsUtil.windowHidePagerTaskbarModal(dialog, modal);
            this._mainApp.activate_action('textEntryAccelsTurnOff', null);
            dialog.show();
            dialog.present_with_time(Gdk.CURRENT_TIME);
            dialog.connect('close', () => {
                dialog.response(Gtk.ResponseType.CANCEL);
            });
            dialog.connect(
                'response',
                (_actor, response) => {
                    if (response === Gtk.ResponseType.ACCEPT) {
                        const folder = dialog.get_file();
                        if (folder)
                            returnValue = folder;
                        else
                            returnValue = false;
                    }
                    this._mainApp
                        .activate_action('textEntryAccelsTurnOn', null);
                    dialog.destroy();
                    resolve(returnValue);
                }
            );
        });
    }

    _mailFilesFromSelection() {
        if (this._desktopManager.checkIfSpecialFilesAreSelected())
            return;
        const pathnameArray = this._desktopManager.getCurrentSelection()
            .map(f => f.path);

        if (this._desktopManager.checkIfDirectoryIsSelected()) {
            this._mailzippedFilesFromSelection(pathnameArray)
                .catch(e => console.error(e));
            return;
        }
        this._xdgEmailFiles(pathnameArray);
        this._desktopManager.unselectAll();
    }

    async _mailzippedFilesFromSelection(pathnameArray) {
        this._mainApp.activate_action('textEntryAccelsTurnOff', null);
        const chooser = new Gtk.AlertDialog();
        chooser.set_message(_('Can not email a Directory'));
        chooser.set_detail(
            _('Selection includes a Directory, compress to a .zip file first?')
        );
        chooser.buttons = [_('Cancel'), _('OK')];
        chooser.set_modal(true);
        chooser.set_cancel_button(0);
        chooser.set_default_button(1);
        await chooser.choose(
            this.activeFileItem._grid._window,
            null,
            (actor, choice) => {
                const buttonpress = actor.choose_finish(choice);
                if (buttonpress === 1) {
                    const archive = this._makezippedArchive(pathnameArray);
                    if (archive)
                        this._xdgEmailFiles([archive]);
                }
                this._desktopManager.unselectAll();
            }
        );
        this._mainApp.activate_action('textEntryAccelsTurnOn', null);
    }

    _makezippedArchive(pathnameArray) {
        const zipCommand = GLib.find_program_in_path(this._Enums.ZIP_CMD);
        if (!zipCommand) {
            console.log('zip command not installed, cannot send email');
            const header = _('Mail Error');
            const text =
                _('Unable to find zip command, please install the program');
            this._dbusManager.doNotify(header, text);
            return null;
        }

        // Translators - basename for a zipped archive created for mailing
        const archiveName = _('Archive.zip');

        let archiveFile;
        let checkDir;
        // Create a random directory in /tmp
        do {
            const randomString = GLib.uuid_string_random().slice(0, 5);
            const dir = `/tmp/gtk4-ding-${randomString}`;
            archiveFile = `${dir}/${archiveName}`;
            checkDir = Gio.File.new_for_commandline_arg(dir);
        } while (!checkDir.make_directory(null));

        const args = [zipCommand, this._Enums.ZIP_CMD_OPTIONS, archiveFile];
        try {
            const async = false;
            const env = null;
            const workdir = this._desktopDir.get_path();
            const relativePathArray =
                pathnameArray.map(f => GLib.path_get_basename(f));
            const spawncommand = this._DesktopIconsUtil.trySpawn;
            spawncommand(workdir, args.concat(relativePathArray), env, async);
        } catch (e) {
            console.log(`Error Zipping Files, ${e}`);
            const header = _('Mail Error');
            const text = _('There was an error in creating a zip archive');
            this._dbusManager.doNotify(header, text);
        }

        if (Gio.File.new_for_commandline_arg(archiveFile).query_exists(null))
            return archiveFile;
        else
            return null;
    }

    _xdgEmailFiles(pathnameArray) {
        const xdgEmailCommand =
            GLib.find_program_in_path(this._Enums.XDG_EMAIL_CMD);

        if (!xdgEmailCommand) {
            console.log('xdg-email command not installed, cannot send email');
            const header = _('Mail Error');
            const text =
                _('Unable to find xdg-email, please install the program');
            this._dbusManager.doNotify(header, text);
            return;
        }

        const args = [xdgEmailCommand];

        try {
            const newPathNameArray = [];
            pathnameArray.forEach(f => {
                newPathNameArray.push(this._Enums.XDG_EMAIL_CMD_OPTIONS);
                newPathNameArray.push(f);
            });
            const spawncommand = this._DesktopIconsUtil.trySpawn;
            spawncommand(null, args.concat(newPathNameArray));
        } catch (e) {
            console.log(`Error emailing Files, ${e}`);
            const header = _('Mail Error');
            const text = _('There was an error in emailing Files');
            this._dbusManager.doNotify(header, text);
        }
    }

    _doCompressFilesFromSelection() {
        const desktopFolder = this._desktopDir;
        if (!desktopFolder)
            return;

        const toCompress = this._desktopManager.getCurrentSelection();
        if (!toCompress)
            return;

        const uriListtoCompress = toCompress?.map(f => f.uri);

        const remotecompressAvailable =
            this._DBusUtils.GnomeArchiveManager.isAvailable;

        if (remotecompressAvailable) {
            this._DBusUtils.RemoteFileOperations.CompressRemote(
                uriListtoCompress,
                desktopFolder.get_uri(),
                true
            );
        } else {
            this._desktopManager.autoAr.compressFileItems(
                toCompress,
                desktopFolder.get_path()
            );
        }

        this._desktopManager.unselectAll();
    }

    async _newFolderFromSelection() {
        const event = {
            'parentWindow': this.activeFileItem._grid._window,
            'timestamp': Gdk.CURRENT_TIME,
        };
        await this._doNewFolderFromSelection(
            this.activeFileItem.savedCoordinates,
            this.activeFileItem,
            event
        ).catch(e => console.error(e));
    }

    async _doNewFolderFromSelection(newposition = null, clickedItem, event) {
        if (!clickedItem)
            return;
        const newFolderFileItems =
            this._desktopManager.getCurrentSelectionAsUri();
        if (!newFolderFileItems)
            return;

        const position = newposition
            ? newposition
            : clickedItem.savedCoordinates;
        const newFolder = await this._desktopManager.doNewFolder(position);

        if (!newFolder)
            return;

        this._desktopManager.unselectAll();
        clickedItem.removeFromGrid({callOnDestroy: false});
        this._DBusUtils.RemoteFileOperations.pushEvent(event);
        const remoteOp = this._DBusUtils.RemoteFileOperations;
        remoteOp.MoveURIsRemote(newFolderFileItems, newFolder);
    }

    _onPropertiesClicked() {
        const propList = this._desktopManager.getCurrentSelectionAsUri();
        if (!propList)
            return;

        this._desktopManager
            .DBusUtils
            .RemoteFileOperations
            .ShowItemPropertiesRemote(
                propList,
                this._menuTimestamp ?? Gdk.CURRENT_TIME
            );
    }

    _onShowInFilesClicked() {
        const showInFilesList = this._desktopManager.getCurrentSelectionAsUri();
        if (!showInFilesList)
            return;

        this._desktopManager
            .DBusUtils
            .RemoteFileOperations
            .ShowItemsRemote(
                showInFilesList,
                this._menuTimestamp ?? Gdk.CURRENT_TIME
            );
    }

    _openInTerminal() {
        if (!this.activeFileItem || !this.activeFileItem.isDirectory)
            return;

        this.launchTerminal(this.activeFileItem.path, null);
    }


    launchTerminal(fileItemPath = null, commandLine = null) {
        let workingdir =
            fileItemPath ? fileItemPath : this._desktopDir.get_path();

        if (!GLib.file_test(workingdir, GLib.FileTest.EXISTS)) {
            const header = _('Can Not open the Working Directory');
            const text = _(`${workingdir} does not exist`);
            this._dbusManager.doNotify(header, text);
            return;
        }

        let success = false;
        const xdgTerminalExec =
            GLib.find_program_in_path(this._Enums.XDG_TERMINAL_EXEC);

        if (xdgTerminalExec) {
            try {
                commandLine = commandLine ? commandLine : '';
                const [args] =
                    GLib.shell_parse_argv(`${xdgTerminalExec} ${commandLine}`)
                        .slice(1);
                this._DesktopIconsUtil.trySpawn(workingdir, args, null);
                console.log('Executed xdg-terminal-exec');
                success = true;
            } catch (e) {
                console.log(`Error opening xdg-terminal-exec ${e}`);
                success = false;
            }
        }

        if (success)
            return;

        if (this._Prefs.Terminal) {
            this._Prefs.TerminalGioList.some(t => {
                let exec =
                    t.get_string(this._Enums.DESKTOPFILE_TERMINAL_EXEC_KEY);
                if (exec === 'ptyxis')
                    exec = `${exec} --new-window -d ${workingdir}`;
                let execswitch =
                    t.get_string(this._Enums.DESKTOPFILE_TERMINAL_EXEC_SWITCH);
                execswitch = execswitch ? execswitch : '-e';
                commandLine = commandLine ? `${execswitch} ${commandLine}` : '';
                const [args] =
                    GLib.shell_parse_argv(`${exec} ${commandLine}`)
                        .slice(1);
                try {
                    this._DesktopIconsUtil.trySpawn(workingdir, args, null);
                    success = true;
                } catch (e) {
                    console.log(`{Error opening ${t.get_string('Name')}, ${e}`);
                    success = false;
                }
                return success;
            });
        } else {
            const header = _('Unable to Open in Gnome Console');
            const text =
                _('Please Install Gnome Console or other Terminal Program');
            this._dbusManager.doNotify(header, text);
        }

        if (success)
            return;

        const header =
            _('Unable to Open {0}').replace('{0}', this._Prefs.TerminalName);
        const text =
            _('Please Install {0}').replace('{0}', this._Prefs.TerminalName);
        this._dbusManager.doNotify(header, text);
    }

    _makeLinks() {
        const toLink = this._desktopManager.getCurrentSelectionAsUri();
        if (!toLink || this._desktopManager.checkIfSpecialFilesAreSelected())
            return;

        const desktopFolderUri = this._desktopDir.get_uri();
        const [X, Y] = this.activeFileItem.getCoordinates().slice(0, 2);
        this._dragManager.makeLinks(toLink, desktopFolderUri, X, Y)
        .catch(e => logError(e));
    }

    _doCopy() {
        const copy = true;
        this._manageCutCopy(copy);
    }

    _doCut() {
        const cut = false;
        this._manageCutCopy(cut);
    }

    async _bulkCopy() {
        if (this._desktopManager.checkIfSpecialFilesAreSelected())
            return;

        const copyList = this._desktopManager.getCurrentSelectionAsUri();
        if (!copyList)
            return;

        const folder = await this._getSelectedFolderGio()
            .catch(e => console.error(e));

        if (folder) {
            this._DBusUtils
                .RemoteFileOperations
                .CopyURIsRemote(copyList, folder.get_uri());

            return;
        }

        const header = _('Copy Cancelled');
        const text = _('Unable to copy Files, no destination folder');
        this._dbusManager.doNotify(header, text);
    }

    async _bulkMove() {
        if (this._desktopManager.checkIfSpecialFilesAreSelected())
            return;

        const moveList = this._desktopManager.getCurrentSelectionAsUri();
        if (!moveList)
            return;

        const folder = await this._getSelectedFolderGio()
            .catch(e => console.error(e));

        if (folder) {
            this._dragManager.saveCurrentFileCoordinatesForUndo();
            this._DBusUtils
                .RemoteFileOperations
                .MoveURIsRemote(moveList, folder.get_uri());
            return;
        }

        const header = _('Move Cancelled');
        const text = _('Unable to move Files, no destination folder');
        this._dbusManager.doNotify(header, text);
    }

    _desktopFileAction(path, actionName, action) {
        if (!this.activeFileItem)
            return;

        if (path === this.activeFileItem.path &&
            this.activeFileItem.actionMap.has(actionName)
        ) {
            const context = Gdk.Display.get_default().get_app_launch_context();
            context.set_timestamp(Gdk.CURRENT_TIME);
            this.activeFileItem.desktopAppInfo.launch_action(action, context);
        }
    }

    doTrash(localDrag = false, event = null) {
        const currentSelection = this._desktopManager.getCurrentSelection();

        if (!currentSelection)
            return;

        const selectionItems = currentSelection.filter(i => !i.isSpecial);

        if (!selectionItems || !selectionItems.length)
            return;

        if (!localDrag)
            this._dragManager.saveCurrentFileCoordinatesForUndo(selectionItems);

        const selectionURIs = [];

        selectionItems.forEach(f => {
            selectionURIs.push(f.file.get_uri());
        });

        if (event)
            this._DBusUtils.RemoteFileOperations.pushEvent(event);

        this._DBusUtils.RemoteFileOperations.TrashURIsRemote(selectionURIs);
    }

    doDeletePermanently() {
        const currentSelection = this._desktopManager.getCurrentSelection();

        if (!currentSelection)
            return;

        const toDelete =
            currentSelection
            .filter(i => !i.isSpecial)
            .map(i => i.file.get_uri());

        if (!toDelete || !toDelete.length) {
            if (this._desktopManager.getCurrentSelection()
                .some(i => i.isTrash)
            )
                this.doEmptyTrash();

            return;
        }

        this._DBusUtils.RemoteFileOperations.DeleteURIsRemote(toDelete);
    }

    doEmptyTrash(askConfirmation = true) {
        this._DBusUtils.RemoteFileOperations.EmptyTrashRemote(askConfirmation);
    }

    _onScriptClicked(menuItemPath) {
        let pathList = 'NAUTILUS_SCRIPT_SELECTED_FILE_PATHS=';
        let uriList = 'NAUTILUS_SCRIPT_SELECTED_URIS=';
        let currentUri =
            `NAUTILUS_SCRIPT_CURRENT_URI=${this._desktopDir.get_uri()}`;
        let params = [menuItemPath];
        for (let item of this._desktopManager.getCurrentSelection()) {
            if (!item.isSpecial) {
                pathList += `${item.file.get_path()}\n`;
                uriList += `${item.file.get_uri()}\n`;
                params.push(item.file.get_path());
            }
        }

        let environ = this._DesktopIconsUtil.getFilteredEnviron();
        environ.push(pathList);
        environ.push(uriList);
        environ.push(currentUri);
        this._DesktopIconsUtil.trySpawn(null, params, environ);
    }

    // Clipboard management
    // ************************************************************************ */
    /*
     * Before Gnome Shell 40, St API couldn't access binary data in the clipboard,
     * only text data. Also, the original Desktop Icons was a pure extension, so it
     * was limited to what Clutter and St offered. That was the reason why Nautilus
     * accepted a text format for CUT and COPY operations in the form:
     *
     *     x-special/nautilus-clipboard
     *     OPERATION
     *     FILE_URI
     *     [FILE_URI]
     *     [...]
     *
     * In Gnome Shell 40, St was enhanced and now it supports binary data; that's
     * why Nautilus migrated to a binary format identified by the atom
     * 'x-special/gnome-copied-files', where the CUT or COPY operation is shared.
     *
     * To maintain compatibility, in the past, we checked the current Gnome Shell
     * version and, based on that, set the binary or the text clipboards.
     *
     * With the newer versions of gtk4-ding, we only set the binary version and add
     * other composite providers for the plain text versions like the newer
     * Nautilus/Files.
     */

    _manageCutCopy(action) {
        const uriList =
            this._dragManager
            .fillDragDataGet(this._Enums.DndTargetInfo.TEXT_URI_LIST);

        if (!uriList?.length)
            return;

        const pathList =
            this._dragManager
            .fillDragDataGet(this._Enums.DndTargetInfo.TEXT_PLAIN);

        const clipboard = Gdk.Display.get_default().get_clipboard();
        const textCoder = new TextEncoder();

        let content = action ? 'copy\n' : 'cut\n';
        content += uriList?.replaceAll('\r', '').trim();

        const encodedUriList = textCoder.encode(uriList);
        const encodedPathList = textCoder.encode(pathList);

        const gnomeContentProvider =
            Gdk.ContentProvider.new_for_bytes(
                'x-special/gnome-copied-files',
                textCoder.encode(content)
            );

        const textUriListContentProvider =
            Gdk.ContentProvider
            .new_for_bytes(
                this._Enums.DndTargetInfo.URI_LIST,
                encodedUriList
            );

        const textListContentProvider =
            Gdk.ContentProvider
                .new_for_bytes(
                    this._Enums.DndTargetInfo.TEXT_PLAIN,
                    encodedPathList
                );

        const textUtf8ListContentProvider =
            Gdk.ContentProvider
            .new_for_bytes(
                this._Enums.DndTargetInfo.TEXT_PLAIN_UTF8,
                encodedPathList
            );

        const clipboardContentProvider =
            Gdk.ContentProvider.new_union([
                gnomeContentProvider,
                textUriListContentProvider,
                textListContentProvider,
                textUtf8ListContentProvider,
            ]);

        clipboard.set_content(clipboardContentProvider);
    }


    get _desktopDir() {
        return this._desktopManager._desktopDir;
    }

    get activeFileItem() {
        return this._desktopManager.activeFileItem;
    }
};
