var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Meta from 'gi://Meta';
import St from 'gi://St';
import { Color } from '../common/color.js';
import { ItemType, getImagesPath } from '../common/constants.js';
import { registerClass } from '../common/gjs.js';
import { ClipboardEntry, FileOperation } from '../database/database.js';
import { Keyboard } from './keyboard.js';
Gio._promisify(Gio.File.prototype, 'load_contents_async');
Gio._promisify(Gio.File.prototype, 'replace_contents_async');
Gio._promisify(Gio.MemoryOutputStream.prototype, 'splice_async');
Gio._promisify(Meta.SelectionSource.prototype, 'read_async');
const MimeTypes = {
    Text: ['text/plain;charset=utf-8', 'UTF8_STRING', 'text/plain', 'STRING'],
    Image: ['image/png', 'image/jxl', 'image/webp', 'image/avif', 'image/jpeg'],
    File: ['x-special/gnome-copied-files', 'text/uri-list'],
    Sensitive: ['x-kde-passwordManagerHint'],
};
export const ContentType = {
    Text: 0,
    Image: 1,
    File: 2,
};
function contentChecksum(content) {
    switch (content.type) {
        case ContentType.Text:
            return GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, content.text, content.text.length);
        case ContentType.Image:
            return content.checksum;
        case ContentType.File: {
            const s = content.paths.map((f) => decodeURI(f).substring('file://'.length)).join('\n');
            return GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, s, s.length);
        }
    }
}
let ClipboardManager = class ClipboardManager extends GObject.Object {
    ext;
    tracker;
    selection;
    clipboard;
    keyboard;
    signalId = -1;
    pasteSignalId = -1;
    prevClipboard = null;
    constructor(ext, tracker) {
        super();
        this.ext = ext;
        this.tracker = tracker;
        this.selection = global.display.get_selection();
        this.clipboard = St.Clipboard.get_default();
        this.keyboard = new Keyboard();
        this.signalId = this.selection.connect('owner-changed', this.ownerChanged.bind(this));
    }
    destroy() {
        this.keyboard.destroy();
        if (this.signalId >= 0)
            this.selection.disconnect(this.signalId);
        if (this.pasteSignalId >= 0)
            GLib.source_remove(this.pasteSignalId);
        this.signalId = -1;
        this.pasteSignalId = -1;
    }
    copyContent(content) {
        const checksum = contentChecksum(content);
        if (!checksum)
            return;
        this.prevClipboard = [content.type, checksum];
        // Text
        if (content.type === ContentType.Text) {
            this.clipboard.set_text(St.ClipboardType.CLIPBOARD, content.text);
            if (this.ext.settings.get_boolean('sync-primary')) {
                this.clipboard.set_text(St.ClipboardType.PRIMARY, content.text);
            }
            return;
        }
        // Image
        if (content.type === ContentType.Image) {
            this.clipboard.set_content(St.ClipboardType.CLIPBOARD, content.mimetype, content.data);
            return;
        }
        // File
        if (content.type === ContentType.File) {
            const s = `${FileOperation.Copy}\n${content.paths.join('\n')}`;
            const bytes = new TextEncoder().encode(s);
            this.clipboard.set_content(St.ClipboardType.CLIPBOARD, MimeTypes.File[0], bytes);
            return;
        }
    }
    pasteContent(content) {
        this.copyContent(content);
        if (this.pasteSignalId >= 0)
            GLib.source_remove(this.pasteSignalId);
        this.pasteSignalId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => {
            // https://github.com/Tudmotu/gnome-shell-extension-clipboard-indicator/blob/89c57703641a9d5d15f899f6e780174641911d95/extension.js#L1094
            if (this.keyboard.purpose === Clutter.InputContentPurpose.TERMINAL) {
                this.keyboard.press(Clutter.KEY_Control_L);
                this.keyboard.press(Clutter.KEY_Shift_L);
                this.keyboard.press(Clutter.KEY_Insert);
                this.keyboard.release(Clutter.KEY_Insert);
                this.keyboard.release(Clutter.KEY_Shift_L);
                this.keyboard.release(Clutter.KEY_Control_L);
            }
            else {
                this.keyboard.press(Clutter.KEY_Shift_L);
                this.keyboard.press(Clutter.KEY_Insert);
                this.keyboard.release(Clutter.KEY_Insert);
                this.keyboard.release(Clutter.KEY_Shift_L);
            }
            this.pasteSignalId = -1;
            return GLib.SOURCE_REMOVE;
        });
    }
    copyText(s) {
        this.copyContent({ type: ContentType.Text, text: s });
        this.emit('text', s);
    }
    pasteText(s) {
        this.pasteContent({ type: ContentType.Text, text: s });
    }
    copyPng(data, width, height) {
        const checksum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.MD5, data);
        if (!checksum)
            return;
        this.copyContent({ type: ContentType.Image, mimetype: 'image/png', data, checksum });
        this.emit('image', data, width, height);
    }
    async copyEntry(entry) {
        await this.handleEntry(entry, false);
    }
    async pasteEntry(entry) {
        await this.handleEntry(entry, true);
    }
    async handleEntry(entry, paste) {
        const fn = paste ? this.pasteContent.bind(this) : this.copyContent.bind(this);
        if (this.ext.settings.get_boolean('update-date-on-copy')) {
            entry.datetime = GLib.DateTime.new_now_utc();
        }
        switch (entry.type) {
            case ItemType.Text:
            case ItemType.Code:
            case ItemType.Link:
            case ItemType.Character:
            case ItemType.Color:
                return fn({ type: ContentType.Text, text: entry.content });
            case ItemType.Image:
                try {
                    const image = Gio.File.new_for_uri(entry.content);
                    const [contents, _etag] = await image.load_contents_async(null);
                    const [contentType] = Gio.content_type_guess(image.get_path(), contents);
                    const mimetype = Gio.content_type_get_mime_type(contentType);
                    if (!mimetype)
                        break;
                    const checksum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.MD5, contents);
                    if (!checksum)
                        return;
                    return fn({ type: ContentType.Image, mimetype, data: contents, checksum });
                }
                catch {
                    break;
                }
            case ItemType.File:
            case ItemType.Files: {
                const paths = entry.content.split('\n');
                return fn({ type: ContentType.File, paths, operation: FileOperation.Copy });
            }
        }
    }
    shouldSave(selectionSource) {
        // Mime Type
        const mimeTypes = selectionSource.get_mimetypes();
        if (MimeTypes.Sensitive.some((value) => mimeTypes.includes(value))) {
            return false;
        }
        // WM Class
        const window = global.display.focus_window;
        if (window) {
            const exclusions = this.ext.settings.get_strv('wmclass-exclusions');
            if (exclusions.includes(window.wm_class))
                return false;
        }
        return !this.ext.settings.get_boolean('incognito');
    }
    async ownerChanged(_selection, selectionType, selectionSource) {
        try {
            if (selectionSource === null)
                return;
            if (selectionType !== Meta.SelectionType.SELECTION_CLIPBOARD)
                return;
            const content = await this.getContent(selectionSource);
            if (!content)
                return;
            const checksum = contentChecksum(content);
            if (!checksum)
                return;
            // Check duplicate
            if (this.prevClipboard) {
                const [type, prevChecksum] = this.prevClipboard;
                if (type === content.type && prevChecksum === checksum) {
                    return;
                }
                // Do not update clipboard when clipboard ownership of a copied file is lost
                // i.e. nautilus is closed
                if (type === ContentType.File && content.type === ContentType.Text && prevChecksum === checksum) {
                    return;
                }
            }
            this.prevClipboard = [content.type, checksum];
            // Check if history should be saved after setting the previous clipboard item.
            // This ensures that content copied in incognito mode is not saved to history
            // after copying an item after exiting incognito mode.
            if (!this.shouldSave(selectionSource))
                return;
            const res = await this.convertContent(content);
            if (!res)
                return;
            const [type, text, metadata] = res;
            const entry = await this.tracker.insert(type, text, metadata);
            if (entry) {
                this.emit('clipboard', entry);
            }
        }
        catch (e) {
            this.ext.logger.error(e);
        }
    }
    async getContent(selectionSource) {
        async function getBytes(mimeType) {
            const source = await selectionSource.read_async(mimeType, null);
            const out = Gio.MemoryOutputStream.new_resizable();
            await out.splice_async(source, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_DEFAULT, null);
            return out.steal_as_bytes().toArray();
        }
        const mimeTypes = selectionSource.get_mimetypes();
        // Image
        const imageMimeType = MimeTypes.Image.find((value) => mimeTypes.includes(value));
        if (imageMimeType) {
            const bytes = await getBytes(imageMimeType);
            if (bytes.length > 0) {
                const checksum = GLib.compute_checksum_for_bytes(GLib.ChecksumType.MD5, bytes);
                if (!checksum)
                    return null;
                return { type: ContentType.Image, mimetype: imageMimeType, data: bytes, checksum };
            }
            else {
                return null;
            }
        }
        // File
        const fileMimeType = MimeTypes.File.find((value) => mimeTypes.includes(value));
        if (fileMimeType) {
            const bytes = await getBytes(fileMimeType);
            const text = new TextDecoder().decode(bytes).trim();
            if (text) {
                const files = text
                    .split('\n')
                    .map((f) => f.trim())
                    .filter((f) => f.length !== 0);
                const operation = files[0]?.toLowerCase();
                if (operation === FileOperation.Copy || operation === FileOperation.Cut) {
                    return { type: ContentType.File, paths: files.slice(1), operation };
                }
                else {
                    return { type: ContentType.File, paths: files, operation: FileOperation.Copy };
                }
            }
            else {
                return null;
            }
        }
        // Text
        const textMimeType = MimeTypes.Text.find((value) => mimeTypes.includes(value));
        if (textMimeType) {
            const bytes = await getBytes(textMimeType);
            const text = new TextDecoder().decode(bytes);
            if (text && text.trim()) {
                return { type: ContentType.Text, text };
            }
            else {
                return null;
            }
        }
        return null;
    }
    async convertContent(content) {
        if (content.type === ContentType.Text) {
            const trimmed = content.text.trim();
            // Link
            try {
                if (trimmed.startsWith('http') && GLib.uri_is_valid(trimmed, GLib.UriFlags.NONE)) {
                    return [ItemType.Link, content.text, null];
                }
            }
            catch {
                // Ignore
            }
            // Character
            const iterator = new Intl.Segmenter().segment(trimmed)[Symbol.iterator]();
            const maxCharacters = this.ext.settings.get_child('character-item').get_int('max-characters');
            for (let i = 0; i < maxCharacters; i++)
                iterator.next();
            if (!iterator.next().value) {
                return [ItemType.Character, content.text, null];
            }
            // Color
            if (Color.parse(trimmed)) {
                return [ItemType.Color, content.text, null];
            }
            // Code
            const slice = trimmed.slice(0, 10000);
            const n = Math.max(1, slice.length / 100);
            const highlightResult = this.ext.hljs?.highlightAuto(slice);
            if (highlightResult && highlightResult.language && highlightResult.relevance / n >= 3) {
                const id = highlightResult.language;
                const name = this.ext.hljs?.getLanguage(id)?.name ?? id;
                const metadata = {
                    language: { id, name: id.length < name.length - 3 ? id.charAt(0) + id.slice(1) : name },
                };
                return [ItemType.Code, content.text, metadata];
            }
            // Text
            return [ItemType.Text, content.text, null];
        }
        // Image
        if (content.type === ContentType.Image) {
            try {
                const path = getImagesPath(this.ext);
                if (!path.query_exists(null))
                    path.make_directory_with_parents(null);
                const extension = content.mimetype.split('/')[1];
                const image = path.get_child(`${content.checksum}.${extension}`);
                if (!image.query_exists(null)) {
                    await image.replace_contents_async(content.data, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
                }
                return [ItemType.Image, image.get_uri(), null];
            }
            catch {
                return null;
            }
        }
        // File
        if (content.type === ContentType.File) {
            const metadata = { operation: content.operation };
            if (content.paths.length === 1) {
                return [ItemType.File, content.paths[0], metadata];
            }
            else {
                return [ItemType.Files, content.paths.join('\n'), metadata];
            }
        }
        return null;
    }
};
ClipboardManager = __decorate([
    registerClass({
        Signals: {
            clipboard: {
                param_types: [ClipboardEntry.$gtype],
            },
            text: {
                param_types: [GObject.TYPE_STRING],
            },
            image: {
                param_types: [GObject.TYPE_JSOBJECT, GObject.TYPE_INT, GObject.TYPE_INT],
            },
        },
    })
], ClipboardManager);
export { ClipboardManager };
