// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
//
// SPDX-License-Identifier: GPL-2.0-or-later

import Gdk from 'gi://Gdk';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';

import * as Components from '../components/index.js';
import Plugin from '../plugin.js';


export const Metadata = {
    label: _('Find My Phone'),
    description: _('Ring your paired device'),
    id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.FindMyPhone',
    incomingCapabilities: ['kdeconnect.findmyphone.request'],
    outgoingCapabilities: ['kdeconnect.findmyphone.request'],
    actions: {
        ring: {
            label: _('Ring'),
            icon_name: 'phonelink-ring-symbolic',

            parameter_type: null,
            incoming: [],
            outgoing: ['kdeconnect.findmyphone.request'],
        },
    },
};


/**
 * FindMyPhone Plugin
 * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/findmyphone
 */
const FindMyPhonePlugin = GObject.registerClass({
    GTypeName: 'GSConnectFindMyPhonePlugin',
}, class FindMyPhonePlugin extends Plugin {

    _init(device) {
        super._init(device, 'findmyphone');

        this._dialog = null;
        this._player = Components.acquire('sound');
        this._mixer = Components.acquire('pulseaudio');
    }

    handlePacket(packet) {
        switch (packet.type) {
            case 'kdeconnect.findmyphone.request':
                this._handleRequest();
                break;
        }
    }

    /**
     * Handle an incoming location request.
     */
    _handleRequest() {
        try {
            // If this is a second request, stop announcing and return
            if (this._dialog !== null) {
                this._dialog.response(Gtk.ResponseType.DELETE_EVENT);
                return;
            }

            this._dialog = new Dialog({
                device: this.device,
                plugin: this,
            });

            this._dialog.connect('response', () => {
                this._dialog = null;
            });
        } catch (e) {
            this._cancelRequest();
            logError(e, this.device.name);
        }
    }

    /**
     * Cancel any ongoing ringing and destroy the dialog.
     */
    _cancelRequest() {
        if (this._dialog !== null)
            this._dialog.response(Gtk.ResponseType.DELETE_EVENT);
    }

    /**
     * Request that the remote device announce it's location
     */
    ring() {
        this.device.sendPacket({
            type: 'kdeconnect.findmyphone.request',
            body: {},
        });
    }

    destroy() {
        this._cancelRequest();

        if (this._mixer !== undefined)
            this._mixer = Components.release('pulseaudio');

        if (this._player !== undefined)
            this._player = Components.release('sound');

        super.destroy();
    }
});


/*
 * Used to ensure 'audible-bell' is enabled for fallback
 */
const _WM_SETTINGS = new Gio.Settings({
    schema_id: 'org.gnome.desktop.wm.preferences',
    path: '/org/gnome/desktop/wm/preferences/',
});


/**
 * A custom GtkMessageDialog for alerting of incoming requests
 */
const Dialog = GObject.registerClass({
    GTypeName: 'GSConnectFindMyPhoneDialog',
    Properties: {
        'device': GObject.ParamSpec.object(
            'device',
            'Device',
            'The device associated with this window',
            GObject.ParamFlags.READWRITE,
            GObject.Object
        ),
        'plugin': GObject.ParamSpec.object(
            'plugin',
            'Plugin',
            'The plugin providing messages',
            GObject.ParamFlags.READWRITE,
            GObject.Object
        ),
    },
}, class Dialog extends Gtk.MessageDialog {
    _init(params) {
        super._init({
            buttons: Gtk.ButtonsType.CLOSE,
            device: params.device,
            image: new Gtk.Image({
                icon_name: 'phonelink-ring-symbolic',
                pixel_size: 512,
                halign: Gtk.Align.CENTER,
                hexpand: true,
                valign: Gtk.Align.CENTER,
                vexpand: true,
                visible: true,
            }),
            plugin: params.plugin,
            urgency_hint: true,
        });

        this.set_keep_above(true);
        this.maximize();
        this.message_area.destroy();

        // If an output stream is available start fading the volume up
        if (this.plugin._mixer && this.plugin._mixer.output) {
            this._stream = this.plugin._mixer.output;

            this._previousMuted = this._stream.muted;
            this._previousVolume = this._stream.volume;

            this._stream.muted = false;
            this._stream.fade(0.85, 15);

        // Otherwise ensure audible-bell is enabled
        } else {
            this._previousBell = _WM_SETTINGS.get_boolean('audible-bell');
            _WM_SETTINGS.set_boolean('audible-bell', true);
        }

        // Start the alarm
        if (this.plugin._player !== undefined)
            this.plugin._player.loopSound('phone-incoming-call', this.cancellable);

        // Show the dialog
        this.show_all();
    }

    vfunc_key_press_event(event) {
        this.response(Gtk.ResponseType.DELETE_EVENT);

        return Gdk.EVENT_STOP;
    }

    vfunc_motion_notify_event(event) {
        this.response(Gtk.ResponseType.DELETE_EVENT);

        return Gdk.EVENT_STOP;
    }

    vfunc_response(response_id) {
        // Stop the alarm
        this.cancellable.cancel();

        // Restore the mixer level
        if (this._stream) {
            this._stream.muted = this._previousMuted;
            this._stream.fade(this._previousVolume);

        // Restore the audible-bell
        } else {
            _WM_SETTINGS.set_boolean('audible-bell', this._previousBell);
        }

        this.destroy();
    }

    get cancellable() {
        if (this._cancellable === undefined)
            this._cancellable = new Gio.Cancellable();

        return this._cancellable;
    }

    get device() {
        if (this._device === undefined)
            this._device = null;

        return this._device;
    }

    set device(device) {
        this._device = device;
    }

    get plugin() {
        if (this._plugin === undefined)
            this._plugin = null;

        return this._plugin;
    }

    set plugin(plugin) {
        this._plugin = plugin;
    }
});

export default FindMyPhonePlugin;
