import { Command, findAttributeRange, first } from 'ckeditor5';
import {showWarning} from "./utils";

export default class FlmngrCommand extends Command {

	static flmngr;

	imageExtensions = ['jpeg', 'jpg', 'png', 'bmp', 'svg', 'webp', 'gif'];

	constructor( editor ) {
		super( editor );

		// Remove default document listener to lower its priority.
		this.stopListening( this.editor.model.document, 'change' );

		// Lower this command listener priority to be sure that refresh() will be called after link & image refresh.
		this.listenTo( this.editor.model.document, 'change', () => this.refresh(), { priority: 'low' } );
	}

	refresh() {
		const imageCommand = this.editor.commands.get( 'insertImage' );
		const linkCommand = this.editor.commands.get( 'link' );
		this.isEnabled = !imageCommand || // if there is no image command, the button IS always enabled: we will show a message
			(imageCommand.isEnabled || (!!linkCommand && linkCommand.isEnabled));
	}

	isImage(filepath) {
		let i = filepath.lastIndexOf(".");
		if (i > -1 && i < filepath.length-1) {
			let ext = filepath.substr(i + 1).toLowerCase();
			return ext === 'jpeg' || ext === 'jpg' || ext === 'png' || ext === 'gif' || ext === "bmp" || ext === "svg" || ext === "webp";
		}
		return false;
	}

	// Call a dialog to select local file and upload them ("Upload" action)
	executeUpload() {
		this.execute2(true, null);
	}

	getSelectedElements() {
		const selection = this.editor.model.document.selection;
		const el = selection.getSelectedElement() || first( selection.getSelectedBlocks() );

		let currentUrl = null;
		let elA = null;
		const position = selection.getFirstPosition();
		if ( selection.hasAttribute( 'linkHref' ) ) {
			elA = findAttributeRange(position, 'linkHref', selection.getAttribute('linkHref'), this.editor.model).getItems().next().value.textNode;
			currentUrl = elA.getAttribute("linkHref");
		}

		let elImg = null;
		if (!!el && (el.name === 'imageBlock' || el.name === 'imageInline')) {
			elImg = el;
			currentUrl = elImg.getAttribute("src");
			elA = null;
		}

		return {
			el: el,
			elImg: elImg,
			elA: elA,
			currentUrl: currentUrl
		}
	}

	// Call Flmngr ("Browse" action)
	execute() {
		this.execute2(false, (urls) => {
			if (urls !== null) {
				let selectedElements = this.getSelectedElements();
				this.createOrChange(
					selectedElements.el,
					selectedElements.elImg,
					selectedElements.elA,
					urls
				);
			}
		});
	}

	execute2(
		doUpload, // false = browse
		callback  // will insert files instead if callback === null
	) {

		let selectedElements = this.getSelectedElements();

		const imageCommand = this.editor.commands.get( 'insertImage' );
		if (!imageCommand) {
			let msg = "Please enable CKEditor 5 `Image` plugin in order to use Flmngr file manager";
			if (!!window.Drupal)
				msg += ":\n\nDrupal users must set `Image Upload` -> `Enable image uploads` checkbox on the page of CKEditor 5 text format";
			alert(msg);
			callback(null);
			return;
		}


		if (!FlmngrCommand.flmngr) {
			console.log("File manager is not loaded yet");
			callback(null);
			return;
		}

		if (doUpload) {
			FlmngrCommand.flmngr.selectFiles({
				acceptExtensions: !!selectedElements.elImg ? this.imageExtensions : null,
				isMultiple: false,
				onFinish: (files) => {
					FlmngrCommand.flmngr.upload({
						filesOrLinks: files,
						onFinish: (urls, paths) => {
							callback(urls);
						},
						onFail: (error) => {
							showWarning(this.editor, 'Unable to upload files', true, error, false);
							callback(null);
						},
						onCancel: () => {
							callback(null);
						}
					});
				}
			})
		} else {
			FlmngrCommand.flmngr.pickFiles({
				acceptExtensions: !!selectedElements.elImg ? this.imageExtensions : null,
				isMultiple: false,
				list: selectedElements.currentUrl ? [selectedElements.currentUrl] : null,
				onFinish: (files) => {
					let urls = files.map(f => f.url);
					callback(urls)
				},
				onCancel: () => {
					callback(null);
				}
			});
		}
	}

	isPlainTextSelection(selection) {
		const ranges = selection.getRanges();
		for (const range of ranges) {
			for (const item of range.getItems()) {
				if (item.is('textProxy'))
					continue;
				return false;
			}
		}
		return true;
	}

	createOrChange(el, elImg, elA, urls) {

		// If non-image file is selected, and selection is a plain text, just convert it to a link
		if (!this.isImage(urls[0])) {
			const selection = this.editor.model.document.selection;
			if (!selection.isCollapsed && this.isPlainTextSelection(selection)) {
				editor.model.change(writer => {
					this.editor.commands.get('link').execute(urls[0]);
				});
				return;
			}
		}

		if (!!elImg) {
			this.changeImgSrc(elImg, FlmngrCommand.flmngr.getNoCacheUrl(urls[0]));
		} else if (!!elA) {
			this.changeAHref(elA, urls[0]);
		} else {
			// Create new IMG and A elements
			let urlsImages = [];
			let urlsFiles = [];
			for (let url of urls) {
				if (this.isImage(url))
					urlsImages.push(FlmngrCommand.flmngr.getNoCacheUrl(url));
				else
					urlsFiles.push(url);
			}

			for (const url of urlsFiles)
				this.createNewA(url);

			for (const url of urlsImages)
				this.createNewImg(url);
		}
	}

	createNewImg(url) {
		this.editor.model.change( writer => {

			const imageCommand = this.editor.commands.get( 'insertImage' );

			// Check if inserting an image is actually possible - it might be possible to only insert a link.
			if ( !imageCommand.isEnabled ) {
				showWarning(this.editor, 'Inserting image failed', true, 'Could not insert image at the current position.', true);
				return;
			}

			this.editor.execute( 'insertImage', { source: [url] } );

		} );
	};

	createNewA(url) {
		this.editor.model.change( writer => {
			const insertPosition = this.editor.model.document.selection.getFirstPosition();

			const i = url.lastIndexOf("/");
			const filename = url.substr(i + 1);
			const title = "Download " + filename;

			writer.insertText( title, { linkHref: url }, insertPosition );
		} );
	};

	changeImgSrc(el, url) {

		this.editor.model.change( writer => {
			writer.setAttribute("src", url, el);
			writer.removeAttribute("srcset", el);
			writer.removeAttribute("sizes", el);

			/*
			In Drupal when using existing content with existing image,
			CKEditor 5 sets also a custom HTML attribute "src", so
			to change an image URL we need not only to set an
			attribute "src" of a model of CKEditor 5,
			but also and "src" attribute of HTML.
			Probably this is a bug or a misconfiguration of
			CKEditor 5 in Drupal 9/10.
			 */
			let attr = el.getAttribute("htmlAttributes");
			if (attr) {
				delete attr.attributes.src;
				delete attr.attributes.srcset;
				delete attr.attributes.sizes;
				writer.setAttribute("htmlAttributes", attr, el);
			}
		});

	};

	changeAHref(el, url) {
		this.editor.model.change( writer => {
			writer.setAttribute( 'linkHref', url, el );
			// TODO: probably change text
		});
	};

	isImage(filepath) {
		let i = filepath.lastIndexOf(".");
		if (i > -1 && i < filepath.length-1) {
			let ext = filepath.substr(i + 1).toLowerCase();
			if (this.imageExtensions.indexOf(ext) > -1)
				return true;
		}
		return false;
	}
}
