"""
MATE-like app menu applet for the Panorama panel.
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public Licence as published by
the Free Software Foundation, either version 3 of the Licence, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public Licence for more details.

You should have received a copy of the GNU General Public Licence
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

import os
from pathlib import Path
import locale
import panorama_panel

import gi
gi.require_version("Gtk", "4.0")

from gi.repository import Gtk, GLib, Gio, Gdk


module_directory = Path(__file__).resolve().parent


CATEGORY_MAPPINGS = {
    "Utility": {"menu_name": "Accessories", "icon": "applications-accessories"},
    "Development": {"menu_name": "Programming", "icon": "applications-development"},
    "Game": {"menu_name": "Games", "icon": "applications-games"},
    "Graphics": {"menu_name": "Graphics", "icon": "applications-graphics"},
    "Network": {"menu_name": "Network", "icon": "applications-internet"},
    "AudioVideo": {"menu_name": "Multimedia", "icon": "applications-multimedia"},
    "Office": {"menu_name": "Office", "icon": "applications-office"},
    "Science": {"menu_name": "Science", "icon": "applications-science"},
    "Education": {"menu_name": "Education", "icon": "applications-education"},
    "System": {"menu_name": "System", "icon": "applications-system"},
    "Settings": {"menu_name": "Settings", "icon": "preferences-desktop"},
    "Other": {"menu_name": "Other", "icon": "applications-other"},
}

custom_css = """
.no-menu-item-padding:dir(ltr) {
    padding-left: 0;
}

.no-menu-item-padding:dir(rtl) {
    padding-right: 0;
}
"""

css_provider = Gtk.CssProvider()
css_provider.load_from_data(custom_css)
Gtk.StyleContext.add_provider_for_display(
    Gdk.Display.get_default(),
    css_provider,
    Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)


def force_visible_on_visible_notify(widget, *args):
    if not widget.get_visible():
        widget.set_visible(True)


def add_icons_to_menu(popover: Gtk.PopoverMenu):
    section = popover.get_child().get_first_child().get_first_child().get_first_child()
    while section is not None:
        child = section.get_first_child().get_first_child()

        while child is not None:
            gutter_box: Gtk.Box = child.get_first_child()
            if isinstance(gutter_box.get_next_sibling(), Gtk.Image):
                # For some reason GTK creates images but they're hidden?
                image: Gtk.Image = gutter_box.get_next_sibling()
                label: Gtk.Label = image.get_next_sibling()

                image.unparent()
                gutter_box.unparent()
                gutter_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
                gutter_box.insert_before(child, label)
                gutter_box.append(image)
                image.set_icon_size(Gtk.IconSize.LARGE)
                image.set_margin_start(8)
                image.set_margin_end(8)

                child.add_css_class("no-menu-item-padding")

                # Push the arrow to the left
                label.set_halign(Gtk.Align.FILL)
                label.set_hexpand(True)
                label.set_xalign(0)

                # GTK pushes its stance on icons so hard it makes them invisible multiple times;
                # force it visible
                image.set_visible(True)
                image.connect("notify::visible", force_visible_on_visible_notify)

            # Find the submenu if there is one
            subchild = child.get_first_child()
            submenu = None
            while subchild is not None:
                if isinstance(subchild, Gtk.PopoverMenu):
                    submenu = subchild
                subchild = subchild.get_next_sibling()

            # Recursive
            if submenu is not None:
                add_icons_to_menu(submenu)

            child = child.get_next_sibling()

        section = section.get_next_sibling()


locale.bindtextdomain("panorama-app-menu", module_directory / "locale")
_ = lambda x: locale.dgettext("panorama-app-menu", x)


class AppMenu(panorama_panel.Applet):
    name = _("App menu")
    description = _("Show apps installed on your system, grouped by category")

    def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None):
        super().__init__(orientation=orientation, config=config)
        locale.bindtextdomain("panorama-app-menu", module_directory / "locale")
        _ = lambda x: locale.dgettext("panorama-app-menu", x)
        if config is None:
            config = {}

        self.auto_refresh = config.get("auto_refresh", True)
        self.category_mappings = config.get("category_mappings", CATEGORY_MAPPINGS)
        self.trigger_name = config.get("trigger_name", "app-menu")
        self.icon_name = config.get("icon_name", "start-here-symbolic")

        self.button = Gtk.MenuButton()
        self.button.set_has_frame(False)   # flat look
        self.icon = Gtk.Image.new_from_icon_name(self.icon_name)
        self.button.set_child(self.icon)
        self.apps_by_id: dict[int, Gio.AppInfo] = {}
        # Wait for the widget to be in a layer-shell window before doing this
        self.connect("realize", lambda *args: self.add_trigger_to_app())

        self.menu = Gio.Menu()
        self.popover = Gtk.PopoverMenu.new_from_model_full(self.menu, Gtk.PopoverMenuFlags.NESTED)
        self.popover.set_has_arrow(False)
        panorama_panel.track_popover(self.popover)
        self.button.set_popover(self.popover)

        self.generate_app_menu()

        self.append(self.button)

        self.context_menu = self.make_context_menu()
        panorama_panel.track_popover(self.context_menu)

        right_click_controller = Gtk.GestureClick()
        right_click_controller.set_button(3)
        right_click_controller.connect("pressed", self.show_context_menu)

        self.add_controller(right_click_controller)

        action_group = Gio.SimpleActionGroup()
        options_action = Gio.SimpleAction.new("options", None)
        options_action.connect("activate", self.show_options)
        action_group.add_action(options_action)
        options_action = Gio.SimpleAction.new("launch-app", GLib.VariantType.new("s"))
        options_action.connect("activate", self.launch_app)
        action_group.add_action(options_action)
        self.insert_action_group("applet", action_group)

        if self.auto_refresh:
            self.app_info_monitor = Gio.AppInfoMonitor.get()
            self.app_info_monitor.connect("changed", self.generate_app_menu)

        self.options_window = None

    def add_trigger_to_app(self):
        app: Gtk.Application = self.get_root().get_application()
        action = Gio.SimpleAction.new(self.trigger_name, None)
        action.connect("activate", lambda *args: self.button.popup())
        app.add_action(action)

    def launch_app(self, action, id: GLib.Variant):
        app = self.apps_by_id[int(id.get_string())]
        app.launch()

    def make_context_menu(self):
        menu = Gio.Menu()
        menu.append(_("Menu _options"), "applet.options")
        context_menu = Gtk.PopoverMenu.new_from_model(menu)
        context_menu.set_has_arrow(False)
        context_menu.set_parent(self)
        context_menu.set_halign(Gtk.Align.START)
        context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
        return context_menu

    def show_context_menu(self, gesture, n_presses, x, y):
        rect = Gdk.Rectangle()
        rect.x = int(x)
        rect.y = int(y)
        rect.width = 1
        rect.height = 1

        self.context_menu.set_pointing_to(rect)
        self.context_menu.popup()

    def show_options(self, _0=None, _1=None):
        ...

    def shutdown(self):
        app: Gtk.Application = self.get_root().get_application()
        app.remove_action(self.trigger_name)

    def get_config(self):
        return {"category_mappings": self.category_mappings, "trigger_name": self.trigger_name, "icon_name": self.icon_name}

    def set_panel_position(self, position):
        self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position])
        self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]])

    def generate_app_menu(self, app_info_monitor=None):
        self.menu.remove_all()
        self.apps_by_id = {}

        all_apps = Gio.AppInfo.get_all()
        apps_by_category: dict[str, list[Gio.AppInfo]] = {}
        for category, info in self.category_mappings.items():
            apps_by_category[category] = []

        for app in all_apps:
            category_found = False
            if isinstance(app, Gio.DesktopAppInfo):
                if app.get_categories() is not None:
                    categories = app.get_categories().split(";")
                    for category in categories:
                        if category in apps_by_category:
                            apps_by_category[category].append(app)
                            category_found = True

            if not category_found:
                apps_by_category["Other"].append(app)

        for apps in apps_by_category.values():
            apps.sort(key=lambda app: app.get_display_name())

        for category, info in self.category_mappings.items():
            if apps_by_category[category]:
                item = Gio.MenuItem.new(_(info["menu_name"]))
                item.set_icon(Gio.ThemedIcon.new(info["icon"]))
                submenu = Gio.Menu()

                for app in apps_by_category[category]:
                    if isinstance(app, Gio.DesktopAppInfo) and (app.get_is_hidden() or app.get_nodisplay()):
                        continue

                    subitem = Gio.MenuItem.new(app.get_display_name(), f"applet.launch-app::{id(app)}")
                    subitem.set_icon(app.get_icon() or Gio.ThemedIcon.new("image-missing"))
                    self.apps_by_id[id(app)] = app
                    submenu.append_item(subitem)

                item.set_submenu(submenu)
                self.menu.append_item(item)

        add_icons_to_menu(self.popover)
