const Me = imports.misc.extensionUtils.getCurrentExtension();

const {Clutter, GLib, Gio, GMenu, Gtk, Shell, St} = imports.gi;
const AppFavorites = imports.ui.appFavorites;
const appSys = Shell.AppSystem.get_default();
const ArcSearch = Me.imports.search;
const Constants = Me.imports.constants;
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
const Main = imports.ui.main;
const MenuLayouts = Me.imports.menulayouts;
const MW = Me.imports.menuWidgets;
const PlaceDisplay = Me.imports.placeDisplay;
const PopupMenu = imports.ui.popupMenu;
const { RecentFilesManager } = Me.imports.recentFilesManager;
const Utils =  Me.imports.utils;

//This class handles the core functionality of all the menu layouts.
//Each menu layout extends this class.
var BaseLayout = class {
    constructor(menuButton, layoutProperties){
        this.menuButton = menuButton;
        this._settings = menuButton._settings;
        this.mainBox = menuButton.mainBox; 
        this.contextMenuManager = menuButton.contextMenuManager;
        this.subMenuManager = menuButton.subMenuManager;
        this.arcMenu = menuButton.arcMenu;
        this.section = menuButton.section;
        this.layout = this._settings.get_enum('menu-layout');
        this.layoutProperties = layoutProperties;
        this._focusChild = null;
        this.shouldLoadPinnedApps = true;
        this.hasPinnedApps = false;

        if(this.layoutProperties.Search){
            this.searchResults = new ArcSearch.SearchResults(this);
            this.searchBox = new MW.SearchBox(this);
            this._searchBoxChangedId = this.searchBox.connect('search-changed', this._onSearchBoxChanged.bind(this));
            this._searchBoxKeyPressId = this.searchBox.connect('entry-key-press', this._onSearchBoxKeyPress.bind(this));
        }

        this._mainBoxKeyPressId = this.mainBox.connect('key-press-event', this._onMainBoxKeyPress.bind(this));
        
        this._tree = new GMenu.Tree({ menu_basename: 'applications.menu' });
        this._treeChangedId = this._tree.connect('changed', () => this.reloadApplications());

        this._gnomeFavoritesReloadID = AppFavorites.getAppFavorites().connect('changed', () => {
            if(this.categoryDirectories){
                let categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.FAVORITES);
                if(categoryMenuItem)
                    this._loadGnomeFavorites(categoryMenuItem);
            }
        });

        this.mainBox.vertical = this.layoutProperties.VerticalMainBox;

        this.createLayout();
    }

    createLayout(){
        this.disableFadeEffect = this._settings.get_boolean('disable-scrollview-fade-effect');
        this.activeCategoryType = -1;
        let layout = new Clutter.GridLayout({ 
            orientation: Clutter.Orientation.VERTICAL,
            column_spacing: this.layoutProperties.ColumnSpacing,
            row_spacing: this.layoutProperties.RowSpacing 
        });
        this.applicationsGrid = new St.Widget({ 
            x_expand: true,
            x_align: this.layoutProperties.DisplayType === Constants.DisplayType.LIST ? Clutter.ActorAlign.FILL : Clutter.ActorAlign.CENTER,
            layout_manager: layout 
        });
        layout.hookup_style(this.applicationsGrid);
    }

    setDefaultMenuView(){
        if(this.layoutProperties.Search){
            this.searchBox.clearWithoutSearchChangeEvent();
            this.searchResults.setTerms([]);
        }

        this._clearActorsFromBox();
        this.resetScrollBarPosition();
    }

    updateWidth(setDefaultMenuView, leftPanelWidthOffset = 0, rightPanelWidthOffset = 0){
        if(this.layoutProperties.DualPanelMenu){
            const leftPanelWidth = this._settings.get_int("left-panel-width") + leftPanelWidthOffset;
            const rightPanelWidth = this._settings.get_int("right-panel-width") + rightPanelWidthOffset;
            this.leftBox.style = `width: ${leftPanelWidth}px;`;
            this.rightBox.style = `width: ${rightPanelWidth}px;`;
        }
        else{
            const widthAdjustment = this._settings.get_int("menu-width-adjustment");
            let menuWidth = this.layoutProperties.DefaultMenuWidth + widthAdjustment;
            //Set a 300px minimum limit for the menu width
            menuWidth = Math.max(300, menuWidth);
            this.applicationsScrollBox.style = `width: ${menuWidth}px;`;
            this.layoutProperties.MenuWidth = menuWidth;
        }

        if(setDefaultMenuView)
            this.setDefaultMenuView();
    }

    getColumnsFromActor(actor){
        let gridIconWidth = this.getActorWidthFromStyleClass(actor.name);
        return this.getBestFitColumns(gridIconWidth);
    }

    getColumnsFromGridIconSizeSetting(){
        let gridIconWidth;
        let iconSizeEnum = this._settings.get_enum("menu-item-grid-icon-size");

        if(iconSizeEnum === Constants.GridIconSize.DEFAULT)
            gridIconWidth = this.getActorWidthFromStyleClass(this.layoutProperties.DefaultIconGridStyle);
        else{
            Constants.GridIconInfo.forEach((info) => {
                if(iconSizeEnum === info.ENUM){
                    gridIconWidth = info.SIZE;
                    return;
                }
            });
        }
        return this.getBestFitColumns(gridIconWidth);
    }

    getBestFitColumns(gridIconWidth){
        let width = this.layoutProperties.MenuWidth;      
        let spacing = this.layoutProperties.ColumnSpacing;
        let columns = Math.floor(width / (gridIconWidth + spacing));
        return columns;
    }

    getActorWidthFromStyleClass(name){
        let size;
        
        Constants.GridIconInfo.forEach((info) => {
            if(name === info.NAME){
                size = info.SIZE;
                return;
            }
        });
        return size;
    }

    resetScrollBarPosition(){
        let appsScrollBoxAdj;

        if(this.applicationsScrollBox){
            appsScrollBoxAdj = this.applicationsScrollBox.get_vscroll_bar().get_adjustment();
            appsScrollBoxAdj.set_value(0);
        }
        if(this.categoriesScrollBox){
            appsScrollBoxAdj = this.categoriesScrollBox.get_vscroll_bar().get_adjustment();
            appsScrollBoxAdj.set_value(0);
        }
        if(this.shortcutsScrollBox){
            appsScrollBoxAdj = this.shortcutsScrollBox.get_vscroll_bar().get_adjustment();
            appsScrollBoxAdj.set_value(0);
        }
        if(this.actionsScrollBox){
            appsScrollBoxAdj = this.actionsScrollBox.get_vscroll_bar().get_adjustment();
            appsScrollBoxAdj.set_value(0);
        }
    }

    reloadApplications(){
        //Don't reload applications if the menu is open.
        //Instead, reload on menu-closed event.
        //Prevents the menu from jumping to its default view
        //when reloadApplications() is called.
        if(this.arcMenu.isOpen){
            if(!this._menuClosedID){
                this._menuClosedID = this.arcMenu.connect('menu-closed', () => {
                    this.reloadApplications();
                    if(this._menuClosedID){
                        this.arcMenu.disconnect(this._menuClosedID);
                        this._menuClosedID = null;
                    }
                });
            }
            return;
        }

        if(this.applicationsMap){
            this.applicationsMap.forEach((value,key,map)=>{
                value.destroy();
            });
            this.applicationsMap = null;
        }

        if(this.categoryDirectories){
            this.categoryDirectories.forEach((value,key,map)=>{
                value.destroy();
            });
            this.categoryDirectories = null;    
        }

        this.loadCategories();
        this.setDefaultMenuView();
    }

    loadCategories(displayType = Constants.DisplayType.LIST){  
        this.applicationsMap = new Map();    
        this._tree.load_sync();
        let root = this._tree.get_root_directory();
        let iter = root.iter();
        let nextType;
        while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
            if (nextType == GMenu.TreeItemType.DIRECTORY) {
                let dir = iter.get_directory();                  
                if (!dir.get_is_nodisplay()) {
                    let categoryId = dir.get_menu_id();
                    let categoryMenuItem = new MW.CategoryMenuItem(this, dir, displayType);
                    this.categoryDirectories.set(categoryId, categoryMenuItem);
                    let foundRecentlyInstallApp = this._loadCategory(categoryId, dir);
                    categoryMenuItem.setRecentlyInstalledIndicator(foundRecentlyInstallApp);
                    //Sort the App List Alphabetically
                    categoryMenuItem.appList.sort((a, b) => {
                        return a.get_name().toLowerCase() > b.get_name().toLowerCase();
                    });
                }
            }
        }
        let categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.ALL_PROGRAMS);
        if(categoryMenuItem){
            let appList = [];
            this.applicationsMap.forEach((value,key,map) => {
                appList.push(key);
                //Show Recently Installed Indicator on All Programs category
                if(value.isRecentlyInstalled && !categoryMenuItem.isRecentlyInstalled)
                    categoryMenuItem.setRecentlyInstalledIndicator(true);
            });
            appList.sort((a, b) => {
                return a.get_name().toLowerCase() > b.get_name().toLowerCase();
            });
            categoryMenuItem.appList = appList;
        }
        categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.FAVORITES);
        if(categoryMenuItem){
            this._loadGnomeFavorites(categoryMenuItem);
        }
        categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.FREQUENT_APPS);
        if(categoryMenuItem){
            let mostUsed = Shell.AppUsage.get_default().get_most_used();
            for (let i = 0; i < mostUsed.length; i++) {
                if (mostUsed[i] && mostUsed[i].get_app_info().should_show())
                    categoryMenuItem.appList.push(mostUsed[i]);
            }
        }
        categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.PINNED_APPS);
        if(categoryMenuItem){
            this.hasPinnedApps = true;
            categoryMenuItem.appList = categoryMenuItem.appList.concat(this.pinnedAppsArray);
        }
        categoryMenuItem = this.categoryDirectories.get(Constants.CategoryType.RECENT_FILES);
        if(categoryMenuItem){
            this._loadRecentFiles(categoryMenuItem);
        }
            
    }

    _loadCategory(categoryId, dir) {
        let iter = dir.iter();
        let nextType;
        let foundRecentlyInstallApp = false;
        while ((nextType = iter.next()) != GMenu.TreeItemType.INVALID) {
            if (nextType == GMenu.TreeItemType.ENTRY) {
                let entry = iter.get_entry();
                let id;
                try {
                    id = entry.get_desktop_file_id();
                } catch (e) {
                    continue;
                }
                let app = appSys.lookup_app(id);
                if (!app)
                    app = new Shell.App({ app_info: entry.get_app_info() });
                if (app.get_app_info().should_show()){
                    let item = this.applicationsMap.get(app);
                    if (!item) {
                        let isContainedInCategory = true;
                        item = new MW.ApplicationMenuItem(this, app, this.layoutProperties.DisplayType, null, isContainedInCategory);
                    }

                    let disabled = this._settings.get_boolean("disable-recently-installed-apps")
                    if(!disabled && item.isRecentlyInstalled)
                        foundRecentlyInstallApp = true;

                    let categoryMenuItem = this.categoryDirectories.get(categoryId);
                    categoryMenuItem.appList.push(app);
                    this.applicationsMap.set(app, item);
                } 
            } 
            else if (nextType == GMenu.TreeItemType.DIRECTORY) {
                let subdir = iter.get_directory();
                if (!subdir.get_is_nodisplay()){
                    let recentlyInstallApp = this._loadCategory(categoryId, subdir);
                    if(recentlyInstallApp)
                        foundRecentlyInstallApp = true;
                }    
            }
        }
        return foundRecentlyInstallApp;
    }

    setRecentlyInstalledIndicator(){
        let disabled = this._settings.get_boolean("disable-recently-installed-apps")
        if(!disabled){
            for(let categoryMenuItem of this.categoryDirectories.values()){
                categoryMenuItem.setRecentlyInstalledIndicator(false);
                for(let i = 0; i < categoryMenuItem.appList.length; i++){
                    let item = this.applicationsMap.get(categoryMenuItem.appList[i]);
                    if(!item)
                        continue;
                    if(item.isRecentlyInstalled){
                        categoryMenuItem.setRecentlyInstalledIndicator(true);
                        break;
                    }   
                }
            }
        }
    }   

    displayCategories(categoriesBox){
        if(!categoriesBox){
            categoriesBox = this.applicationsBox;
        }
        this._clearActorsFromBox(categoriesBox);
        
        this._futureActiveItem = false;

        for(let categoryMenuItem of this.categoryDirectories.values()){
            if(categoryMenuItem.get_parent())
                continue;
            categoriesBox.add_child(categoryMenuItem.actor);	
            if(!this._futureActiveItem){
                this._futureActiveItem = categoryMenuItem;
            }	 
        }

        this.activeMenuItem = this._futureActiveItem;
    }

    _loadGnomeFavorites(categoryMenuItem){
        let appList = AppFavorites.getAppFavorites().getFavorites();

        //Show Recently Installed Indicator on GNOME favorites category
        for(let i = 0; i < appList.length; i++){
            let item = this.applicationsMap.get(appList[i]);
            if(item && item.isRecentlyInstalled && !categoryMenuItem.isRecentlyInstalled)
                categoryMenuItem.setRecentlyInstalledIndicator(true);
        }

        categoryMenuItem.appList = appList;
        if(this.activeCategoryType === Constants.CategoryType.FAVORITES)
            categoryMenuItem.displayAppList();
    }

    _loadRecentFiles(){
        if(!this.recentFilesManager)
            this.recentFilesManager = new RecentFilesManager();
    }

    displayRecentFiles(box = this.applicationsBox, callback){
        const homeRegExp = new RegExp('^(' + GLib.get_home_dir() + ')');
        this._clearActorsFromBox(box);
        this._futureActiveItem = false;

        this.recentFilesManager.filterRecentFiles(recentFile => {
            let file = Gio.File.new_for_uri(recentFile.get_uri());
            let filePath = file.get_path();
            let name = recentFile.get_display_name();
            let icon = Gio.content_type_get_symbolic_icon(recentFile.get_mime_type()).to_string();
            let isContainedInCategory = true;

            let placeMenuItem = this.createMenuItem([name, icon, filePath], Constants.DisplayType.LIST, isContainedInCategory);
            placeMenuItem.parentFolderPath = file.get_parent()?.get_path() // can be null
            placeMenuItem.style = "padding-right: 15px;";
            placeMenuItem.description = recentFile.get_uri_display().replace(homeRegExp, '~');
            placeMenuItem.fileUri = recentFile.get_uri();

            placeMenuItem._removeBtn = new MW.ArcMenuButtonItem(this, null, 'edit-delete-symbolic');
            placeMenuItem._removeBtn.toggleMenuOnClick = false;
            placeMenuItem._removeBtn.x_align = Clutter.ActorAlign.END;
            placeMenuItem._removeBtn.x_expand = true;
            placeMenuItem._removeBtn.add_style_class_name("arcmenu-small-button");
            placeMenuItem._removeBtn.setIconSize(14);
            placeMenuItem._removeBtn.connect('activate', () =>  {
                try {
                    let recentManager = this.recentFilesManager.getRecentManager();
                    recentManager.remove_item(placeMenuItem.fileUri);
                } catch(err) {
                    log(err);
                }
                
                placeMenuItem.cancelPopupTimeout();
                placeMenuItem.contextMenu?.close();
                box.remove_child(placeMenuItem);
                placeMenuItem.destroy();
            });

            placeMenuItem.add_child(placeMenuItem._removeBtn);
            box.add_child(placeMenuItem);

            if(!this._futureActiveItem){
                this._futureActiveItem = placeMenuItem;
                this.activeMenuItem = this._futureActiveItem;
            }
            
            if(callback)
                callback();
        });
    }

    _displayPlaces() {
        var SHORTCUT_TRANSLATIONS = [_("Home"), _("Documents"), _("Downloads"), _("Music"), _("Pictures"), _("Videos"), _("Computer"), _("Network")];
        let directoryShortcuts = this._settings.get_value('directory-shortcuts-list').deep_unpack();
        for (let i = 0; i < directoryShortcuts.length; i++) {
            let directory = directoryShortcuts[i];
            let isContainedInCategory = false;
            let placeMenuItem = this.createMenuItem(directory, Constants.DisplayType.LIST, isContainedInCategory);
            if(placeMenuItem)
                this.shortcutsBox.add_child(placeMenuItem.actor);
        }
    }

    loadExtraPinnedApps(pinnedAppsArray, separatorIndex){
        let pinnedApps = pinnedAppsArray;
        //if the extraPinnedApps array is empty, create a default list of apps. 
        if(!pinnedApps.length || !Array.isArray(pinnedApps)){
            pinnedApps = this._createExtraPinnedAppsList();
        }

        for(let i = 0;i < pinnedApps.length; i += 3){
            if(i === separatorIndex * 3 && i !== 0)
                this._addSeparator();
            let isContainedInCategory = false;
            let placeMenuItem = this.createMenuItem([pinnedApps[i], pinnedApps[i + 1], pinnedApps[i + 2]], Constants.DisplayType.BUTTON, isContainedInCategory);
            placeMenuItem.actor.x_expand = false;
            placeMenuItem.actor.y_expand = false;
            placeMenuItem.actor.y_align = Clutter.ActorAlign.CENTER;
            placeMenuItem.actor.x_align = Clutter.ActorAlign.CENTER;
            this.actionsBox.add_child(placeMenuItem.actor);
        }  
    }

    createMenuItem(menuItemArray, displayType, isContainedInCategory){
        let placeInfo, placeMenuItem;
        let command = menuItemArray[2];
        let app = Shell.AppSystem.get_default().lookup_app(command);

        if(command === "ArcMenu_Home"){
            let homePath = GLib.get_home_dir();
            placeInfo = new PlaceDisplay.PlaceInfo('special', Gio.File.new_for_path(homePath), _("Home"));
            placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
        }
        else if(command === "ArcMenu_Computer"){
            placeInfo = new PlaceDisplay.RootInfo();
            placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
        }
        else if(command === "ArcMenu_Network"){
            placeInfo = new PlaceDisplay.PlaceInfo('network', Gio.File.new_for_uri('network:///'), _('Network'),'network-workgroup-symbolic');
            placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
        }
        else if(command === "ArcMenu_Software"){
            let software = Utils.findSoftwareManager();
            if(software)
                placeMenuItem = new MW.ShortcutMenuItem(this, _("Software"), menuItemArray[1], software, displayType, isContainedInCategory);
            else
                placeMenuItem = new MW.ShortcutMenuItem(this, _("Software"), 'system-software-install-symbolic', 'ArcMenu_unfound.desktop', displayType, isContainedInCategory);
        }
        else if(command === Constants.ArcMenuSettingsCommand || command === "ArcMenu_Suspend" || command === "ArcMenu_LogOut" || command === "ArcMenu_PowerOff"
            || command === "ArcMenu_Lock" || command === "ArcMenu_Restart" || command === "ArcMenu_HybridSleep" || command === "ArcMenu_Hibernate" || app){

                placeMenuItem = new MW.ShortcutMenuItem(this, menuItemArray[0], menuItemArray[1], menuItemArray[2], displayType, isContainedInCategory);
        }
        else if(command === "ArcMenu_Recent"){
            let uri = "recent:///";
            placeInfo = new PlaceDisplay.PlaceInfo('special', Gio.File.new_for_uri(uri), _(menuItemArray[0]));
            placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
        }
        else if(command.startsWith("ArcMenu_")){
            let path = command.replace("ArcMenu_",'');

            if(path === "Documents")
                path = imports.gi.GLib.UserDirectory.DIRECTORY_DOCUMENTS;
            else if(path === "Downloads")
                path = imports.gi.GLib.UserDirectory.DIRECTORY_DOWNLOAD;
            else if(path === "Music")
                path = imports.gi.GLib.UserDirectory.DIRECTORY_MUSIC;
            else if(path === "Pictures")
                path = imports.gi.GLib.UserDirectory.DIRECTORY_PICTURES;
            else if(path === "Videos")
                path = imports.gi.GLib.UserDirectory.DIRECTORY_VIDEOS;

            path = GLib.get_user_special_dir(path);
            if (path !== null){
                placeInfo = new PlaceDisplay.PlaceInfo('special', Gio.File.new_for_path(path), _(menuItemArray[0]));
                placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
            }
        }
        else{
            let path = command;
            placeInfo = new PlaceDisplay.PlaceInfo('special', Gio.File.new_for_path(path));
            placeMenuItem = new MW.PlaceMenuItem(this, placeInfo, displayType, isContainedInCategory);
        }
        return placeMenuItem;
    }

    loadPinnedApps(){
        let pinnedApps = this._settings.get_strv('pinned-app-list');

        this.pinnedAppsArray = null;
        this.pinnedAppsArray = [];

        let categoryMenuItem = this.categoryDirectories ? this.categoryDirectories.get(Constants.CategoryType.PINNED_APPS) : null;
        let isContainedInCategory = categoryMenuItem ? true : false;

        for(let i = 0; i < pinnedApps.length; i += 3){
            if(i === 0 && pinnedApps[0] === "ArcMenu_WebBrowser")
                this._updatePinnedAppsWebBrowser(pinnedApps);

            let pinnedAppsMenuItem = new MW.PinnedAppsMenuItem(this, pinnedApps[i], pinnedApps[i + 1], pinnedApps[i + 2], this.layoutProperties.DisplayType, isContainedInCategory);
            pinnedAppsMenuItem.connect('saveSettings', ()=> {
                let array = [];
                for(let i = 0; i < this.pinnedAppsArray.length; i++){
                    array.push(this.pinnedAppsArray[i]._name);
                    array.push(this.pinnedAppsArray[i]._iconPath);
                    array.push(this.pinnedAppsArray[i]._command);		   
                }
                this._settings.set_strv('pinned-app-list',array);
            });
            this.pinnedAppsArray.push(pinnedAppsMenuItem);
        }  

        if(categoryMenuItem){
            categoryMenuItem.appList = null;
            categoryMenuItem.appList = [];
            categoryMenuItem.appList = categoryMenuItem.appList.concat(this.pinnedAppsArray);
        } 
    }

    _updatePinnedAppsWebBrowser(pinnedApps){
        //Find the Default Web Browser, if found add to pinned apps list, if not found delete the placeholder.
        //Will only run if placeholder is found. Placeholder only found with default settings set.
        if(pinnedApps[0] === "ArcMenu_WebBrowser"){   
            let browserName = '';
            try{
                //user may not have xdg-utils package installed which will throw error
                let [res, stdout, stderr, status] = GLib.spawn_command_line_sync("xdg-settings get default-web-browser");
                let webBrowser = String.fromCharCode(...stdout);
                browserName = webBrowser.split(".desktop")[0];
                browserName += ".desktop";
            } 
            catch(error){
                global.log("ArcMenu Error - Failed to find default web browser. Removing placeholder pinned app.")
            }

            this._app = appSys.lookup_app(browserName);
            if(this._app){
                pinnedApps[0] = this._app.get_name();
                pinnedApps[1] = '';
                pinnedApps[2] = this._app.get_id();
            }
            else{
                pinnedApps.splice(0,3);
            }
            this.shouldLoadPinnedApps = false; // We don't want to trigger a setting changed event
            this._settings.set_strv('pinned-app-list', pinnedApps);
            this.shouldLoadPinnedApps = true;
        }
    }

    displayPinnedApps(){
        this._clearActorsFromBox();
        this._displayAppList(this.pinnedAppsArray, Constants.CategoryType.PINNED_APPS, this.applicationsGrid);
    }

    placesAddSeparator(id){
        let separator = new MW.ArcMenuSeparator(Constants.SeparatorStyle.SHORT, Constants.SeparatorAlignment.HORIZONTAL);
        this._sections[id].add_child(separator);  
    }

    _redisplayPlaces(id) {
        if(this._sections[id].get_n_children() > 0){
            this.bookmarksShorctus = false;
            this.externalDevicesShorctus = false;
            this.networkDevicesShorctus = false;
            this._sections[id].destroy_all_children();
        }
        this._createPlaces(id);
    }

    _createPlaces(id) {
        let places = this.placesManager.get(id);
        if(this.placesManager.get('network').length > 0)
            this.networkDevicesShorctus = true; 
        if(this.placesManager.get('devices').length > 0)
            this.externalDevicesShorctus=true;  
        if(this.placesManager.get('bookmarks').length > 0)
            this.bookmarksShorctus = true;

        if(this._settings.get_boolean('show-bookmarks')){
            if(id === 'bookmarks' && places.length > 0){
                for (let i = 0; i < places.length; i++){
                    let item = new MW.PlaceMenuItem(this, places[i]);
                    this._sections[id].add_child(item); 
                } 
                //create a separator if bookmark and software shortcut are both shown
                if(this.bookmarksShorctus && this.softwareShortcuts){
                    this.placesAddSeparator(id);
                }
            }
        }
        if(this._settings.get_boolean('show-external-devices')){
            if(id === 'devices'){
                for (let i = 0; i < places.length; i++){
                    let item = new MW.PlaceMenuItem(this, places[i]);
                    this._sections[id].add_child(item); 
                }
                if((this.externalDevicesShorctus && !this.networkDevicesShorctus) && (this.bookmarksShorctus || this.softwareShortcuts))
                    this.placesAddSeparator(id);
            }
            if(id === 'network'){
                for (let i = 0; i < places.length; i++){
                    let item = new MW.PlaceMenuItem(this, places[i]);
                    this._sections[id].add_child(item); 
                }
                if(this.networkDevicesShorctus && (this.bookmarksShorctus || this.softwareShortcuts))
                    this.placesAddSeparator(id);
            }
        }
    }   

    setActiveCategory(categoryItem, setActive = true){
        if(this.activeCategoryItem){
            this.activeCategoryItem.isActiveCategory = false;
            this.activeCategoryItem.remove_style_pseudo_class('active');
            this.activeCategoryItem = null;
        }

        if(!setActive)
            return;

        this.activeCategoryItem = categoryItem;
        this.activeCategoryItem.isActiveCategory = true;
        this.activeCategoryItem.add_style_pseudo_class('active');   

        this._futureActiveItem = categoryItem;
        this.activeMenuItem = categoryItem;
    }

    setFrequentAppsList(categoryMenuItem){
        categoryMenuItem.appList = [];
        let mostUsed = Shell.AppUsage.get_default().get_most_used();
        for (let i = 0; i < mostUsed.length; i++) {
            if (mostUsed[i] && mostUsed[i].get_app_info().should_show())
                categoryMenuItem.appList.push(mostUsed[i]);
        }
    }

    _clearActorsFromBox(box){
        this.ignoreHover = true;
        this.recentFilesManager?.cancelCurrentQueries();
        if(!box){
            box = this.applicationsBox;
            this.activeCategoryType = -1;
        }
        let parent = box.get_parent();
        if(parent instanceof St.ScrollView){
            let scrollBoxAdj = parent.get_vscroll_bar().get_adjustment();
            scrollBoxAdj.set_value(0);
        }
        let actors = box.get_children();
        for (let i = 0; i < actors.length; i++) {
            let actor = actors[i];
            box.remove_child(actor);
        }
    }

    displayCategoryAppList(appList, category){
        this._clearActorsFromBox();
        this._displayAppList(appList, category, this.applicationsGrid);
    }

    _displayAppList(apps, category, grid){
        this.activeCategoryType = category;
        grid.remove_all_children();
        let count = 0;
        let top = -1;
        let left = 0;
        this._futureActiveItem = false;
        let currentCharacter;
        let alphabetizeAllPrograms = this._settings.get_boolean("alphabetize-all-programs") && this.layoutProperties.DisplayType === Constants.DisplayType.LIST;
        let rtl = this.mainBox.get_text_direction() == Clutter.TextDirection.RTL;
        let columns = -1;

        for (let i = 0; i < apps.length; i++) {
            let app = apps[i];
            let item;
            let shouldShow = true;

            if(category === Constants.CategoryType.PINNED_APPS || category === Constants.CategoryType.HOME_SCREEN){
                item = app;
                if(!item.shouldShow)
                    shouldShow = false;
            }
            else{
                item = this.applicationsMap.get(app);
                if (!item) {
                    item = new MW.ApplicationMenuItem(this, app, this.layoutProperties.DisplayType);
                    this.applicationsMap.set(app, item);
                }
            }

            if(item.actor.get_parent())
                item.actor.get_parent().remove_child(item.actor);

            if(shouldShow){
                if(columns === -1){
                    if(grid.layout_manager.forceGridColumns)
                        columns = grid.layout_manager.forceGridColumns;
                    else if(this.layoutProperties.DisplayType === Constants.DisplayType.GRID)
                        columns = this.getColumnsFromActor(item);
                    else
                        columns = 1;
                    grid.layout_manager.gridColumns = columns;
                }
                    
                if(!rtl && (count % columns === 0)){
                    top++;
                    left = 0;
                }
                else if(rtl && (left === 0)){
                    top++;
                    left = columns;
                }

                if(alphabetizeAllPrograms && category === Constants.CategoryType.ALL_PROGRAMS){
                    if(currentCharacter !== app.get_name().charAt(0).toLowerCase()){
                        currentCharacter = app.get_name().charAt(0).toLowerCase();

                        let label = this._createLabelWithSeparator(currentCharacter.toUpperCase());
                        grid.layout_manager.attach(label, left, top, 1, 1);
                        top++;
                    }
                }

                grid.layout_manager.attach(item, left, top, 1, 1);
                item.gridLocation = [left, top];
                
                if(!rtl)
                    left++;
                else if(rtl)
                    left--;
                count++;
    
                if(!this._futureActiveItem && grid === this.applicationsGrid){
                    this._futureActiveItem = item;
                }
            }
        }
        if(this.applicationsBox && !this.applicationsBox.contains(this.applicationsGrid))
            this.applicationsBox.add_child(this.applicationsGrid);
        if(this._futureActiveItem)
            this.activeMenuItem = this._futureActiveItem;
    }

    displayAllApps(){
        let appList = [];
        this.applicationsMap.forEach((value,key,map) => {
            appList.push(key);
        });
        appList.sort((a, b) => {
            return a.get_name().toLowerCase() > b.get_name().toLowerCase();
        });
        this._clearActorsFromBox();
        this._displayAppList(appList, Constants.CategoryType.ALL_PROGRAMS, this.applicationsGrid);
    }

    get activeMenuItem() {
        return this._activeMenuItem;
    }

    set activeMenuItem(item) {
        let itemChanged = item !== this._activeMenuItem;
        if(itemChanged){
            this._activeMenuItem = item;
            if(this.arcMenu.isOpen && item && this.layoutProperties.SupportsCategoryOnHover)
                item.grab_key_focus();
            if(this.layout === Constants.MenuLayout.LAUNCHER && !this.layoutProperties.StandaloneRunner && item)
                this.createActiveSearchItemPanel(item);
        }
    }

    _onSearchBoxChanged(searchBox, searchString) { 
        if(searchBox.isEmpty()){
            this.searchResults.hide();
            this.setDefaultMenuView();
        }            
        else{
            if(this.activeCategoryItem){
                this.setActiveCategory(null, false);
            }
            this._clearActorsFromBox();
            let appsScrollBoxAdj = this.applicationsScrollBox.get_vscroll_bar().get_adjustment();
            appsScrollBoxAdj.set_value(0);
            this.applicationsBox.add_child(this.searchResults);
            this.searchResults.show();
            searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
            this.searchResults.setTerms(searchString.split(/\s+/));
        }            	
    }

    _onSearchBoxKeyPress(searchBox, event) {
        let symbol = event.get_key_symbol();
        switch (symbol) {
            case Clutter.KEY_Up:
            case Clutter.KEY_Down:
            case Clutter.KEY_Left:
            case Clutter.KEY_Right:
                let direction;
                if (symbol === Clutter.KEY_Down || symbol === Clutter.KEY_Up)
                    return Clutter.EVENT_PROPAGATE;
                if (symbol === Clutter.KEY_Right)
                    direction = St.DirectionType.RIGHT;
                if (symbol === Clutter.KEY_Left)
                    direction = St.DirectionType.LEFT;

                let cursorPosition = this.searchBox.clutter_text.get_cursor_position();

                if(cursorPosition === Constants.CaretPosition.END && symbol === Clutter.KEY_Right)
                    cursorPosition = Constants.CaretPosition.END;
                else if(cursorPosition === Constants.CaretPosition.START && symbol === Clutter.KEY_Left)
                    cursorPosition = Constants.CaretPosition.START;
                else
                    cursorPosition = Constants.CaretPosition.MIDDLE;

                if(cursorPosition === Constants.CaretPosition.END || cursorPosition === Constants.CaretPosition.START){
                    let navigateActor = this.activeMenuItem;
                    if(this.searchResults.hasActiveResult()){
                        navigateActor = this.searchResults.getTopResult();
                        if(navigateActor.has_style_pseudo_class("active")){
                            navigateActor.grab_key_focus();
                            return this.mainBox.navigate_focus(navigateActor, direction, false); 
                        }
                        navigateActor.grab_key_focus();
                        return Clutter.EVENT_STOP;
                    }
                    if(!navigateActor)
                        return Clutter.EVENT_PROPAGATE;
                    return this.mainBox.navigate_focus(navigateActor, direction, false);
                }
                return Clutter.EVENT_PROPAGATE;
            default:
                return Clutter.EVENT_PROPAGATE;
        }
    }

    _onMainBoxKeyPress(actor, event) {
        if (event.has_control_modifier()) {
            if(this.searchBox)
                this.searchBox.grab_key_focus();
            return Clutter.EVENT_PROPAGATE;
        }

        let symbol = event.get_key_symbol();
        let unicode = Clutter.keysym_to_unicode(symbol);

        switch (symbol) {
            case Clutter.KEY_BackSpace:
                if(this.searchBox && !this.searchBox.hasKeyFocus() && !this.searchBox.isEmpty()){
                    this.searchBox.grab_key_focus();
                    let newText = this.searchBox.getText().slice(0, -1);
                    this.searchBox.setText(newText);
                }
                return Clutter.EVENT_PROPAGATE;
            case Clutter.KEY_Up:
            case Clutter.KEY_Down:
            case Clutter.KEY_Left:
            case Clutter.KEY_Right:
                let direction;
                if (symbol === Clutter.KEY_Down)
                    direction = St.DirectionType.DOWN;
                if (symbol === Clutter.KEY_Right)
                    direction = St.DirectionType.RIGHT
                if (symbol === Clutter.KEY_Up)
                    direction = St.DirectionType.UP;
                if (symbol === Clutter.KEY_Left)
                    direction = St.DirectionType.LEFT;
                    
                if(this.layoutProperties.Search && this.searchBox.hasKeyFocus() && this.searchResults.hasActiveResult() && this.searchResults.get_parent()){
                    const topSearchResult = this.searchResults.getTopResult();
                    if(topSearchResult.has_style_pseudo_class("active")){
                        topSearchResult.actor.grab_key_focus();
                        topSearchResult.remove_style_pseudo_class('active');
                        return actor.navigate_focus(global.stage.key_focus, direction, false); 
                    }
                    topSearchResult.actor.grab_key_focus();
                    return Clutter.EVENT_STOP;
                }
                else if(global.stage.key_focus === this.mainBox && symbol === Clutter.KEY_Up){
                    return actor.navigate_focus(global.stage.key_focus, direction, true);
                }
                else if(global.stage.key_focus === this.mainBox){
                    this.activeMenuItem.grab_key_focus();
                    return Clutter.EVENT_STOP;
                }

                return actor.navigate_focus(global.stage.key_focus, direction, false);
            case Clutter.KEY_Tab:
            case Clutter.KEY_KP_Tab:
            case Clutter.KEY_KP_Enter:
            case Clutter.KEY_Return:
            case Clutter.KEY_Escape:
                return Clutter.EVENT_PROPAGATE;
            default:
                if (unicode !== 0 && this.searchBox) {
                    global.stage.set_key_focus(this.searchBox.clutter_text);
                    let synthEvent = event.copy();
                    synthEvent.set_source(this.searchBox.clutter_text);
                    this.searchBox.clutter_text.event(synthEvent, false);
                }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    destroy(){
        if(this.recentFilesManager){
            this.recentFilesManager.destroy();
            this.recentFilesManager = null;
        }

        if(this._treeChangedId){
            this._tree.disconnect(this._treeChangedId);
            this._treeChangedId = null;
            this._tree = null;
        }

        if(this.applicationsBox){
            if(this.applicationsBox.contains(this.applicationsGrid))
                this.applicationsBox.remove_child(this.applicationsGrid);
        }
        
        if(this.network){
            this.network.destroy();
            this.networkMenuItem.destroy();
        }

        if(this.computer){
            this.computer.destroy();
            this.computerMenuItem.destroy();
        }

        if(this.placesManager){
            for(let id in this._sections){
                this._sections[id].get_children().forEach((child) =>{
                    child.destroy();
                });
            };
            if(this.placeManagerUpdatedID){
                this.placesManager.disconnect(this.placeManagerUpdatedID);
                this.placeManagerUpdatedID = null;
            }
            this.placesManager.destroy();
            this.placesManager = null
        }

        if(this._searchBoxChangedId){
            this.searchBox?.disconnect(this._searchBoxChangedId);
            this._searchBoxChangedId = null;;
        }
        if(this._searchBoxKeyPressId){
            this.searchBox?.disconnect(this._searchBoxKeyPressId);
            this._searchBoxKeyPressId = null;
        }
        if(this._searchBoxKeyFocusInId){
            this.searchBox?.disconnect(this._searchBoxKeyFocusInId);
            this._searchBoxKeyFocusInId = null;
        }

        if(this.searchBox)
            this.searchBox.destroy();

        if(this.searchResults){
            this.searchResults.setTerms([]);
            this.searchResults.destroy();
            this.searchResults = null;
        }

        if (this._mainBoxKeyPressId) {
            this.mainBox.disconnect(this._mainBoxKeyPressId);
            this._mainBoxKeyPressId = null;
        }

        if(this._gnomeFavoritesReloadID){
            AppFavorites.getAppFavorites().disconnect(this._gnomeFavoritesReloadID);
            this._gnomeFavoritesReloadID = null;
        }

        if(this.pinnedAppsArray){
            for(let i = 0; i < this.pinnedAppsArray.length; i++){
                this.pinnedAppsArray[i].destroy();
            }
            this.pinnedAppsArray = null;
        }

        if(this.applicationsMap){
            this.applicationsMap.forEach((value,key,map)=>{
                value.destroy();
            });
            this.applicationsMap = null;
        }

        if(this.categoryDirectories){
            this.categoryDirectories.forEach((value,key,map)=>{
                value.destroy();
            });
            this.categoryDirectories = null;    
        }

        this.mainBox.destroy_all_children();
    }

    _createScrollBox(params){
        let scrollBox = new St.ScrollView(params);    
        let panAction = new Clutter.PanAction({ interpolate: false });
        panAction.connect('pan', (action) => {
            this._blockActivateEvent = true;
            this.onPan(action, scrollBox);
        });
        panAction.connect('gesture-cancel',(action) => this.onPanEnd(action, scrollBox));
        panAction.connect('gesture-end', (action) => this.onPanEnd(action, scrollBox));
        scrollBox.add_action(panAction);

        scrollBox.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        scrollBox.clip_to_allocation = true;

        return scrollBox;
    }

    _createLabelWithSeparator(headerLabel){
        let separator = new MW.ArcMenuSeparator(Constants.SeparatorStyle.HEADER_LABEL, Constants.SeparatorAlignment.HORIZONTAL, headerLabel);
        return separator;
    }

    createLabelRow(title){
        let labelRow = new St.BoxLayout({
            style: "padding: 9px 12px;"
        });
        let label = new St.Label({
            text:_(title),
            y_align: Clutter.ActorAlign.CENTER,
            style: 'font-weight: bold;'
        })
        labelRow.add_child(label);
        labelRow.label = label;
        return labelRow;
    }

    _keyFocusIn(actor) {
        if (this._focusChild == actor)
            return;
        this._focusChild = actor;
        Utils.ensureActorVisibleInScrollView(actor);
    }

    onPan(action, scrollbox) {
        let [dist_, dx_, dy] = action.get_motion_delta(0);
        let adjustment = scrollbox.get_vscroll_bar().get_adjustment();
        adjustment.value -=  dy;
        return false;
    }
    
    onPanEnd(action, scrollbox) {
        let velocity = -action.get_velocity(0)[2];
        let adjustment = scrollbox.get_vscroll_bar().get_adjustment();
        let endPanValue = adjustment.value + velocity * 2;
        adjustment.value = endPanValue;
    }
};
