namespace QuickSetup {
    public enum AppPinTarget {
        NONE,
        DOCK,
        START_MENU
    }

    private class FlatpakPinRule : Object {
        public string desktop_file;
        public AppPinTarget pin_target;
        public bool is_browser;
    }

    private class SwaiBundlePin : Object {
        public string[] app_ids;
    }

    const string SHELL_SCHEMA = "org.gnome.shell";
    const string SHELL_FAVORITES_KEY = "favorite-apps";
    const string ARCMENU_SCHEMA = "org.gnome.shell.extensions.arcmenu";
    const string ARCMENU_PINNED_KEY = "pinned-apps";
    const string APP_FOLDERS_SCHEMA = "org.gnome.desktop.app-folders";
    const string APP_FOLDER_SCHEMA = "org.gnome.desktop.app-folders.folder";

    const string[] BROWSER_DESKTOPS = {
        "firefox.desktop",
        "org.mozilla.firefox.desktop",
        "org.chromium.Chromium.desktop",
        "app.zen_browser.zen.desktop",
        "io.gitlab.librewolf-community.desktop",
        "com.brave.Browser.desktop",
        "com.vivaldi.Vivaldi.desktop",
        "com.google.Chrome.desktop",
        "com.microsoft.Edge.desktop",
        "com.opera.Opera.desktop"
    };

    private class AppPinMaps : Object {
        private static GLib.HashTable<string, FlatpakPinRule> flatpak_pin_rules;
        private static GLib.HashTable<string, AppPinTarget> swai_pin_rules;
        private static GLib.HashTable<string, SwaiBundlePin> swai_bundle_pin_apps;

        static construct {
        flatpak_pin_rules = new HashTable<string, FlatpakPinRule>(str_hash, str_equal);
        swai_pin_rules = new HashTable<string, AppPinTarget>(str_hash, str_equal);
        swai_bundle_pin_apps = new HashTable<string, SwaiBundlePin>(str_hash, str_equal);

        register_flatpak("org.mozilla.firefox", "org.mozilla.firefox.desktop", AppPinTarget.DOCK, true);
        register_flatpak("org.chromium.Chromium", "org.chromium.Chromium.desktop", AppPinTarget.DOCK, true);
        register_flatpak("app.zen_browser.zen", "app.zen_browser.zen.desktop", AppPinTarget.DOCK, true);
        register_flatpak("io.gitlab.librewolf-community", "io.gitlab.librewolf-community.desktop", AppPinTarget.DOCK, true);
        register_flatpak("com.brave.Browser", "com.brave.Browser.desktop", AppPinTarget.DOCK, true);
        register_flatpak("com.vivaldi.Vivaldi", "com.vivaldi.Vivaldi.desktop", AppPinTarget.DOCK, true);
        register_flatpak("com.google.Chrome", "com.google.Chrome.desktop", AppPinTarget.DOCK, true);
        register_flatpak("com.microsoft.Edge", "com.microsoft.Edge.desktop", AppPinTarget.DOCK, true);
        register_flatpak("com.opera.Opera", "com.opera.Opera.desktop", AppPinTarget.DOCK, true);

        register_flatpak("org.gnome.Contacts", "", AppPinTarget.NONE);
        register_flatpak("com.belmoussaoui.Authenticator", "", AppPinTarget.NONE);
        register_flatpak("it.mijorus.gearlever", "", AppPinTarget.NONE);
        register_flatpak("org.gnome.NetworkDisplays", "", AppPinTarget.NONE);
        register_flatpak("org.gnome.Firmware", "", AppPinTarget.NONE);
        register_flatpak("com.github.tchx84.Flatseal", "", AppPinTarget.NONE);

        register_flatpak("org.onlyoffice.desktopeditors", "org.onlyoffice.desktopeditors.desktop", AppPinTarget.DOCK);
        register_flatpak("org.mozilla.Thunderbird", "org.mozilla.Thunderbird.desktop", AppPinTarget.DOCK);
        register_flatpak("org.gnome.Geary", "org.gnome.Geary.desktop", AppPinTarget.DOCK);
        register_flatpak("us.zoom.Zoom", "us.zoom.Zoom.desktop", AppPinTarget.DOCK);
        register_flatpak("com.slack.Slack", "com.slack.Slack.desktop", AppPinTarget.DOCK);
        register_flatpak("org.jitsi.jitsi-meet", "org.jitsi.jitsi-meet.desktop", AppPinTarget.DOCK);
        register_flatpak("com.mattermost.Desktop", "com.mattermost.Desktop.desktop", AppPinTarget.DOCK);
        register_flatpak("md.obsidian.Obsidian", "md.obsidian.Obsidian.desktop", AppPinTarget.DOCK);
        register_flatpak("com.logseq.Logseq", "com.logseq.Logseq.desktop", AppPinTarget.DOCK);
        register_flatpak("com.todoist.Todoist", "com.todoist.Todoist.desktop", AppPinTarget.DOCK);
        register_flatpak("com.spotify.Client", "com.spotify.Client.desktop", AppPinTarget.DOCK);
        register_flatpak("com.valvesoftware.Steam", "com.valvesoftware.Steam.desktop", AppPinTarget.DOCK);
        register_flatpak("net.lutris.Lutris", "net.lutris.Lutris.desktop", AppPinTarget.DOCK);
        register_flatpak("com.heroicgameslauncher.hgl", "com.heroicgameslauncher.hgl.desktop", AppPinTarget.DOCK);
        register_flatpak("com.discordapp.Discord", "com.discordapp.Discord.desktop", AppPinTarget.DOCK);
        register_flatpak("com.obsproject.Studio", "com.obsproject.Studio.desktop", AppPinTarget.DOCK);
        register_flatpak("org.gimp.GIMP", "org.gimp.GIMP.desktop", AppPinTarget.DOCK);
        register_flatpak("org.kde.krita", "org.kde.krita.desktop", AppPinTarget.DOCK);
        register_flatpak("org.blender.Blender", "org.blender.Blender.desktop", AppPinTarget.DOCK);
        register_flatpak("org.gnome.Showtime", "org.gnome.Showtime.desktop", AppPinTarget.DOCK);
        register_flatpak("de.haeckerfelix.Shortwave", "de.haeckerfelix.Shortwave.desktop", AppPinTarget.DOCK);
        register_flatpak("ca.edestcroix.Recordbox", "ca.edestcroix.Recordbox.desktop", AppPinTarget.DOCK);
        register_flatpak("org.gnome.Podcasts", "org.gnome.Podcasts.desktop", AppPinTarget.DOCK);

        register_flatpak("io.github.amit9838.mousam", "io.github.amit9838.mousam.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.SoundRecorder", "org.gnome.SoundRecorder.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.github.maoschanz.drawing", "com.github.maoschanz.drawing.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.World.Secrets", "org.gnome.World.Secrets.desktop", AppPinTarget.START_MENU);
        register_flatpak("io.gitlab.news_flash.NewsFlash", "io.gitlab.news_flash.NewsFlash.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.rafaelmardojai.Blanket", "com.rafaelmardojai.Blanket.desktop", AppPinTarget.START_MENU);
        register_flatpak("app.drey.Warp", "app.drey.Warp.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.DejaDup", "", AppPinTarget.NONE);
        register_flatpak("org.gnome.Music", "org.gnome.Music.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.Lollypop", "org.gnome.Lollypop.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.Rhythmbox3", "org.gnome.Rhythmbox3.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.musicbrainz.Picard", "org.musicbrainz.Picard.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.SoundJuicer", "org.gnome.SoundJuicer.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.soundconverter.SoundConverter", "org.soundconverter.SoundConverter.desktop", AppPinTarget.START_MENU);
        register_flatpak("io.github.sharkwouter.Minigalaxy", "io.github.sharkwouter.Minigalaxy.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.usebottles.bottles", "com.usebottles.bottles.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.vysp3r.ProtonPlus", "com.vysp3r.ProtonPlus.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.libretro.RetroArch", "org.libretro.RetroArch.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.dosbox_x.DOSBox-X", "com.dosbox_x.DOSBox-X.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.prismlauncher.PrismLauncher", "org.prismlauncher.PrismLauncher.desktop", AppPinTarget.START_MENU);
        register_flatpak("io.github.antimicrox.antimicrox", "io.github.antimicrox.antimicrox.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.kde.kdenlive", "org.kde.kdenlive.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.pitivi.Pitivi", "org.pitivi.Pitivi.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.audacityteam.Audacity", "org.audacityteam.Audacity.desktop", AppPinTarget.START_MENU);
        register_flatpak("fm.reaper.Reaper", "fm.reaper.Reaper.desktop", AppPinTarget.START_MENU);
        register_flatpak("fr.handbrake.ghb", "fr.handbrake.ghb.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.inkscape.Inkscape", "org.inkscape.Inkscape.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.darktable.Darktable", "org.darktable.Darktable.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.icons8.Lunacy", "com.icons8.Lunacy.desktop", AppPinTarget.START_MENU);
        register_flatpak("com.authormore.penpotdesktop", "com.authormore.penpotdesktop.desktop", AppPinTarget.START_MENU);
        register_flatpak("io.github.alainm23.planify", "io.github.alainm23.planify.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.Weather", "org.gnome.Weather.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.gnome.World.PikaBackup", "org.gnome.World.PikaBackup.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.vinegarhq.Sober", "org.vinegarhq.Sober.desktop", AppPinTarget.START_MENU);
        register_flatpak("org.libreoffice.LibreOffice", "org.libreoffice.LibreOffice.desktop", AppPinTarget.DOCK);

        register_swai("com.google.mail", AppPinTarget.DOCK);
        register_swai("com.microsoft.outlook", AppPinTarget.DOCK);
        register_swai("com.microsoft.teams", AppPinTarget.DOCK);
        register_swai("com.google.meet", AppPinTarget.DOCK);
        register_swai("so.notion.app", AppPinTarget.DOCK);
        register_swai("com.trello", AppPinTarget.DOCK);
        register_swai("com.mondaydotcom", AppPinTarget.DOCK);
        register_swai("com.clickup", AppPinTarget.DOCK);
        register_swai("com.apple.music", AppPinTarget.DOCK);
        register_swai("com.google.youtubemusic", AppPinTarget.DOCK);
        register_swai("com.tidal", AppPinTarget.DOCK);
        register_swai("com.amazon.music", AppPinTarget.DOCK);
        register_swai("com.photopea", AppPinTarget.DOCK);
        register_swai("com.adobe.photoshop", AppPinTarget.DOCK);
        register_swai("com.adobe.express", AppPinTarget.DOCK);
        register_swai("com.canva", AppPinTarget.DOCK);
        register_swai("com.figma", AppPinTarget.DOCK);

        register_swai_office_bundle("com.microsoft.365.bundle", {
            "com.microsoft.word",
            "com.microsoft.excel",
            "com.microsoft.powerpoint"
        });
        register_swai_office_bundle("com.microsoft.365.bundle.enterprise", {
            "com.microsoft.word.enterprise",
            "com.microsoft.excel.enterprise",
            "com.microsoft.powerpoint.enterprise"
        });
        // Google Workspace: Docs, Sheets, and Slides (Word/Excel/PowerPoint equivalents).
        register_swai_office_bundle("com.google.workplace", {
            "com.google.docs",
            "com.google.sheets",
            "com.google.slides"
        });
        }

        private static void register_flatpak(
            string flatpak_id,
            string desktop_file,
            AppPinTarget pin_target,
            bool is_browser = false
        ) {
            var rule = new FlatpakPinRule() {
                desktop_file = desktop_file,
                pin_target = pin_target,
                is_browser = is_browser
            };
            flatpak_pin_rules.insert(flatpak_id, rule);
        }

        private static void register_swai(string swai_id, AppPinTarget pin_target) {
            swai_pin_rules.insert(swai_id, pin_target);
        }

        private static void register_swai_office_bundle(string bundle_id, string[] component_app_ids) {
            swai_bundle_pin_apps.insert(bundle_id, new SwaiBundlePin() {
                app_ids = component_app_ids
            });
            foreach (var app_id in component_app_ids) {
                register_swai(app_id, AppPinTarget.DOCK);
            }
        }

        public static FlatpakPinRule? lookup_flatpak_rule(string flatpak_id) {
            return flatpak_pin_rules.lookup(flatpak_id);
        }

        public static bool contains_swai(string swai_id) {
            return swai_pin_rules.contains(swai_id);
        }

        public static AppPinTarget lookup_swai(string swai_id) {
            return swai_pin_rules.lookup(swai_id);
        }

        public static bool contains_swai_bundle(string bundle_key) {
            return swai_bundle_pin_apps.contains(bundle_key);
        }

        public static string[] lookup_swai_bundle(string bundle_key) {
            return swai_bundle_pin_apps.lookup(bundle_key).app_ids;
        }
    }

    public bool is_browser_app(AppData app) {
        string? flatpak_id = extract_flatpak_id(app.app_id);
        if (flatpak_id == null) {
            return false;
        }

        var rule = AppPinMaps.lookup_flatpak_rule(flatpak_id);
        return rule != null && rule.is_browser;
    }

    public void pin_checked_browsers(List<AppData> apps) {
        foreach (var app in apps) {
            if (app.checked && is_browser_app(app)) {
                pin_browser_if_checked(app);
            }
        }
    }

    public void pin_checked_browsers_in_categories(List<CategoryData> categories) {
        foreach (var category in categories) {
            pin_checked_browsers(category.apps);
        }
    }

    public void sync_browser_favorites() {
        foreach (var desktop_file in BROWSER_DESKTOPS) {
            if (desktop_app_is_installed(desktop_file)
                || shell_favorites_contains(desktop_file)) {
                prepend_shell_favorite(desktop_file);
            }
        }
    }

    public void configure_gnome_app_folders() {
        append_app_folder("System", {
            "org.gnome.Firmware.desktop",
            "org.gnome.DejaDup.desktop",
            "it.mijorus.gearlever.desktop",
            "org.gnome.NetworkDisplays.desktop",
            "com.github.tchx84.Flatseal.desktop"
        });
        append_app_folder("Utilities", {
            "org.gnome.Showtime.desktop",
            "org.gnome.SoundRecorder.desktop"
        });
    }

    public void pin_forums_to_arcmenu() {
        apply_pin(
            "io.stillhq.swai.io.stillhq.forums.desktop",
            AppPinTarget.START_MENU
        );
    }

    private void pin_browser_if_checked(AppData app) {
        if (!app.checked || !is_browser_app(app)) {
            return;
        }

        var desktop = resolve_desktop_file(app);
        if (desktop == null || desktop.strip() == "") {
            warning("Could not resolve desktop file for browser %s (%s)", app.name, app.app_id);
            return;
        }

        prepend_shell_favorite(desktop);
        schedule_browser_pin_after_install(desktop);
    }

    private static void schedule_browser_pin_after_install(string desktop_file) {
        string helper = "/usr/bin/quick-setup-browser-pin-helper";
        if (!FileUtils.test(helper, FileTest.IS_EXECUTABLE)) {
            helper = "scripts/quick-setup-browser-pin-helper";
        }

        try {
            Process.spawn_async(
                null,
                { helper, desktop_file },
                null,
                SpawnFlags.SEARCH_PATH,
                null,
                null
            );
        } catch (Error e) {
            warning("Failed to schedule browser pin for %s: %s", desktop_file, e.message);
        }
    }

    public void pin_app(AppData app) {
        if (app.app_id == null || app.app_id.strip() == "") {
            return;
        }

        if (is_browser_app(app)) {
            return;
        }

        if (is_swai_bundle_install(app)) {
            pin_swai_bundle_apps(app);
            return;
        }

        string? flatpak_id = extract_flatpak_id(app.app_id);
        if (flatpak_id == "org.libreoffice.LibreOffice") {
            pin_libreoffice_suite(get_pin_target(app));
            return;
        }

        var target = get_pin_target(app);
        if (target == AppPinTarget.NONE) {
            return;
        }

        var desktop = resolve_desktop_file(app);
        if (desktop == null || desktop.strip() == "") {
            warning("Could not resolve desktop file for %s (%s)", app.name, app.app_id);
            return;
        }

        apply_pin(desktop, target);
    }

    public AppPinTarget get_pin_target(AppData app) {
        string? flatpak_id = extract_flatpak_id(app.app_id);
        if (flatpak_id != null) {
            var rule = AppPinMaps.lookup_flatpak_rule(flatpak_id);
            if (rule != null) {
                return rule.pin_target;
            }
            return classify_flatpak_by_subcategory(app);
        }

        string swai_id = extract_swai_id(app);
        if (swai_id == "") {
            return AppPinTarget.NONE;
        }

        if (AppPinMaps.contains_swai(swai_id)) {
            return AppPinMaps.lookup_swai(swai_id);
        }
        return classify_swai_by_subcategory(app);
    }

    public string? resolve_desktop_file(AppData app) {
        string? flatpak_id = extract_flatpak_id(app.app_id);
        if (flatpak_id != null) {
            return resolve_flatpak_desktop(flatpak_id);
        }

        string swai_id = extract_swai_id(app);
        if (swai_id != "") {
            return resolve_swai_desktop(swai_id);
        }

        return null;
    }

    private static bool is_swai_bundle_install(AppData app) {
        if (app.package_manager != "swai") {
            return false;
        }
        return AppPinMaps.contains_swai_bundle(extract_swai_id(app));
    }

    private static void pin_swai_bundle_apps(AppData app) {
        string bundle_key = extract_swai_id(app);
        if (!AppPinMaps.contains_swai_bundle(bundle_key)) {
            warning("Unknown SWAI bundle id for pinning: %s", bundle_key);
            return;
        }

        var target = get_pin_target(app);
        if (target == AppPinTarget.NONE) {
            return;
        }

        foreach (var swai_id in AppPinMaps.lookup_swai_bundle(bundle_key)) {
            apply_pin(resolve_swai_desktop(swai_id), target);
        }
    }

    // Desktop filenames are derived from package metadata because pinning runs
    // when the install is queued, before the .desktop file exists on disk.
    private static string resolve_flatpak_desktop(string flatpak_id) {
        var rule = AppPinMaps.lookup_flatpak_rule(flatpak_id);
        if (rule != null && rule.desktop_file != "") {
            return rule.desktop_file;
        }
        return flatpak_id + ".desktop";
    }

    private static string resolve_swai_desktop(string swai_id) {
        return "io.stillhq.swai." + swai_id + ".desktop";
    }

    private static void pin_libreoffice_suite(AppPinTarget target) {
        if (target == AppPinTarget.NONE) {
            return;
        }

        string[] desktops = {
            "org.libreoffice.LibreOffice.writer.desktop",
            "org.libreoffice.LibreOffice.calc.desktop",
            "org.libreoffice.LibreOffice.impress.desktop"
        };

        foreach (var desktop in desktops) {
            apply_pin(desktop, target);
        }
    }

    private static string? extract_flatpak_id(string app_id) {
        if (!app_id.has_prefix("app/")) {
            return null;
        }
        string[] parts = app_id.split("/");
        if (parts.length >= 2 && parts[1] != "") {
            return parts[1];
        }
        return null;
    }

    private static string extract_swai_id(AppData app) {
        string raw = app.app_id;
        int comma = raw.index_of_char(',');
        if (comma >= 0) {
            raw = raw.substring(0, comma);
        }
        return raw.strip();
    }

    private static AppPinTarget classify_flatpak_by_subcategory(AppData app) {
        switch (app.sub_category) {
            case "office":
            case "email":
            case "communication":
            case "music_streaming":
            case "knowledge_base":
            case "launchers":
            case "graphics":
            case "video_editing":
            case "design":
                return AppPinTarget.DOCK;
            case "project_management":
                return AppPinTarget.DOCK;
            case "crm":
            case "local_music":
            case "music_tools":
            case "compatibility":
            case "emulators":
            case "gaming_tools":
            case "audio_editing":
            case "creative_tools":
            case "three_d":
                return AppPinTarget.START_MENU;
            default:
                if (app.recommended) {
                    return AppPinTarget.START_MENU;
                }
                return AppPinTarget.NONE;
        }
    }

    private static AppPinTarget classify_swai_by_subcategory(AppData app) {
        switch (app.sub_category) {
            case "office":
            case "email":
            case "communication":
            case "music_streaming":
            case "knowledge_base":
            case "graphics":
            case "design":
                return AppPinTarget.DOCK;
            case "project_management":
                return AppPinTarget.DOCK;
            case "crm":
                return AppPinTarget.START_MENU;
            default:
                return AppPinTarget.START_MENU;
        }
    }

    private static void apply_pin(string desktop_file, AppPinTarget target) {
        switch (target) {
            case AppPinTarget.DOCK:
                append_shell_favorite(desktop_file);
                break;
            case AppPinTarget.START_MENU:
                append_arcmenu_pin(desktop_file);
                break;
            default:
                break;
        }
    }

    private static void append_shell_favorite(string desktop_file) {
        try {
            var settings = new GLib.Settings(SHELL_SCHEMA);
            var favorites = settings.get_strv(SHELL_FAVORITES_KEY);
            foreach (var existing in favorites) {
                if (existing == desktop_file) {
                    return;
                }
            }

            settings.set_strv(SHELL_FAVORITES_KEY, append_string(favorites, desktop_file));
        } catch (Error e) {
            warning("Failed to pin %s to dock: %s", desktop_file, e.message);
        }
    }

    private static void prepend_shell_favorite(string desktop_file) {
        try {
            var settings = new GLib.Settings(SHELL_SCHEMA);
            var favorites = settings.get_strv(SHELL_FAVORITES_KEY);
            string[] updated = { desktop_file };

            foreach (var existing in favorites) {
                if (existing != desktop_file) {
                    updated += existing;
                }
            }

            settings.set_strv(SHELL_FAVORITES_KEY, updated);
        } catch (Error e) {
            warning("Failed to pin %s to the start of the dock: %s", desktop_file, e.message);
        }
    }

    private static bool shell_favorites_contains(string desktop_file) {
        var settings = new GLib.Settings(SHELL_SCHEMA);
        foreach (var existing in settings.get_strv(SHELL_FAVORITES_KEY)) {
            if (existing == desktop_file) {
                return true;
            }
        }
        return false;
    }

    private static bool desktop_app_is_installed(string desktop_file) {
        string user_data_dir = Environment.get_user_data_dir();
        string user_home = Environment.get_home_dir();
        string[] application_dirs = {
            Path.build_filename(user_data_dir, "applications"),
            "/usr/local/share/applications",
            "/usr/share/applications",
            Path.build_filename(user_home, ".local/share/flatpak/exports/share/applications"),
            "/var/lib/flatpak/exports/share/applications"
        };

        foreach (var directory in application_dirs) {
            if (FileUtils.test(
                Path.build_filename(directory, desktop_file),
                FileTest.EXISTS
            )) {
                return true;
            }
        }
        return false;
    }

    private static void append_app_folder(string folder_id, string[] desktop_files) {
        try {
            var folders = new GLib.Settings(APP_FOLDERS_SCHEMA);
            var children = folders.get_strv("folder-children");
            bool has_folder = false;
            foreach (var child in children) {
                if (child == folder_id) {
                    has_folder = true;
                    break;
                }
            }
            if (!has_folder) {
                folders.set_strv("folder-children", append_string(children, folder_id));
            }

            string path = "/org/gnome/desktop/app-folders/folders/" + folder_id + "/";
            var folder = new GLib.Settings.with_path(APP_FOLDER_SCHEMA, path);
            if (folder.get_string("name") == "") {
                folder.set_string("name", folder_id);
                folder.set_boolean("translate", false);
            }

            var apps = folder.get_strv("apps");
            foreach (var desktop_file in desktop_files) {
                bool exists = false;
                foreach (var app in apps) {
                    if (app == desktop_file) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) {
                    apps = append_string(apps, desktop_file);
                }
            }
            folder.set_strv("apps", apps);
        } catch (Error e) {
            warning("Failed to configure GNOME app folder %s: %s", folder_id, e.message);
        }
    }

    private static void append_arcmenu_pin(string desktop_file) {
        try {
            var settings = new GLib.Settings(ARCMENU_SCHEMA);
            var pinned = settings.get_value(ARCMENU_PINNED_KEY);
            if (pinned == null) {
                var empty = new VariantBuilder(new VariantType("aa{ss}"));
                pinned = empty.end();
            }

            size_t count = pinned.n_children();
            for (size_t i = 0; i < count; i++) {
                var entry = pinned.get_child_value(i);
                var existing = entry.lookup_value("id", GLib.VariantType.STRING);
                if (existing != null && existing.get_string() == desktop_file) {
                    return;
                }
            }

            var builder = new VariantBuilder(pinned.get_type());
            for (size_t i = 0; i < count; i++) {
                builder.add_value(pinned.get_child_value(i));
            }

            var dict_builder = new VariantBuilder(new VariantType("a{ss}"));
            dict_builder.add("{ss}", "id", desktop_file);
            builder.add_value(dict_builder.end());

            settings.set_value(ARCMENU_PINNED_KEY, builder.end());
        } catch (Error e) {
            warning("Failed to pin %s to start menu: %s", desktop_file, e.message);
        }
    }

    private static string[] append_string(string[] values, string value) {
        var updated = new string[values.length + 1];
        for (int i = 0; i < values.length; i++) {
            updated[i] = values[i];
        }
        updated[values.length] = value;
        return updated;
    }
}
