/*!
 * Jodit Editor (https://xdsoft.net/jodit/)
 * Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
 * For GPL see LICENSE-GPL.txt in the project root for license information.
 * For MIT see LICENSE-MIT.txt in the project root for license information.
 * For commercial licenses see https://xdsoft.net/jodit/commercial/
 * Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
 */

import { IJodit } from '../types/jodit';

import {
	IDictionary,
	IFileBrowser,
	IFileBrowserCallBackData,
	IRGB,
	IUploader,
	IUploaderData
} from '../types/';

import { Dom } from './Dom';
import {
	$$,
	each,
	hexToRgb,
	isPlainObject,
	normalizeColor,
	val,
	hasBrowserColorPicker
} from './helpers/';
import { ToolbarIcon } from './toolbar/icon';

export namespace Widget {
	/**
	 * Build color picker
	 *
	 * @param {Jodit} editor
	 * @param {function} callback Callback 'function (color) {}'
	 * @param {string} [coldColor] Color value ex. #fff or rgb(123, 123, 123) or rgba(123, 123, 123, 1)
	 * @example
	 * ```javascript
	 * $tabs = TabsWidget(editor, {
	 *    'Text' : ColorPickerWidget(editor, function (color) {
	 *         box.style.color = color;
	 *     }, box.style.color),
	 *     'Background' : ColorPickerWidget(editor, function (color) {
	 *         box.style.backgroundColor = color;
	 *     }, box.style.backgroundColor),
	 * });
	 * ```
	 */
	export const ColorPickerWidget = (
		editor: IJodit,
		callback: (newColor: string) => void,
		coldColor: string
	): HTMLDivElement => {
		const valueHex = normalizeColor(coldColor),
			form: HTMLDivElement = editor.create.div('jodit_colorpicker'),
			iconEye: string = editor.options.textIcons
				? ''
				: ToolbarIcon.getIcon('eye'),
			iconEraser: string = editor.options.textIcons
				? `<span>${editor.i18n('eraser')}</span>`
				: ToolbarIcon.getIcon('eraser'),
			iconPalette: string = editor.options.textIcons
				? `<span>${editor.i18n('palette')}</span>`
				: ToolbarIcon.getIcon('palette'),
			setColor = (target: HTMLElement, color: string) => {
				target.innerHTML = ToolbarIcon.getIcon('eye');
				target.classList.add('active');

				const colorRGB: IRGB | null = hexToRgb(color);
				if (colorRGB) {
					(target.firstChild as HTMLElement).style.fill =
						'rgb(' +
						(255 - colorRGB.r) +
						',' +
						(255 - colorRGB.g) +
						',' +
						(255 - colorRGB.b) +
						')';
				}
			},
			eachColor = (colors: string[] | IDictionary<string[]>) => {
				const stack: string[] = [];
				if (isPlainObject(colors)) {
					Object.keys(colors).forEach(key => {
						stack.push(
							'<div class="jodit_colorpicker_group jodit_colorpicker_group-' +
								key +
								'">'
						);
						stack.push(eachColor((colors as any)[key]));
						stack.push('</div>');
					});
				} else if (Array.isArray(colors)) {
					colors.forEach(color => {
						stack.push(
							'<a ' +
								(valueHex === color ? ' class="active" ' : '') +
								' title="' +
								color +
								'" style="background-color:' +
								color +
								'" data-color="' +
								color +
								'" href="javascript:void(0)">' +
								(valueHex === color ? iconEye : '') +
								'</a>'
						);
					});
				}
				return stack.join('');
			};

		form.appendChild(
			editor.create.fromHTML(
				'<div>' + eachColor(editor.options.colors) + '</div>'
			)
		);

		form.appendChild(
			editor.create.fromHTML(
				'<a ' +
					(editor.options.textIcons
						? 'class="jodit_text_icon"'
						: '') +
					' data-color="" href="javascript:void(0)">' +
					iconEraser +
					'</a>'
			)
		);

		if (editor.options.showBrowserColorPicker && hasBrowserColorPicker()) {
			form.appendChild(
				editor.create.fromHTML(
					'<span>' +
						'<em ' +
						(editor.options.textIcons
							? 'class="jodit_text_icon"'
							: '') +
						'>' +
						iconPalette +
						'</em>' +
						'<input type="color" value=""/>' +
						'</span>'
				)
			);

			editor.events.on(form, 'change', (e: MouseEvent) => {
				e.stopPropagation();

				let target: HTMLInputElement = e.target as HTMLInputElement;

				if (
					!target ||
					!target.tagName ||
					target.tagName.toUpperCase() !== 'INPUT'
				) {
					return;
				}

				const color: string = target.value || '';

				if (color) {
					setColor(target, color);
				}

				if (callback && typeof callback === 'function') {
					callback(color);
				}

				e.preventDefault();
			});
		}

		editor.events.on(form, 'mousedown touchend', (e: MouseEvent) => {
			e.stopPropagation();

			let target: HTMLElement = e.target as HTMLElement;

			if (
				(!target ||
					!target.tagName ||
					target.tagName.toUpperCase() === 'SVG' ||
					target.tagName.toUpperCase() === 'PATH') &&
				target.parentNode
			) {
				target = Dom.closest(
					target.parentNode,
					'A',
					editor.editor
				) as HTMLElement;
			}
			if (target.tagName.toUpperCase() !== 'A') {
				return;
			}

			const active: HTMLElement | null = form.querySelector('a.active');
			if (active) {
				active.classList.remove('active');
				active.innerHTML = '';
			}

			const color: string = target.getAttribute('data-color') || '';

			if (color) {
				setColor(target, color);
			}

			if (callback && typeof callback === 'function') {
				callback(color);
			}

			e.preventDefault();
		});

		return form;
	};

	/**
	 * Build tabs system
	 *
	 * @param {Jodit} editor
	 * @param {object} tabs PlainObject where 'key' will be tab's Title and `value` is tab's content
	 * @param {object} state You can use for this param any HTML element for remembering active tab
	 * @param {string} state.activeTab
	 *
	 * @example
	 * ```javascript
	 * let tabs = widget.create('Tabs', {
	 *    'Images': '<div>Images</div>',
	 *    'Title 2': Jodit.modules.Helpers.dom('<div>Some content</div>'),
	 *    'Color Picker': ColorPickerWidget(editor, function (color) {
	 *         box.style.color = color;
	 *     }, box.style.color),
	 * });
	 * ```
	 */
	export const TabsWidget = (
		editor: IJodit,
		tabs: IDictionary<(() => void) | HTMLElement>,
		state?: { __activeTab: string }
	): HTMLDivElement => {
		const box: HTMLDivElement = editor.create.div('jodit_tabs'),
			tabBox: HTMLDivElement = editor.create.div('jodit_tabs_wrapper'),
			buttons: HTMLDivElement = editor.create.div('jodit_tabs_buttons'),
			nameToTab: IDictionary<{
				button: HTMLElement;
				tab: HTMLElement;
			}> = {};

		let
			firstTab: string = '',
			tabcount: number = 0;

		box.appendChild(buttons);
		box.appendChild(tabBox);

		each<(() => void) | HTMLElement>(tabs, (name: string, tabOptions) => {
			const
				tab = editor.create.div('jodit_tab'),
				button = editor.create.element('a', {
					href: 'javascript:void(0);'
				});

			if (!firstTab) {
				firstTab = name.toString();
			}

			button.innerHTML = /<svg/.test(name.toString()) ? name : editor.i18n(name.toString());
			buttons.appendChild(button);

			if (typeof tabOptions !== 'function') {
				tab.appendChild(tabOptions);
			} else {
				tab.appendChild(editor.create.div('jodit_tab_empty'));
			}

			tabBox.appendChild(tab);

			editor.events.on(button, 'mousedown touchend', (e: MouseEvent) => {
				$$('a', buttons).forEach(a => {
					a.classList.remove('active');
				});
				$$('.jodit_tab', tabBox).forEach(a => {
					a.classList.remove('active');
				});

				button.classList.add('active');
				tab.classList.add('active');

				if (typeof tabOptions === 'function') {
					tabOptions.call(editor);
				}

				e.stopPropagation();

				if (state) {
					state.__activeTab = name.toString();
				}

				return false;
			});

			nameToTab[name] = {
				button,
				tab
			};

			tabcount += 1;
		});

		if (!tabcount) {
			return box;
		}

		$$('a', buttons).forEach(a => {
			a.style.width = (100 / tabcount).toFixed(10) + '%';
		});

		if (!state || !state.__activeTab || !nameToTab[state.__activeTab]) {
			nameToTab[firstTab].button.classList.add('active');
			nameToTab[firstTab].tab.classList.add('active');
		} else {
			nameToTab[state.__activeTab].button.classList.add('active');
			nameToTab[state.__activeTab].tab.classList.add('active');
		}

		return box;
	};

	/**
	 * Generate 3 tabs
	 * upload - Use Drag and Drop
	 * url - By specifying the image url
	 * filebrowser - After opening the file browser . In the absence of one of the parameters will be less tabs
	 *
	 * @params {Object} callbacks Object with keys `url`, `upload` and `filebrowser`, values which are callback
	 * functions with different parameters
	 * @param {Function} callbacks.upload - function that will be called when the user selects a file or using drag
	 * and drop files to the `Upload` tab
	 * @param {Function} callbacks.url - function that will be called when the user enters the URL of the tab image
	 * and alternative text for images
	 * @param {Function} callbacks.filebrowser - function that will be called when the user clicks on the file browser
	 * tab, and then choose any image in the window that opens, faylbrauzera
	 * @params {HTMLNode} image image object
	 * @example
	 * ```javascript
	 * let widget = new Jodit.modules.Widget(editor);
	 *
	 * return widget.create('ImageSelector', {
	 *      url: function (url, alt) {
	 *          editor.selections.insertImage(url);
	 *      },
	 *      upload: function (images) {
	 *          editor.selections.insertImage(images[0]);
	 *      },
	 *      filebrowser: function (images) {
	 *          editor.selections.insertImage(images[0]);
	 *      }
	 * }, image);
	 * ```
	 */

	interface ImageSelectorCallbacks {
		url?: (this: IJodit, url: string, alt: string) => void;
		filebrowser?: (data: IFileBrowserCallBackData) => void;
		upload?: (this: IJodit, data: IFileBrowserCallBackData) => void;
	}

	/**
	 *
	 * @param {Jodit} editor
	 * @param {Widget.ImageSelectorCallbacks} callbacks
	 * @param {HTMLElement} elm
	 * @param {Function} close Close popup
	 * @param {boolean} isImage
	 * @return {HTMLDivElement}
	 * @constructor
	 */
	export const FileSelectorWidget = (
		editor: IJodit,
		callbacks: ImageSelectorCallbacks,
		elm: HTMLElement | null,
		close: () => void,
		isImage: boolean = true
	): HTMLDivElement => {
		let currentImage: any;

		const tabs: IDictionary<HTMLElement> | IDictionary<() => void> = {};

		if (
			callbacks.upload &&
			editor.options.uploader &&
			(editor.options.uploader.url ||
				editor.options.uploader.insertImageAsBase64URI)
		) {
			const dragbox: HTMLElement = editor.create.fromHTML(
				'<div class="jodit_draganddrop_file_box">' +
					'<strong>' +
					editor.i18n(isImage ? 'Drop image' : 'Drop file') +
					'</strong>' +
					'<span><br> ' +
					editor.i18n('or click') +
					'</span>' +
					'<input type="file" accept="' +
					(isImage ? 'image/*' : '*') +
					'image/*" tabindex="-1" dir="auto" multiple=""/>' +
					'</div>'
			);

			editor.getInstance<IUploader>('Uploader').bind(
				dragbox,
				(resp: IUploaderData) => {
					if (typeof callbacks.upload === 'function') {
						callbacks.upload.call(editor, {
							baseurl: resp.baseurl,
							files: resp.files
						} as IFileBrowserCallBackData);
					}
				},
				(error: Error) => {
					editor.events.fire('errorMessage', error.message);
				}
			);
			const icon = editor.options.textIcons
				? ''
				: ToolbarIcon.getIcon('upload');
			tabs[icon + editor.i18n('Upload')] = dragbox;
		}

		if (callbacks.filebrowser) {
			if (
				editor.options.filebrowser.ajax.url ||
				editor.options.filebrowser.items.url
			) {
				const icon = editor.options.textIcons
					? ''
					: ToolbarIcon.getIcon('folder');
				tabs[icon + editor.i18n('Browse')] = () => {
					close && close();
					if (callbacks.filebrowser) {
						(editor.getInstance(
							'FileBrowser'
						) as IFileBrowser).open(callbacks.filebrowser, isImage);
					}
				};
			}
		}

		if (callbacks.url) {
			const form: HTMLFormElement = editor.create.fromHTML(
					'<form onsubmit="return false;" class="jodit_form">' +
						'<input type="text" required name="url" placeholder="http://"/>' +
						'<input type="text" name="text" placeholder="' +
						editor.i18n('Alternative text') +
						'"/>' +
						'<div style="text-align: right">' +
						'<button>' +
						editor.i18n('Insert') +
						'</button>' +
						'</div>' +
						'</form>'
				) as HTMLFormElement,
				button: HTMLButtonElement = form.querySelector(
					'button'
				) as HTMLButtonElement,
				url: HTMLInputElement = form.querySelector(
					'input[name=url]'
				) as HTMLInputElement;

			currentImage = null;

			if (
				elm &&
				elm.nodeType !== Node.TEXT_NODE &&
				(elm.tagName === 'IMG' || $$('img', elm).length)
			) {
				currentImage = elm.tagName === 'IMG' ? elm : $$('img', elm)[0];
				val(form, 'input[name=url]', currentImage.getAttribute('src'));
				val(form, 'input[name=text]', currentImage.getAttribute('alt'));
				button.innerText = editor.i18n('Update');
			}

			if (
				elm &&
				elm.nodeType !== Node.TEXT_NODE &&
				elm.nodeName === 'A'
			) {
				val(form, 'input[name=url]', elm.getAttribute('href') || '');
				val(form, 'input[name=text]', elm.getAttribute('title') || '');
				button.innerText = editor.i18n('Update');
			}

			form.addEventListener(
				'submit',
				(event: Event) => {
					event.preventDefault();
					event.stopPropagation();

					if (!val(form, 'input[name=url]')) {
						url.focus();
						url.classList.add('jodit_error');
						return false;
					}

					if (typeof callbacks.url === 'function') {
						callbacks.url.call(
							editor,
							val(form, 'input[name=url]'),
							val(form, 'input[name=text]')
						);
					}

					return false;
				},
				false
			);

			const icon = editor.options.textIcons
				? ''
				: ToolbarIcon.getIcon('link');

			tabs[icon + ' URL'] = form;
		}

		return TabsWidget(editor, tabs);
	};
}
