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

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

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


export const Metadata = {
    label: _('Telephony'),
    description: _('Be notified about calls and adjust system volume during ringing/ongoing calls'),
    id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Telephony',
    incomingCapabilities: [
        'kdeconnect.telephony',
    ],
    outgoingCapabilities: [
        'kdeconnect.telephony.request',
        'kdeconnect.telephony.request_mute',
    ],
    actions: {
        muteCall: {
            // TRANSLATORS: Silence the actively ringing call
            label: _('Mute Call'),
            icon_name: 'audio-volume-muted-symbolic',

            parameter_type: null,
            incoming: ['kdeconnect.telephony'],
            outgoing: ['kdeconnect.telephony.request_mute'],
        },
    },
};


/**
 * Telephony Plugin
 * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/telephony
 * https://github.com/KDE/kdeconnect-android/tree/master/src/org/kde/kdeconnect/Plugins/TelephonyPlugin
 */
const TelephonyPlugin = GObject.registerClass({
    GTypeName: 'GSConnectTelephonyPlugin',
}, class TelephonyPlugin extends Plugin {

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

        // Neither of these are crucial for the plugin to work
        this._mpris = Components.acquire('mpris');
        this._mixer = Components.acquire('pulseaudio');
    }

    handlePacket(packet) {
        switch (packet.type) {
            case 'kdeconnect.telephony':
                this._handleEvent(packet);
                break;
        }
    }

    /**
     * Change volume, microphone and media player state in response to an
     * incoming or answered call.
     *
     * @param {string} eventType - 'ringing' or 'talking'
     */
    _setMediaState(eventType) {
        // Mixer Volume
        if (this._mixer !== undefined) {
            switch (this.settings.get_string(`${eventType}-volume`)) {
                case 'restore':
                    this._mixer.restore();
                    break;

                case 'lower':
                    this._mixer.lowerVolume();
                    break;

                case 'mute':
                    this._mixer.muteVolume();
                    break;
            }

            if (eventType === 'talking' && this.settings.get_boolean('talking-microphone'))
                this._mixer.muteMicrophone();
        }

        // Media Playback
        if (this._mpris && this.settings.get_boolean(`${eventType}-pause`))
            this._mpris.pauseAll();
    }

    /**
     * Restore volume, microphone and media player state (if changed), making
     * sure to unpause before raising volume.
     *
     * TODO: there's a possibility we might revert a media/mixer state set for
     *       another device.
     */
    _restoreMediaState() {
        // Media Playback
        if (this._mpris)
            this._mpris.unpauseAll();

        // Mixer Volume
        if (this._mixer)
            this._mixer.restore();
    }

    /**
     * Load a Gdk.Pixbuf from base64 encoded data
     *
     * @param {string} data - Base64 encoded JPEG data
     * @returns {GdkPixbuf.Pixbuf|null} A contact photo
     */
    _getThumbnailPixbuf(data) {
        const loader = new GdkPixbuf.PixbufLoader();

        try {
            data = GLib.base64_decode(data);
            loader.write(data);
            loader.close();
        } catch (e) {
            debug(e, this.device.name);
        }

        return loader.get_pixbuf();
    }

    /**
     * Handle a telephony event (ringing, talking), showing or hiding a
     * notification and possibly adjusting the media/mixer state.
     *
     * @param {Core.Packet} packet - A `kdeconnect.telephony`
     */
    _handleEvent(packet) {
        // Only handle 'ringing' or 'talking' events; leave the notification
        // plugin to handle 'missedCall' since they're often repliable
        if (!['ringing', 'talking'].includes(packet.body.event))
            return;

        // This is the end of a telephony event
        if (packet.body.isCancel)
            this._cancelEvent(packet);
        else
            this._notifyEvent(packet);
    }

    _cancelEvent(packet) {
        // Ensure we have a sender
        // TRANSLATORS: No name or phone number
        let sender = _('Unknown Contact');

        if (packet.body.contactName)
            sender = packet.body.contactName;
        else if (packet.body.phoneNumber)
            sender = packet.body.phoneNumber;

        this.device.hideNotification(`${packet.body.event}|${sender}`);
        this._restoreMediaState();
    }

    _notifyEvent(packet) {
        let body;
        let buttons = [];
        let icon = null;
        let priority = Gio.NotificationPriority.NORMAL;

        // Ensure we have a sender
        // TRANSLATORS: No name or phone number
        let sender = _('Unknown Contact');

        if (packet.body.contactName)
            sender = packet.body.contactName;
        else if (packet.body.phoneNumber)
            sender = packet.body.phoneNumber;

        // If there's a photo, use it as the notification icon
        if (packet.body.phoneThumbnail)
            icon = this._getThumbnailPixbuf(packet.body.phoneThumbnail);

        if (icon === null)
            icon = new Gio.ThemedIcon({name: 'call-start-symbolic'});

        // Notify based based on the event type
        if (packet.body.event === 'ringing') {
            this._setMediaState('ringing');

            // TRANSLATORS: The phone is ringing
            body = _('Incoming call');
            buttons = [{
                action: 'muteCall',
                // TRANSLATORS: Silence the actively ringing call
                label: _('Mute'),
                parameter: null,
            }];
            priority = Gio.NotificationPriority.URGENT;
        }

        if (packet.body.event === 'talking') {
            this.device.hideNotification(`ringing|${sender}`);
            this._setMediaState('talking');

            // TRANSLATORS: A phone call is active
            body = _('Ongoing call');
        }

        this.device.showNotification({
            id: `${packet.body.event}|${sender}`,
            title: sender,
            body: body,
            icon: icon,
            priority: priority,
            buttons: buttons,
        });
    }

    /**
     * Silence an incoming call and restore the previous mixer/media state, if
     * applicable.
     */
    muteCall() {
        this.device.sendPacket({
            type: 'kdeconnect.telephony.request_mute',
            body: {},
        });

        this._restoreMediaState();
    }

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

        if (this._mpris !== undefined)
            this._mpris = Components.release('mpris');

        super.destroy();
    }
});

export default TelephonyPlugin;
