import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

import {ServiceImplementation} from './dbusService.js';

import {loadInterfaceXML} from './misc/dbusUtils.js';

const NotificationsIface = loadInterfaceXML('org.freedesktop.Notifications');
const NotificationsProxy = Gio.DBusProxy.makeProxyWrapper(NotificationsIface);

Gio._promisify(Gio.DBusConnection.prototype, 'call');

export const NotificationDaemon = class extends ServiceImplementation {
    constructor() {
        super(NotificationsIface, '/org/freedesktop/Notifications');

        this._autoShutdown = false;

        this._activeNotifications = new Map();

        this._proxy = new NotificationsProxy(Gio.DBus.session,
            'org.gnome.Shell',
            '/org/freedesktop/Notifications',
            (proxy, error) => {
                if (error)
                    log(error.message);
            });

        this._proxy.connectSignal('ActivationToken',
            (proxy, sender, params) => {
                const [id] = params;
                this._emitSignal(
                    this._activeNotifications.get(id),
                    'ActivationToken',
                    new GLib.Variant('(us)', params));
            });
        this._proxy.connectSignal('ActionInvoked',
            (proxy, sender, params) => {
                const [id] = params;
                this._emitSignal(
                    this._activeNotifications.get(id),
                    'ActionInvoked',
                    new GLib.Variant('(us)', params));
            });
        this._proxy.connectSignal('NotificationClosed',
            (proxy, sender, params) => {
                const [id] = params;
                this._emitSignal(
                    this._activeNotifications.get(id),
                    'NotificationClosed',
                    new GLib.Variant('(uu)', params));
                this._activeNotifications.delete(id);
            });
    }

    _emitSignal(sender, signalName, params) {
        if (!sender)
            return;
        this._dbusImpl.get_connection()?.emit_signal(
            sender,
            this._dbusImpl.get_object_path(),
            'org.freedesktop.Notifications',
            signalName,
            params);
    }

    _untrackSender(sender) {
        super._untrackSender(sender);

        this._activeNotifications.forEach((value, key) => {
            if (value === sender)
                this._activeNotifications.delete(key);
        });
    }

    _checkNotificationId(invocation, id) {
        if (id === 0)
            return true;

        if (!this._activeNotifications.has(id))
            return true;

        if (this._activeNotifications.get(id) === invocation.get_sender())
            return true;

        const error = new GLib.Error(Gio.DBusError,
            Gio.DBusError.INVALID_ARGS, 'Invalid notification ID');
        this._handleError(invocation, error);
        return false;
    }

    register() {
        Gio.DBus.session.own_name(
            'org.freedesktop.Notifications',
            Gio.BusNameOwnerFlags.REPLACE,
            null, null);
    }

    async NotifyAsync(params, invocation) {
        const sender = invocation.get_sender();
        const pid = await this._getSenderPid(sender);
        const replaceId = params[1];
        const hints = params[6];

        if (!this._checkNotificationId(invocation, replaceId))
            return;

        params[6] = {
            ...hints,
            'x-shell-sender-pid': new GLib.Variant('u', pid),
            'x-shell-sender': new GLib.Variant('s', sender),
        };

        try {
            const [id] = await this._proxy.NotifyAsync(...params);
            this._activeNotifications.set(id, sender);
            invocation.return_value(new GLib.Variant('(u)', [id]));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async CloseNotificationAsync(params, invocation) {
        const [id] = params;
        if (!this._checkNotificationId(invocation, id))
            return;

        try {
            await this._proxy.CloseNotificationAsync(...params);
            invocation.return_value(null);
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async GetCapabilitiesAsync(params, invocation) {
        try {
            const res = await this._proxy.GetCapabilitiesAsync(...params);
            invocation.return_value(new GLib.Variant('(as)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async GetServerInformationAsync(params, invocation) {
        try {
            const res = await this._proxy.GetServerInformationAsync(...params);
            invocation.return_value(new GLib.Variant('(ssss)', res));
        } catch (error) {
            this._handleError(invocation, error);
        }
    }

    async _getSenderPid(sender) {
        const res = await Gio.DBus.session.call(
            'org.freedesktop.DBus',
            '/',
            'org.freedesktop.DBus',
            'GetConnectionUnixProcessID',
            new GLib.Variant('(s)', [sender]),
            new GLib.VariantType('(u)'),
            Gio.DBusCallFlags.NONE,
            -1,
            null);
        const [pid] = res.deepUnpack();
        return pid;
    }
};
