import { EditorType, StacksEditor, StacksEditorOptions } from '@microsoft/stacks-editor';
import {
	buttonsMap,
	modeButtonsToDisable,
	EditorModes,
	editorTag,
	editorId,
	images,
	ImageStore,
	Image,
	GitHubAttributes
} from './models/editor-model';
import { debounce } from './utils/debounce';
import { getStyles } from './styles/importStyles';
import { handleDuplicateFileName, imageDefaultPath } from './imageHandlers/imageDefaultPath';
import { ImageUploadOptions } from '@microsoft/stacks-editor/dist/shared/prosemirror-plugins/image-upload';
import { addAccessibilityToButtons } from './accessibility/addAccessibilityToButtons';
import { addContentClass } from './utils/addContentClass';
import { updateImagesMap } from './imageHandlers/updateImageMap';
import { renameFile } from './utils/fileHandlers';
import { DocsImagesPlugin } from './externalPlugins/docs-images';
import { addEventListenerForAddNewImage } from './imageHandlers/imageRewrite';

export class DocsMarkdownEditor extends HTMLElement {
	content: string;
	mode: EditorModes;
	gitHubAttributes: GitHubAttributes = {
		repo: '',
		org: '',
		path: '',
		branch: ''
	};
	images: ImageStore = new Map<string, Image>();
	onToggleClick: EventListener;
	onMenuClick: EventListener;
	toggle: HTMLElement;
	editor: StacksEditor;
	options: StacksEditorOptions = {
		defaultView: 0,
		editorHelpLink: 'https://docs.microsoft.com'
	};
	async connectedCallback(): Promise<void> {
		this.getAttributes();
		await this.load();
	}
	disconnectedCallback(): void {
		this.toggle?.removeEventListener('click', this.onToggleClick);
	}
	/** get all of the attributes that are on
	 *  the docs-markdown-editor web component
	 *  and assign them to class properties.
	 */
	getAttributes() {
		this.mode = (this.getAttribute('mode') as EditorModes) ?? EditorModes.docs;
		this.gitHubAttributes = {
			org: this.getAttribute('org'),
			repo: this.getAttribute('repo'),
			path: this.getAttribute('path'),
			branch: this.getAttribute('branch')
		};
		this.content = this.getAttribute('content') ?? '';

		const that = this;
		this.addEventListener('keyup', _e => {
			that.setAttribute('content', that.editor.content);
		});
	}
	/** Initial creation of StacksEditor
	 *  This handles the initial content, and mode
	 */
	async load() {
		if (!this.editor) {
			this.innerHTML = `<div id="${editorTag}"></div>`;
			this.handleEditorStyles();
			const docsMarkdownEditorElement = document.getElementById(editorTag);
			this.editor = new StacksEditor(
				docsMarkdownEditorElement,
				this.content,
				this.setOptionsBasedOnMode(this.mode)
			);
			await this.setupEditorBasedOnMode(this.mode);
		}
	}
	/** Initial editor load - replace relative urls with github images
	 *  Setup Toggle functions - functions that should run each time
	 *  editor reloads.
	 */
	async setupEditorBasedOnMode(mode: EditorModes) {
		await this.onEditorLoad(mode);
		// Each time we toggle, we need to do the inital rendering work again
		// - add event listeners to image forms
		// - update image src to use data-url, and image form value to use relative url path.
		await this.onEditorToggle(mode);
	}
	/** Initial editor rendering. Work should be done here
	 *  to parse out relative urls and rewrite them to be
	 *  understood by the editor. Things like relative
	 *  urls need to be fetched from github to render the images.
	 */
	async onEditorLoad(mode: EditorModes) {
		switch (mode) {
			case EditorModes.docs:
				//Need to add custom event to listen for when image gets uploaded
				addEventListenerForAddNewImage(this);
				break;
			case EditorModes.renewalAsessments:
				break;
			case EditorModes.qna:
				break;
			default:
				break;
		}
		//Hide buttons based on mode
		this.disableButtonsBasedOnMode(this);
		//class="content" needs to be on the document to get the docs-ui styles
		addContentClass();
	}
	/** Editor toggle re-renders and re-parses the prosemirror
	 *  nodes, so all of the intial docs hackery needs
	 *  to be done on toggle as well. The toggle function
	 *  relies on an array of functions that are run on each
	 *  toggle event. And should handle both CommonMark
	 *  and RichText events.
	 */
	async onEditorToggle(mode: EditorModes) {
		// Functions that should run in all modes
		// Initial rendering functions go here
		const functionsRunOnToggle = [addContentClass, this.disableButtonsBasedOnMode] as ((
			that: any
		) => void)[];
		switch (mode) {
			case EditorModes.docs:
				break;
			case EditorModes.renewalAsessments:
				break;
			case EditorModes.qna:
				break;
			default:
				break;
		}
		this.handleToggle(this, functionsRunOnToggle);
	}
	/** Define which buttons and functionality
	 *  will be loaded into the Editor. These options
	 *  will be set based on the mode passed in.
	 */
	setOptionsBasedOnMode(mode) {
		// These options determine which features are available in editor
		switch (mode) {
			case EditorModes.docs:
				this.options.imageUpload = {
					brandingHtml:
						'Please see our <a class="s-link" href="https://review.docs.microsoft.com/en-us/new-hope/resources/docs-admin-portal/?branch=main">Docs image guidelines</a> for more information.',
					contentPolicyHtml: '',
					wrapImagesInLinks: false,
					handler: this.docsImageHandler
				};
				this.options.parserFeatures = {
					tables: true,
					defaultValidateLink: true,
					redo: true,
					undo: true
				};
				this.options.externalPlugins = [DocsImagesPlugin];
				break;
			case EditorModes.qna:
				break;
			case EditorModes.renewalAsessments:
				this.options.parserFeatures = {
					tables: false,
					redo: true,
					undo: true
				};
			default:
				break;
		}
		return this.options;
	}
	/** Initial styles rendering to set the editor styles on load */
	handleEditorStyles() {
		// Initial rendering of styles
		const style = document.createElement('style');
		style.textContent = getStyles();
		document.getElementsByTagName('docs-markdown-editor')[0].prepend(style);
	}
	/** Track the toggle event
	 *  Each time the editor is toggled, the document is re-parsed
	 *  From CommonMark to RichText.
	 *  Based on which mode is active, we need to
	 *  update the editor to handle certain things. */
	handleToggle(
		that: {
			onMenuClick: EventListener;
			onToggleClick: EventListener;
		},
		toggleFunctions: ((that: any) => void)[]
	) {
		const toggleElement = document.querySelector('[title="Toggle Markdown editing"]');
		if (toggleElement) {
			this.toggle = toggleElement as HTMLElement;
			// The toggleFunctions will run which each time the editor is toggled.
			// It is an array of functions that pass in the context, which update the editor
			// These functions are defined in onEditorToggle
			this.onToggleClick = debounce(toggleFunctions, this, 1);
			this.toggle.addEventListener('click', that.onToggleClick);
		}
	}
	/** hides certain buttons in menu based on EditorMode
	 *  The buttons to disable are define in an array for
	 *  modeButtonsToDisable.
	 *
	 *  Also adds aria-labels to buttons that are in menu
	 */
	disableButtonsBasedOnMode(that: { mode: string }) {
		const buttons: string[] = (modeButtonsToDisable[that.mode] as string).split(',');
		buttons.forEach(btn => {
			const el = document.getElementsByClassName(buttonsMap.get(btn))[0] as HTMLElement;
			if (el) {
				el.remove();
			}
		});
		addAccessibilityToButtons();
	}
	/** Docs conceptual image upload handler
	 *  specifically designed for the Docs Web Editor experience.
	 */
	docsImageHandler: ImageUploadOptions['handler'] = file => {
		const {
			gitHubAttributes: { path },
			editor
		} = this;
		return new Promise(function (resolve) {
			const fileNames: string[] = [];
			let duplicateFileName;
			images.forEach(value => {
				if (value?.file?.name === file.name) {
					duplicateFileName = !!file.name;
				}
				if (value?.file?.name) {
					fileNames.push(value.file.name);
				}
			});
			if (duplicateFileName) {
				file = renameFile(file, handleDuplicateFileName(file.name, fileNames));
			}
			let defaultDirectory = imageDefaultPath(path, file.name);
			const reader = new FileReader();
			reader.readAsDataURL(file);
			reader.addEventListener('load', () => {
				const result = reader.result as string;
				if (images.has(defaultDirectory)) {
					defaultDirectory = handleDuplicateFileName(defaultDirectory, [...images.keys()]);
				}
				const value = {
					file,
					data: result,
					id: null
				};
				updateImagesMap(images, defaultDirectory, value);
				const event = new CustomEvent('image-uploaded', {
					detail: defaultDirectory,
					bubbles: true
				});
				document.querySelector(editorId).dispatchEvent(event);
				if (editor.currentViewType === EditorType.RichText) {
					resolve(result);
				} else {
					resolve(defaultDirectory);
				}
			});
		});
	};
}
customElements.define(editorTag, DocsMarkdownEditor);
