/**
 * Dropzone image paste functions, separated from `dropzone-binding.js` to avoid ES6+ syntax errors on IE11.
 */

// Permitted MIME types when pasting:
DropzoneUtil.supportedPasteTypes = {
	'image/png': {extension: 'png'},
	'image/jpeg': {extension: 'jpg'},
};
// Pasted image base name. A "paste index" a file extension is appended to this e.g. "Image 1.png".
DropzoneUtil.PASTE_NAME = 'Image';
// Error if no image was found in the clipboard on paste.
DropzoneUtil.UNEXPECTED_CLIPBOARD_DATA = 'UNEXPECTED_CLIPBOARD_DATA';

/**
 * Can this browser use the Async Clipboard API?
 * @return {boolean}
 */
DropzoneUtil.asyncPasteSupported = () => {
	return Boolean(navigator && navigator.clipboard && navigator.clipboard.read);
};

/**
 * Check if a dropzone accepts files which can be pasted (images mainly).
 * @param {Dropzone} dz
 * @return {boolean}
 */
DropzoneUtil.acceptsPasteType = (dz) => {
	let dzAcceptedStr = (dz.options.acceptedFiles || '').trim();
	if (!dzAcceptedStr) {
		// No whitelist required: All possible paste types are valid:
		return true;
	}
	let dzAccepted = dzAcceptedStr.split(',');
	// Ensure e.g. "image/*" is still indicated as an accepted type:
	let showPasteMimes = new Set();
	for (let mime of Object.keys(DropzoneUtil.supportedPasteTypes)) {
		let mimeWildcard = mime.substring(0, mime.indexOf('/')) + '/*';
		showPasteMimes.add(mimeWildcard)
		showPasteMimes.add(mime)
	}
	// Check dzAccepted has at-least one of the paste mimes:
	for (let mime of showPasteMimes) {
		if (dzAccepted.includes(mime)) {
			return true;
		}
	}
	return false;
};

/**
 * Handle paste events. Attempts to populate image data as a file.
 * Populates either through an optional ClipboardEvent (supplied if Ctrl-V is pressed) or the Async Clipboard API.
 * @param {Dropzone} dz
 * @param {ClipboardEvent} clipboardEvent
 */
DropzoneUtil.pasteHandler = async (dz, clipboardEvent = null) => {
	
	try {
		// If ClipboardEvent is set, populate using data from that. Else, attempt to retrieve clipboard contents 
		// using Async Clipboard API. 
		if (clipboardEvent) {
			DropzoneUtil._pasteFileFromEvent(dz, clipboardEvent);
		} else {
			await DropzoneUtil._asyncPasteHandler(dz);
		}
		
	} catch (pasteErr) {
		
		// Clear any error messages and wait for the message transition to finish.
		await DropzoneUtil.resetMessage(dz);
		
		let msg = `<b>No image found in clipboard.</b>
		           <br>For simple screenshots, try pressing <b>PrintScreen</b> on your keyboard and paste again.`;
		
		// If the user did not give browser permission on page:
		if (pasteErr instanceof DOMException && pasteErr.name === 'NotAllowedError') {
			msg = '<b>Permission denied on browser.</b> Try <b>Ctrl-V</b> instead.';
		}
		DropzoneUtil.setMessage(dz, msg);	
	}
};

/**
 * Attempt to add clipboard image content to the supplied Dropzone via Async Clipboard API.
 * This will work in any context and does not require a ClipboardEvent.
 * @param {Dropzone} dz
 * @return {Promise<boolean>}
 * @throws {Error} If no valid image data is in clipboard.
 * @throws {DOMException} NotAllowedError if clipboard access is unavailable.
 */
DropzoneUtil._asyncPasteHandler = async (dz) => {
	// Get file Blob from clipboard, or `null`.
	let [blob, mimeType] = await DropzoneUtil._getClipboardImage();
	// Add blob to dropzone:
	DropzoneUtil._addBlobAsFile(dz, blob, mimeType);
};

/**
 * Add file from event data following a "paste" event.
 * @param {Dropzone} dz
 * @param {ClipboardEvent} event
 */
DropzoneUtil._pasteFileFromEvent = (dz, event) => {
	for (let item of event.clipboardData.items) {
		if (item.kind === 'file' && item.type in DropzoneUtil.supportedPasteTypes) {
			let file = item.getAsFile();
			// Extract a  Blob instance containing just file content. Necessary to overwrite read-only `File.name`.
			let blob = file.slice(0, file.size, file.type);
			DropzoneUtil._addBlobAsFile(dz, blob, item.type);
			return;
		}
	}
	throw DropzoneUtil.UNEXPECTED_CLIPBOARD_DATA;
};

/**
 * Add Blob instance for a supported image paste type to this dropzone as a file.
 * @param {Dropzone} dz
 * @param {Blob}     blob
 * @param {string}   mimeType
 * @protected
 */
DropzoneUtil._addBlobAsFile = (dz, blob, mimeType) => {
	// Prepare blob for dropzone:
	let pasteIndex = DropzoneUtil._getPasteIndex(dz); // Include image number e.g. "Image 1.png", "Image 2.png" etc.
	let extension = DropzoneUtil.supportedPasteTypes[mimeType].extension;
	blob.name = `${DropzoneUtil.PASTE_NAME} ${pasteIndex}.${extension}`;
	dz.addFile(blob);
};

/**
 * Get next available paste index (e.g. "Image 1", "Image 2" etc) on a Dropzone instance.
 * @param {Dropzone} dz
 * @return {number}
 * @protected
 */
DropzoneUtil._getPasteIndex = (dz) => {
	if (!Array.isArray(dz.files)) {
		return 1;
	}
	
	let sortedFileNames = dz.files.map(file => {return file.name; }).sort();
	let idx = 1;
	for (let name of sortedFileNames) {
		// Does a file already exist with this name? (ignoring file extension).
		if (!name.startsWith(DropzoneUtil.PASTE_NAME + ' ' + idx)) {
			break;
		}
		idx++;
	}
	return idx;
};

/**
 * Retrieve first image Blob from the clipboard.
 * @return {Promise<(Array>} Index 0: The image Blob. Index 1: The image MIME type.
 * @throws {Error} If no valid image data is in clipboard.
 * @throws {DOMException} NotAllowedError if clipboard access is unavailable.
 * @protected
 */
DropzoneUtil._getClipboardImage = async () => {
	// Attempt to retrieve the first item in the clipboard array:
	let clipboardItems = await navigator.clipboard.read();
	
	// Search the clipboard for an image file:
	for (let item of clipboardItems) {
		for (let mimeType of item.types) {
			if (mimeType in DropzoneUtil.supportedPasteTypes) {
				return [await item.getType(mimeType), mimeType];
			}
		}
	}
	throw DropzoneUtil.UNEXPECTED_CLIPBOARD_DATA;
};