// SPDX-FileCopyrightText: 2019 daronion <stefanosdimos.98@gmail.com>
// SPDX-FileCopyrightText: 2019 Florian Müllner <fmuellner@gnome.org>
//
// SPDX-License-Identifier: GPL-2.0-or-later

import Gdk from 'gi://Gdk?version=4.0';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk?version=4.0';
import GdkPixbuf from 'gi://GdkPixbuf';
import WebKit from 'gi://WebKit?version=6.0';

import {setConsoleLogDomain} from 'console';
import {programArgs} from 'system';

Gio._promisify(WebKit.WebView.prototype, 'get_snapshot');
Gio._promisify(WebKit.WebView.prototype, 'evaluate_javascript');

const PREVIEW_WIDTH = 120;
const PREVIEW_HEIGHT = 90;

const PreviewWindow = GObject.registerClass(
class PreviewWindow extends Gtk.Window {
    static [GObject.properties] = {
        'uri': GObject.ParamSpec.string(
            'uri', null, null,
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            null),
    };

    static [GObject.signals] = {
        'snapshot-ready': {},
        'snapshot-failed': {},
    };

    _snapshot = null;

    constructor(params) {
        super(params);

        const settings = new WebKit.Settings({
            hardware_acceleration_policy: WebKit.HardwareAccelerationPolicy.NEVER,
        });

        this._view = new WebKit.WebView({
            network_session: WebKit.NetworkSession.new_ephemeral(),
            settings,
        });
        this.set_child(this._view);

        this._view.bind_property('title',
            this, 'title', GObject.BindingFlags.SYNC_CREATE);

        this._view.connect('authenticate', (view, request) => {
            request.cancel();
            return true;
        });

        this._view.connect('notify::is-loading',
            this._onLoadingChanged.bind(this));
        this._view.connect('load-failed', () => this.emit('snapshot-failed'));
        this._view.load_uri(this.uri);

        GLib.timeout_add_seconds_once(GLib.PRIORITY_DEFAULT, 60, () => {
            this._view.stop_loading();
        });
    }

    vfunc_map() {
        // Hack: Override map so we don't actually show the window
    }

    _onLoadingChanged() {
        if (this._view.is_loading)
            return;

        /* Hopefully wait long enough for a meaningful snapshot,
           see https://bugs.webkit.org/show_bug.cgi?id=164180 */
        GLib.timeout_add_once(GLib.PRIORITY_DEFAULT, 1000, () => {
            this._createSnapshot().catch(console.error);
        });
    }

    async _createSnapshot() {
        const getClipOp = this._getImageClip();
        const snapshotOp = this._view.get_snapshot(
            WebKit.SnapshotRegion.VISIBLE,
            WebKit.SnapshotOptions.NONE,
            null);
        let clip, snapshot;

        try {
            clip = await getClipOp;
            snapshot = await snapshotOp;
        } catch (e) {
            console.warn(`Failed to create snapshot of ${this.uri}`);
            console.debug(e);
            this.emit('snapshot-failed');
            return;
        }

        const pixbuf = Gdk.pixbuf_get_from_texture(snapshot);

        if (clip)
            this._snapshot = this._createClippedPixbuf(pixbuf, clip);
        else
            this._snapshot = pixbuf;

        this.emit('snapshot-ready');
    }

    async _getImageClip() {
        const script = `
            const img = document.images[0];
            document.contentType.startsWith('image')
                ? [img.x, img.y, img.width, img.height]
                : null;
        `;

        let obj = null;

        try {
            obj = await this._view.evaluate_javascript(script, -1, null, null, null);
        } catch (e) {
            console.warn(`Failed to get clip information from ${this.uri}`);
            console.debug(e);
        }

        if (!obj || obj.is_null())
            return null;

        const {scaleFactor} = this;
        const [x, y, width, height] = obj.object_enumerate_properties()
            .map(p => obj.object_get_property(p).to_int32() * scaleFactor);

        if (width === 0 || height === 0)
            throw new Error('Invalid image clip');

        return {x, y, width, height};
    }

    _createClippedPixbuf(source, clip) {
        const {x, y, width, height} = clip;
        return source.new_subpixbuf(x, y, width, height);
    }

    getSnapshot() {
        return this._snapshot;
    }
});

class App extends Gtk.Application {
    static {
        GObject.registerClass(this);
    }

    constructor(url, filename) {
        super({
            application_id: 'org.gnome.Polari.Thumbnailer',
        });

        this._uri = url;
        this._filename = filename;
    }

    vfunc_startup() {
        const logDomain = 'Polari Thumbnailer';
        setConsoleLogDomain(logDomain);
        if (GLib.log_writer_is_journald(2))
            GLib.setenv('G_MESSAGES_DEBUG', logDomain, false);

        super.vfunc_startup();
    }

    vfunc_activate() {
        const window = new PreviewWindow({
            application: this,
            uri: this._uri,
            default_width: 10 * PREVIEW_WIDTH,
            default_height: 10 * PREVIEW_HEIGHT,
        });

        window.present();
        window.connect('snapshot-ready', this._onSnapshotReady.bind(this));
        window.connect('snapshot-failed', this._onSnapshotFailed.bind(this));
    }

    _onSnapshotReady(window) {
        const snapshot = window.getSnapshot();
        const title = window.title || this._uri;
        window.destroy();

        if (!snapshot)
            return;

        const sourceWidth = snapshot.get_width();
        const sourceHeight = snapshot.get_height();
        const ratio = sourceWidth / sourceHeight;

        let targetWidth, targetHeight;
        if (ratio >= PREVIEW_WIDTH / PREVIEW_HEIGHT) {
            targetWidth = Math.min(sourceWidth, PREVIEW_WIDTH);
            targetHeight = targetWidth / ratio;
        } else {
            targetHeight = Math.min(sourceHeight, PREVIEW_HEIGHT);
            targetWidth = targetHeight * ratio;
        }

        const pixbuf = snapshot.scale_simple(
            targetWidth, targetHeight, GdkPixbuf.InterpType.BILINEAR);
        pixbuf.savev(this._filename, 'png', ['tEXt::Title'], [title]);
    }

    _onSnapshotFailed(window) {
        window.destroy();

        const file = Gio.File.new_for_path(this._filename);
        const stream = file.create(0, null);
        stream.close(null);
    }
}

const [url, filename] = programArgs;
const app = new App(url, filename);
try {
    await app.runAsync([]);
} catch (e) {
    console.error(e);
}
