"""
D-Bus notifier 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/>.
"""

from __future__ import annotations

import panorama_panel
import locale
from pathlib import Path
from pydbus import SessionBus

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

from gi.repository import Gtk, Gdk, GLib


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

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


custom_css = """
.notification-button {
    padding: 0;
}

.panorama-panel-notifier-summary {
    font-weight: 600;
}
"""

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


class NotificationsService:
    """
    <node>
      <interface name="org.freedesktop.Notifications">
        <method name="GetCapabilities">
          <arg type="as" name="caps" direction="out"/>
        </method>
        <method name="GetServerInformation">
          <arg type="s" name="name" direction="out"/>
          <arg type="s" name="vendor" direction="out"/>
          <arg type="s" name="version" direction="out"/>
          <arg type="s" name="spec_version" direction="out"/>
        </method>
        <method name="Notify">
          <arg type="s" name="app_name" direction="in"/>
          <arg type="u" name="replaces_id" direction="in"/>
          <arg type="s" name="app_icon" direction="in"/>
          <arg type="s" name="summary" direction="in"/>
          <arg type="s" name="body" direction="in"/>
          <arg type="as" name="actions" direction="in"/>
          <arg type="a{sv}" name="hints" direction="in"/>
          <arg type="i" name="expire_timeout" direction="in"/>
          <arg type="u" name="id" direction="out"/>
        </method>
      </interface>
    </node>
    """

    def __init__(self, applet: NotifierApplet):
        self._next_id = 1
        self.applet = applet

    def GetCapabilities(self):
        return ["body"]

    def GetServerInformation(self):
        # name, vendor, version, spec_version
        return "panorama-panel", "panorama", "0.1", "1.2"

    def Notify(self, *args):
        notification_id = self._next_id
        self._next_id += 1
        self.applet.push_notification(*args)
        return notification_id


class BriefNotification(Gtk.Box):
    def __init__(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
        if not app_icon:
            app_icon = "preferences-desktop-notifications"
        self.append(Gtk.Image.new_from_icon_name(app_icon))
        self.append(Gtk.Label.new(summary))
        self.set_halign(Gtk.Align.CENTER)


class DetailedNotification(Gtk.ListBoxRow):
    def __init__(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout):
        Gtk.ListBoxRow.__init__(self)
        if not app_icon:
            app_icon = "preferences-desktop-notifications"

        self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self.set_child(self.box)

        self.icon = Gtk.Image.new_from_icon_name(app_icon)
        self.icon.set_icon_size(Gtk.IconSize.LARGE)
        self.box.append(self.icon)

        self.text_area = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)

        self.summary = Gtk.Label.new(summary)
        self.summary.set_halign(Gtk.Align.START)
        self.summary.set_xalign(0)
        self.summary.add_css_class("panorama-panel-notifier-summary")
        self.text_area.append(self.summary)

        self.body = Gtk.Label.new(body)
        self.body.set_halign(Gtk.Align.START)
        self.body.set_xalign(0)
        self.summary.add_css_class("panorama-panel-notifier-body")
        self.text_area.append(self.body)

        # TODO: add actions

        self.box.append(self.text_area)


class NotifierApplet(panorama_panel.Applet):
    name = _("Notification centre")
    description = _("Get desktop notifications")

    def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None):
        super().__init__(orientation=orientation, config=config)
        if config is None:
            config = {}

        self.button = Gtk.MenuButton()
        self.button.set_has_frame(False)
        self.button.add_css_class("notification-button")
        self.stack = Gtk.Stack()
        self.button.set_child(self.stack)
        self.notification_indicator = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
        self.notification_indicator.append(Gtk.Image.new_from_icon_name("emblem-important"))
        self.notification_indicator.set_halign(Gtk.Align.CENTER)
        self.notification_count_label = Gtk.Label.new("0")
        self.notification_indicator.append(self.notification_count_label)
        self.stack.add_child(self.notification_indicator)
        self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN)
        self.stack.set_transition_duration(500)
        self.popover = Gtk.Popover()
        self.notification_list = Gtk.ListBox(selection_mode=Gtk.SelectionMode.NONE)
        self.popover.set_child(self.notification_list)
        self.button.set_popover(self.popover)
        # TODO: support the other parameters; add a popover with a history
        self.append(self.button)

        self.service = NotificationsService(self)
        bus = SessionBus()
        self.publishing = bus.publish("org.freedesktop.Notifications", self.service)

    def push_notification(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout):
        new_child = BriefNotification(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout)
        self.stack.add_child(new_child)
        self.stack.set_visible_child(new_child)
        def remove_notification():
            def remove_child():
                self.stack.remove(new_child)

            if self.stack.get_visible_child() == new_child:
                self.stack.set_visible_child(self.stack.get_visible_child().get_prev_sibling())
                GLib.timeout_add(self.stack.get_transition_duration(), remove_child)
            else:
                # TODO: split the time
                def readd_timeout(*args):
                    if new_child == self.stack.get_visible_child():
                        GLib.timeout_add(self.stack.get_transition_duration(), remove_notification)
                self.stack.connect("notify::visible-child", readd_timeout)
            return False
        if expire_timeout <= 0:
            expire_timeout = 2500
        GLib.timeout_add(expire_timeout, remove_notification)

        self.notification_list.append(DetailedNotification(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout))
        # TODO: notify that it was closed

    def get_config(self):
        return {}

    def shutdown(self):
        self.publishing.unpublish()
