import { EditorType, StacksEditor, StacksEditorOptions } from '@microsoft/stacks-editor';
import { Node } from 'prosemirror-model';
import { editorTag, ImageStore, NodeWithPosition, Image, images } from '../models/editor-model';
import { updateImagesMap } from './updateImageMap';
import { runWhenReadyWithRetry } from '../utils/runWhenReady';

/** Iterate through all of the image map keys
 *  and check if there are any keys they dont
 *  exist in the document/image nodes from prosemirror.
 *  If there are any orphans, remove them from the image map.
 */
export function removeImageMapKeysThatDontExistInImageNodes(
	images: ImageStore,
	imageNodes: NodeWithPosition[],
	that: {
		editor: StacksEditor;
		images: ImageStore;
		options: StacksEditorOptions;
	}
) {
	const imageKeys = [...images.keys()];
	const imageNodeKeys: string[] = [];
	imageNodes.forEach((x: NodeWithPosition) => {
		const src = x.node.attrs.src as string;
		imageNodeKeys.push(src);
	});
	const keysNotMatchingSrc = imageKeys.filter(image => {
		return !imageNodeKeys.includes(image);
	});
	keysNotMatchingSrc.forEach(key => {
		that.images.delete(key);
	});
}

/** Generally used for newly uploaded images.
 *  Replace the uploaded file data-url with the
 *  default file directory value. This function uses
 *  runWhenReady beacuase the file uploading takes an
 *  unknown amount of time, so once the image element
 *  exists, do the work of rewriting the image on the
 *  form, and adding the event handler for form submission.  */
export function replaceImageDataUrlWithDirectoryPath(
	that: {
		editor: StacksEditor;
	},
	key: string
) {
	const image = images.get(key);
	runWhenReadyWithRetry(
		() => {
			return !!document.querySelector(`input[id='${image.id}']`);
		},
		() => {
			rewriteImageOnForm(key, image.id);
			addFormEventListenerToImage(that, key, image);
		}
	);
}

/** Find the element on the page by id and update the input value
 *  Generally the id here comes from the image map (ie images.get(key).id)
 */
export function rewriteImageOnForm(key: string, id: string) {
	const inputElement = document.querySelector(`input[id='${id}']`) as HTMLInputElement;
	if (inputElement) {
		inputElement.value = key;
	}
}

/** Iterate through all the document nodes and get the images
 *  The return object will contain the position of the image node
 *  to update it.
 */
export function recurseNodesToFindImages(doc: Node, imageNodes: NodeWithPosition[] = []) {
	doc.descendants((node: Node, pos: number, parent: Node) => {
		if (node.type.name === 'image') {
			imageNodes.push({ node, pos, parent });
		}
	});
	return imageNodes;
}

/** get the image form id by trying to query the document
 *  for a img[src=${src}]. If found it will return the form id for
 *  the image.
 */
export function getImageFormId(src: string) {
	const imageElement = document.querySelector(`img[src="${src}"]`);
	if (imageElement) {
		const inputElement = (imageElement.nextSibling as HTMLElement).querySelector(
			'input[name="src"]'
		) as HTMLInputElement;
		return inputElement.id;
	}
	return '';
}

/** Update the editor nodes with a new image src. You can get the
 *  the nodeWithPosition from the function recurseNodesToFindImages
 */
function setImageSrc(nodeWithPosition: NodeWithPosition, editor: StacksEditor, src: string) {
	const view = editor.editorView;
	view.dispatch(
		view.state.tr.setNodeMarkup(nodeWithPosition.pos, null, {
			src,
			alt: nodeWithPosition.node.attrs.alt as string,
			title: nodeWithPosition.node.attrs.title as string
		})
	);
}

/** Used to add event listener for newley uploaded images
 *  Function will replace the image data-url with the
 *  default directory generated during image upload.
 */
export function addEventListenerForAddNewImage(that: { editor: StacksEditor }): void {
	if (that.editor.currentViewType === EditorType.RichText) {
		const editorElement = document.querySelector(editorTag);
		editorElement.addEventListener('image-uploaded', (e: CustomEvent) => {
			const key = e.detail as string;
			replaceImageDataUrlWithDirectoryPath(that, key);
		});
	}
}

/** Adds form event listener for images. So calling this
 *  function will add an event listener to the image form,
 *  but after the form is submitted, prosemirror
 *  replaces the node. So the original event listener disapears.
 *  Luckly this function will recusively add the event listener
 *  on submit event when the original element disapears.
 */
export function addFormEventListenerToImage(
	that: {
		editor: StacksEditor;
	},
	oldKey: string,
	value: Image
) {
	const {
		editor
	}: {
		editor: StacksEditor;
	} = that;
	const inputElement = document.querySelector(`input[id='${value.id}']`) as HTMLInputElement;
	if (!inputElement) {
		return;
	}
	const formElement = inputElement.closest('form') as HTMLFormElement;
	formElement.addEventListener('submit', (event: Event) => {
		if (!event) {
			return;
		}
		const inputElement = (event.currentTarget as HTMLElement).querySelector(
			'input[id^="img-src"]'
		) as HTMLInputElement;
		if (!inputElement) {
			return;
		}
		let newValue: { file: File; data: string; formId: string; src: string };
		const newKey = inputElement.value?.toLowerCase();
		updateImagesMap(images, newKey, value, oldKey);
		const imageNodes = recurseNodesToFindImages(that.editor.document);
		images.forEach((updatedValue: Image) => {
			imageNodes.forEach((x: NodeWithPosition) => {
				const src = x.node.attrs.src as string;
				if (src === newKey) {
					const { file, data } = updatedValue;
					setImageSrc(x, editor, data);
					const formId = getImageFormId(data);
					newValue = { formId, file, data, src };
				}
			});
		});
		const { file, formId, data, src } = newValue;
		if (formId && data && src) {
			updateImagesMap(images, src, {
				file: file ?? null,
				data,
				id: formId
			});
			rewriteImageOnForm(src, formId);
			addFormEventListenerToImage(that, oldKey, {
				file: newValue.file,
				data: newValue.data,
				id: newValue.formId
			});
		}
	});
}
