import { getWindow } from '../misc/getWindow.js';
import { readBlobText } from './Blob.js';
import { createDataTransfer, getBlobFromDataTransferItem } from './DataTransfer.js';

// Clipboard is not available in jsdom
function _define_property(obj, key, value) {
    if (key in obj) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    } else {
        obj[key] = value;
    }
    return obj;
}
// MDN lists string|Blob|Promise<Blob|string> as possible types in ClipboardItemData
// lib.dom.d.ts lists only Promise<Blob|string>
// https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#syntax
function createClipboardItem(window, ...blobs) {
    const dataMap = Object.fromEntries(blobs.map((b)=>[
            typeof b === 'string' ? 'text/plain' : b.type,
            Promise.resolve(b)
        ]));
    // use real ClipboardItem if available
    /* istanbul ignore if */ if (typeof window.ClipboardItem !== 'undefined') {
        return new window.ClipboardItem(dataMap);
    }
    return new class ClipboardItem {
        get types() {
            return Array.from(Object.keys(this.data));
        }
        async getType(type) {
            const value = await this.data[type];
            if (!value) {
                throw new Error(`${type} is not one of the available MIME types on this item.`);
            }
            return value instanceof window.Blob ? value : new window.Blob([
                value
            ], {
                type
            });
        }
        constructor(d){
            _define_property(this, "data", undefined);
            this.data = d;
        }
    }(dataMap);
}
const ClipboardStubControl = Symbol('Manage ClipboardSub');
function createClipboardStub(window, control) {
    return Object.assign(new class Clipboard extends window.EventTarget {
        async read() {
            return Array.from(this.items);
        }
        async readText() {
            let text = '';
            for (const item of this.items){
                const type = item.types.includes('text/plain') ? 'text/plain' : item.types.find((t)=>t.startsWith('text/'));
                if (type) {
                    text += await item.getType(type).then((b)=>readBlobText(b, window.FileReader));
                }
            }
            return text;
        }
        async write(data) {
            this.items = data;
        }
        async writeText(text) {
            this.items = [
                createClipboardItem(window, text)
            ];
        }
        constructor(...args){
            super(...args), _define_property(this, "items", []);
        }
    }(), {
        [ClipboardStubControl]: control
    });
}
function isClipboardStub(clipboard) {
    return !!(clipboard === null || clipboard === undefined ? undefined : clipboard[ClipboardStubControl]);
}
function attachClipboardStubToView(window) {
    if (isClipboardStub(window.navigator.clipboard)) {
        return window.navigator.clipboard[ClipboardStubControl];
    }
    const realClipboard = Object.getOwnPropertyDescriptor(window.navigator, 'clipboard');
    let stub;
    const control = {
        resetClipboardStub: ()=>{
            stub = createClipboardStub(window, control);
        },
        detachClipboardStub: ()=>{
            /* istanbul ignore if */ if (realClipboard) {
                Object.defineProperty(window.navigator, 'clipboard', realClipboard);
            } else {
                Object.defineProperty(window.navigator, 'clipboard', {
                    value: undefined,
                    configurable: true
                });
            }
        }
    };
    stub = createClipboardStub(window, control);
    Object.defineProperty(window.navigator, 'clipboard', {
        get: ()=>stub,
        configurable: true
    });
    return stub[ClipboardStubControl];
}
function resetClipboardStubOnView(window) {
    if (isClipboardStub(window.navigator.clipboard)) {
        window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
    }
}
function detachClipboardStubFromView(window) {
    if (isClipboardStub(window.navigator.clipboard)) {
        window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
    }
}
async function readDataTransferFromClipboard(document) {
    const window = document.defaultView;
    const clipboard = window === null || window === undefined ? undefined : window.navigator.clipboard;
    const items = clipboard && await clipboard.read();
    if (!items) {
        throw new Error('The Clipboard API is unavailable.');
    }
    const dt = createDataTransfer(window);
    for (const item of items){
        for (const type of item.types){
            dt.setData(type, await item.getType(type).then((b)=>readBlobText(b, window.FileReader)));
        }
    }
    return dt;
}
async function writeDataTransferToClipboard(document, clipboardData) {
    const window = getWindow(document);
    const clipboard = window.navigator.clipboard;
    const items = [];
    for(let i = 0; i < clipboardData.items.length; i++){
        const dtItem = clipboardData.items[i];
        const blob = await getBlobFromDataTransferItem(window, dtItem);
        items.push(createClipboardItem(window, blob));
    }
    const written = clipboard && await clipboard.write(items).then(()=>true, // Can happen with other implementations that e.g. require permissions
    /* istanbul ignore next */ ()=>false);
    if (!written) {
        throw new Error('The Clipboard API is unavailable.');
    }
}
const g = globalThis;
/* istanbul ignore else */ if (typeof g.afterEach === 'function') {
    g.afterEach(()=>resetClipboardStubOnView(globalThis.window));
}
/* istanbul ignore else */ if (typeof g.afterAll === 'function') {
    g.afterAll(()=>detachClipboardStubFromView(globalThis.window));
}

export { attachClipboardStubToView, createClipboardItem, detachClipboardStubFromView, readDataTransferFromClipboard, resetClipboardStubOnView, writeDataTransferToClipboard };
