// 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 Gtk from 'gi://Gtk';

import Plugin from '../plugin.js';
import * as URI from '../utils/uri.js';


export const Metadata = {
    label: _('Share'),
    id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.Share',
    description: _('Share files and URLs between devices'),
    incomingCapabilities: ['kdeconnect.share.request'],
    outgoingCapabilities: ['kdeconnect.share.request'],
    actions: {
        share: {
            label: _('Share'),
            icon_name: 'send-to-symbolic',

            parameter_type: null,
            incoming: [],
            outgoing: ['kdeconnect.share.request'],
        },
        shareFile: {
            label: _('Share File'),
            icon_name: 'document-send-symbolic',

            parameter_type: new GLib.VariantType('(sb)'),
            incoming: [],
            outgoing: ['kdeconnect.share.request'],
        },
        shareText: {
            label: _('Share Text'),
            icon_name: 'send-to-symbolic',

            parameter_type: new GLib.VariantType('s'),
            incoming: [],
            outgoing: ['kdeconnect.share.request'],
        },
        shareUri: {
            label: _('Share Link'),
            icon_name: 'send-to-symbolic',

            parameter_type: new GLib.VariantType('s'),
            incoming: [],
            outgoing: ['kdeconnect.share.request'],
        },
    },
};


/**
 * Share Plugin
 * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/share
 *
 * TODO: receiving 'text' TODO: Window with textview & 'Copy to Clipboard..
 *       https://github.com/KDE/kdeconnect-kde/commit/28f11bd5c9a717fb9fbb3f02ddd6cea62021d055
 */
const SharePlugin = GObject.registerClass({
    GTypeName: 'GSConnectSharePlugin',
}, class SharePlugin extends Plugin {

    _init(device) {
        super._init(device, 'share');
    }

    handlePacket(packet) {
        const {filename, text, url} = packet.body;

        // TODO: composite jobs (lastModified, numberOfFiles, totalPayloadSize)
        if (filename !== undefined) {
            debug(`Remote wants to share file "${filename}".`);
            if (this.settings.get_boolean('receive-files'))
                this._handleFile(packet);
            else
                this._refuseFile(packet);
            return;
        }
        if (text === undefined && url === undefined)
            throw new Error('Share request has invalid payload, ignoring.');

        if (this.settings.get_boolean('launch-urls')) {
            let shared_url = url;
            if (url === undefined) {
                const urls = URI.findUrls(text);
                if (urls.length === 1)
                    shared_url = urls[0].url;
            }
            if (shared_url !== undefined) {
                debug(`Launching shared URL "${shared_url}".`);
                return this._handleUri(shared_url);
            }
        }
        const message = text || url;
        debug('Displaying shared message.');
        this._handleText(message);
    }

    _ensureReceiveDirectory() {
        let receiveDir = this.settings.get_string('receive-directory');

        // Ensure a directory is set
        if (receiveDir.length === 0) {
            receiveDir = GLib.get_user_special_dir(
                GLib.UserDirectory.DIRECTORY_DOWNLOAD
            );

            // Fallback to ~/Downloads
            const homeDir = GLib.get_home_dir();

            if (!receiveDir || receiveDir === homeDir)
                receiveDir = GLib.build_filenamev([homeDir, 'Downloads']);

            this.settings.set_string('receive-directory', receiveDir);
        }

        // Ensure the directory exists
        if (!GLib.file_test(receiveDir, GLib.FileTest.IS_DIR))
            GLib.mkdir_with_parents(receiveDir, 448);

        return receiveDir;
    }

    _getFile(filename) {
        const dirpath = this._ensureReceiveDirectory();
        const basepath = GLib.build_filenamev([dirpath, filename]);
        let filepath = basepath;
        let copyNum = 0;

        while (GLib.file_test(filepath, GLib.FileTest.EXISTS))
            filepath = `${basepath} (${++copyNum})`;

        return Gio.File.new_for_path(filepath);
    }

    _refuseFile(packet) {
        try {
            this.device.rejectTransfer(packet);

            this.device.showNotification({
                id: `${Date.now()}`,
                title: _('Transfer Failed'),
                // TRANSLATORS: eg. Google Pixel is not allowed to upload files
                body: _('%s is not allowed to upload files').format(
                    this.device.name
                ),
                icon: new Gio.ThemedIcon({name: 'dialog-error-symbolic'}),
            });
        } catch (e) {
            debug(e, this.device.name);
        }
    }

    async _handleFile(packet) {
        try {
            const file = this._getFile(packet.body.filename);

            // Create the transfer
            const transfer = this.device.createTransfer();

            transfer.addFile(packet, file);

            // Notify that we're about to start the transfer
            this.device.showNotification({
                id: transfer.uuid,
                title: _('Transferring File'),
                // TRANSLATORS: eg. Receiving 'book.pdf' from Google Pixel
                body: _('Receiving “%s” from %s').format(
                    packet.body.filename,
                    this.device.name
                ),
                buttons: [{
                    label: _('Cancel'),
                    action: 'cancelTransfer',
                    parameter: new GLib.Variant('s', transfer.uuid),
                }],
                icon: new Gio.ThemedIcon({name: 'document-save-symbolic'}),
            });

            // We'll show a notification (success or failure)
            let title, body, action, iconName;
            let buttons = [];

            try {
                await transfer.start();

                title = _('Transfer Successful');
                // TRANSLATORS: eg. Received 'book.pdf' from Google Pixel
                body = _('Received “%s” from %s').format(
                    packet.body.filename,
                    this.device.name
                );
                action = {
                    name: 'showPathInFolder',
                    parameter: new GLib.Variant('s', file.get_uri()),
                };
                buttons = [
                    {
                        label: _('Show File Location'),
                        action: 'showPathInFolder',
                        parameter: new GLib.Variant('s', file.get_uri()),
                    },
                    {
                        label: _('Open File'),
                        action: 'openPath',
                        parameter: new GLib.Variant('s', file.get_uri()),
                    },
                ];
                iconName = 'document-save-symbolic';

                const gtk_recent_manager = Gtk.RecentManager.get_default();
                gtk_recent_manager.add_item(file.get_uri());

                if (packet.body.open) {
                    const uri = file.get_uri();
                    Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null);
                }
            } catch (e) {
                debug(e, this.device.name);

                title = _('Transfer Failed');
                // TRANSLATORS: eg. Failed to receive 'book.pdf' from Google Pixel
                body = _('Failed to receive “%s” from %s').format(
                    packet.body.filename,
                    this.device.name
                );
                iconName = 'dialog-warning-symbolic';

                // Clean up the downloaded file on failure
                file.delete_async(GLib.PRIORITY_DEAFAULT, null, null);
            }

            this.device.hideNotification(transfer.uuid);
            this.device.showNotification({
                id: transfer.uuid,
                title: title,
                body: body,
                action: action,
                buttons: buttons,
                icon: new Gio.ThemedIcon({name: iconName}),
            });
        } catch (e) {
            logError(e, this.device.name);
        }
    }

    _handleUri(uri) {
        Gio.AppInfo.launch_default_for_uri_async(uri, null, null, null);
    }

    _handleText(message) {
        const dialog = new Gtk.MessageDialog({
            text: _('Text Shared By %s').format(this.device.name),
            secondary_text: URI.linkify(message),
            secondary_use_markup: true,
            buttons: Gtk.ButtonsType.CLOSE,
        });
        dialog.message_area.get_children()[1].selectable = true;
        dialog.set_keep_above(true);
        dialog.connect('response', (dialog) => dialog.destroy());
        dialog.show();
    }

    /**
     * Open the file chooser dialog for selecting a file or inputing a URI.
     */
    share() {
        const dialog = new FileChooserDialog(this.device);
        dialog.show();
    }

    /**
     * Share local file path or URI
     *
     * @param {string} path - Local file path or URI
     * @param {boolean} open - Whether the file should be opened after transfer
     */
    async shareFile(path, open = false) {
        try {
            let file = null;

            if (path.includes('://'))
                file = Gio.File.new_for_uri(path);
            else
                file = Gio.File.new_for_path(path);

            // Create the transfer
            const transfer = this.device.createTransfer();

            transfer.addFile({
                type: 'kdeconnect.share.request',
                body: {
                    filename: file.get_basename(),
                    open: open,
                },
            }, file);

            // Notify that we're about to start the transfer
            this.device.showNotification({
                id: transfer.uuid,
                title: _('Transferring File'),
                // TRANSLATORS: eg. Sending 'book.pdf' to Google Pixel
                body: _('Sending “%s” to %s').format(
                    file.get_basename(),
                    this.device.name
                ),
                buttons: [{
                    label: _('Cancel'),
                    action: 'cancelTransfer',
                    parameter: new GLib.Variant('s', transfer.uuid),
                }],
                icon: new Gio.ThemedIcon({name: 'document-send-symbolic'}),
            });

            // We'll show a notification (success or failure)
            let title, body, iconName;

            try {
                await transfer.start();

                title = _('Transfer Successful');
                // TRANSLATORS: eg. Sent "book.pdf" to Google Pixel
                body = _('Sent “%s” to %s').format(
                    file.get_basename(),
                    this.device.name
                );
                iconName = 'document-send-symbolic';
            } catch (e) {
                debug(e, this.device.name);

                title = _('Transfer Failed');
                // TRANSLATORS: eg. Failed to send "book.pdf" to Google Pixel
                body = _('Failed to send “%s” to %s').format(
                    file.get_basename(),
                    this.device.name
                );
                iconName = 'dialog-warning-symbolic';
            }

            this.device.hideNotification(transfer.uuid);
            this.device.showNotification({
                id: transfer.uuid,
                title: title,
                body: body,
                icon: new Gio.ThemedIcon({name: iconName}),
            });
        } catch (e) {
            debug(e, this.device.name);
        }
    }

    /**
     * Share a string of text. Remote behaviour is undefined.
     *
     * @param {string} text - A string of unicode text
     */
    shareText(text) {
        this.device.sendPacket({
            type: 'kdeconnect.share.request',
            body: {text: text},
        });
    }

    /**
     * Share a URI. Generally the remote device opens it with the scheme default
     *
     * @param {string} uri - A URI to share
     */
    shareUri(uri) {
        if (GLib.uri_parse_scheme(uri) === 'file') {
            this.shareFile(uri);
            return;
        }

        this.device.sendPacket({
            type: 'kdeconnect.share.request',
            body: {url: uri},
        });
    }
});


/** A simple FileChooserDialog for sharing files */
const FileChooserDialog = GObject.registerClass({
    GTypeName: 'GSConnectShareFileChooserDialog',
}, class FileChooserDialog extends Gtk.FileChooserDialog {

    _init(device) {
        super._init({
            // TRANSLATORS: eg. Send files to Google Pixel
            title: _('Send files to %s').format(device.name),
            select_multiple: true,
            extra_widget: new Gtk.CheckButton({
                // TRANSLATORS: Mark the file to be opened once completed
                label: _('Open when done'),
                visible: true,
            }),
            use_preview_label: false,
        });

        this.device = device;

        // Align checkbox with sidebar
        const box = this.get_content_area().get_children()[0].get_children()[0];
        const paned = box.get_children()[0];
        paned.bind_property(
            'position',
            this.extra_widget,
            'margin-left',
            GObject.BindingFlags.SYNC_CREATE
        );

        // Preview Widget
        this.preview_widget = new Gtk.Image();
        this.preview_widget_active = false;
        this.connect('update-preview', this._onUpdatePreview);

        // URI entry
        this._uriEntry = new Gtk.Entry({
            placeholder_text: 'https://',
            hexpand: true,
            visible: true,
        });
        this._uriEntry.connect('activate', this._sendLink.bind(this));

        // URI/File toggle
        this._uriButton = new Gtk.ToggleButton({
            image: new Gtk.Image({
                icon_name: 'web-browser-symbolic',
                pixel_size: 16,
            }),
            valign: Gtk.Align.CENTER,
            // TRANSLATORS: eg. Send a link to Google Pixel
            tooltip_text: _('Send a link to %s').format(device.name),
            visible: true,
        });
        this._uriButton.connect('toggled', this._onUriButtonToggled.bind(this));

        this.add_button(_('Cancel'), Gtk.ResponseType.CANCEL);
        const sendButton = this.add_button(_('Send'), Gtk.ResponseType.OK);
        sendButton.connect('clicked', this._sendLink.bind(this));

        this.get_header_bar().pack_end(this._uriButton);
        this.set_default_response(Gtk.ResponseType.OK);
    }

    _onUpdatePreview(chooser) {
        try {
            const pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
                chooser.get_preview_filename(),
                chooser.get_scale_factor() * 128,
                -1
            );
            chooser.preview_widget.pixbuf = pixbuf;
            chooser.preview_widget.visible = true;
            chooser.preview_widget_active = true;
        } catch {
            chooser.preview_widget.visible = false;
            chooser.preview_widget_active = false;
        }
    }

    _onUriButtonToggled(button) {
        const header = this.get_header_bar();

        // Show and focus the URL entry
        if (button.active) {
            this.extra_widget.sensitive = false;
            header.set_custom_title(this._uriEntry);
            this._uriEntry.grab_focus();
            this.set_response_sensitive(Gtk.ResponseType.OK, true);

        // Hide the URL entry
        } else {
            header.set_custom_title(null);
            this.set_response_sensitive(
                Gtk.ResponseType.OK,
                this.get_uris().length > 1
            );
            this.extra_widget.sensitive = true;
        }
    }

    _sendLink(widget) {
        if (this._uriButton.active && this._uriEntry.text.length)
            this.response(1);
    }

    vfunc_response(response_id) {
        if (response_id === Gtk.ResponseType.OK) {
            for (const uri of this.get_uris()) {
                const parameter = new GLib.Variant(
                    '(sb)',
                    [uri, this.extra_widget.active]
                );
                this.device.activate_action('shareFile', parameter);
            }
        } else if (response_id === 1) {
            const parameter = new GLib.Variant('s', this._uriEntry.text);
            this.device.activate_action('shareUri', parameter);
        }

        this.destroy();
    }
});

export default SharePlugin;
