var ActionEditor: ActionEditor = {
	elements: {
		up: {
			container: document.querySelector('.event.up'),
			selector: document.querySelector('.event.up').querySelector('.actionselector'),
			actions: document.querySelector('.event.up').querySelector('.actions')
		},
		down: {
			container: document.querySelector('.event.down'),
			selector: document.querySelector('.event.down').querySelector('.actionselector'),
			actions: document.querySelector('.event.down').querySelector('.actions')
		},
		latch: {
			container: document.querySelector('.event.toggle'),
			selector: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actionselector'),
			actions: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actions')
		},
		unlatch: {
			container: document.querySelector('.event.toggle'),
			selector: document
				.querySelector('.event.toggle')
				.querySelector('.tunlatch')
				.querySelector('.actionselector'),
			actions: document.querySelector('.event.toggle').querySelector('.tunlatch').querySelector('.actions')
		}
	},

	openEditors: { up: {}, down: {}, latch: {}, unlatch: {} },

	listeners() {
		if (ActionSelector) {
			ActionSelector.register(ActionEditor.elements.up.selector, (integrationID: string, actionID: string) =>
				handleNewAction(integrationID, actionID, 'up')
			);
			ActionSelector.register(ActionEditor.elements.down.selector, (integrationID: string, actionID: string) =>
				handleNewAction(integrationID, actionID, 'down')
			);
			ActionSelector.register(ActionEditor.elements.latch.selector, (integrationID: string, actionID: string) =>
				handleNewAction(integrationID, actionID, 'latch')
			);
			ActionSelector.register(ActionEditor.elements.unlatch.selector, (integrationID: string, actionID: string) =>
				handleNewAction(integrationID, actionID, 'unlatch')
			);

			function handleNewAction(
				integrationID: string,
				actionID: string,
				type: 'up' | 'down' | 'latch' | 'unlatch'
			) {
				socket.emit(
					'actioneditor',
					'create',
					PageHandler.currentPageID,
					Editor.currentX,
					Editor.currentY,
					type,
					integrationID,
					actionID,
					(actionInstance: Page_Key_Action) => {
						ActionEditor.openAction(actionInstance, type);
					}
				);
			}
		} else setTimeout(ActionEditor.listeners, 100);
	},

	open(actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) {
		for (var type in actions) {
			ActionEditor.elements[type].actions.innerHTML = '';
			for (var actionInstanceID in actions[type]) {
				ActionEditor.openAction(actions[type][actionInstanceID], type);
			}
		}
	},

	openAction(actionInstance: Page_Key_Action, type: string) {
		var actionInstanceID = actionInstance.actionInstanceID;
		var container = <HTMLDivElement>ce('div', 'actioncontainer', { actionInstanceID });

		var header = ce('div', 'header');
		header.appendChild(
			ce('div', 'integration', null, ActionSelector.maps.integrationNames[actionInstance.integrationID])
		);
		header.appendChild(ce('div', 'action', null, ActionSelector.maps.actionNames[actionInstance.actionID]));

		var buttons = ce('div', 'buttons');
		var actionLogs = ce(
			'div',
			[
				'btn',
				'logs'
			],
			null,
			'Logs'
		);
		actionLogs.onclick = () => UndeckedNotification('Not implented yet', 'error');
		var actionRemove = ce(
			'div',
			[
				'btn',
				'remove'
			],
			null,
			'Remove'
		);
		actionRemove.onclick = () => {
			if (container.parentElement) container.parentElement.removeChild(container);
			if (
				ActionEditor.openEditors[type] != undefined &&
				ActionEditor.openEditors[type][actionInstanceID] != undefined
			) {
				ActionEditor.openEditors[type][actionInstanceID].destroy();
			}
			socket.emit(
				'actioneditor',
				'remove',
				PageHandler.currentPageID,
				Editor.currentX,
				Editor.currentY,
				actionInstanceID,
				type
			);
			Editor.registerChange();
		};
		buttons.appendChild(actionLogs);
		buttons.appendChild(actionRemove);
		header.appendChild(buttons);

		container.appendChild(header);

		var fields = <HTMLDivElement>ce('div', 'fields');
		container.appendChild(fields);

		ActionEditor.elements[type].actions.appendChild(container);

		ActionEditor.openEditors[type][actionInstanceID] = new Action(
			{
				integrationID: actionInstance.integrationID,
				actionID: actionInstance.actionID,
				actionType: <any>type,
				keyX: Editor.currentX,
				keyY: Editor.currentY,
				actionInstanceID,
				pageID: PageHandler.currentPageID
			},
			fields
		);
	},

	close() {
		for (var type in ActionEditor.openEditors) {
			for (var actionInstanceID in ActionEditor.openEditors[type]) {
				ActionEditor.openEditors[type][actionInstanceID].destroy();
			}
		}

		ActionEditor.elements.up.actions.innerHTML = '';
		ActionEditor.elements.down.actions.innerHTML = '';
	}
};
ActionEditor.listeners();

var Action = class Action {
	settings: Action_Settings;
	container: HTMLDivElement;

	actionEditorID: string;

	lastFields: EditorAPI_Field[];

	constructor(settings: Action_Settings, container: HTMLDivElement) {
		this.settings = settings;
		this.container = container;

		socket.emit(
			'actioneditor',
			'start',
			this.settings,
			(error: string, actionEditorID: string, ready: () => void) => {
				if (error) return UndeckedNotification(error, 'error', 5000);
				this.actionEditorID = actionEditorID;
				this.listeners();
				this.emit('ready');
			}
		);
	}

	emit(query: string, ...args: any[]) {
		socket.emit('actioneditor', 'instance', this.actionEditorID, query, ...args);
	}

	listeners() {
		socket.on(`AE_${this.actionEditorID}`, (query: string, ...args: any[]) => {
			switch (query) {
				case 'fields':
					var fields: EditorAPI_Field[] = args[0];
					this.render(fields);
					break;
			}
		});
	}

	destroy() {
		if (this.container && this.container.parentElement) this.container.parentElement.removeChild(this.container);
		this.emit('close');
	}

	render(fields: EditorAPI_Field[]) {
		var valueNameMap = {};

		var updateMulti = (selected: string[], input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
			var text: string =
				selected != undefined && selected.length > 0
					? selected
							.map((value) => {
								return valueNameMap[value];
							})
							.join(', ')
					: 'None';

			input.innerHTML = '';
			input.appendChild(ce('option', 'mutli', { value: selected.join(',') }, text));
		};

		var getMultiValues = (dropdowninner: HTMLDivElement): string[] => {
			var values: string[] = [];
			dropdowninner.querySelectorAll('.option').forEach((option: HTMLDivElement) => {
				var checkbox: HTMLInputElement = option.querySelector('input');
				var optionID = option.getAttribute('optionID');

				if (checkbox.checked) values.push(optionID);
			});
			return values;
		};

		fields.forEach((field) => {
			var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
			if (fieldcontainer == null) {
				fieldcontainer = <HTMLDivElement>ce('div', [
					'field',
					`field_${field.id}`
				]);
				this.container.appendChild(fieldcontainer);
			}

			var label: HTMLDivElement = fieldcontainer.querySelector('.fieldlabel');
			if (label == null) {
				label = <HTMLDivElement>ce('div', 'fieldlabel', null, `${field.name}${field.required ? ' *' : ''}`);
				fieldcontainer.appendChild(label);
			} else label.innerText = `${field.name}${field.required ? ' *' : ''}`;

			var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
				'.input'
			);

			if (input == null) {
				switch (field.type) {
					case 'number':
					case 'text':
					case 'color':
						input = <HTMLInputElement>ce('input', 'input', { type: field.type, value: field.value });
						fieldcontainer.appendChild(input);
						break;
					case 'select':
					case 'connection':
						if (field.multi != undefined && field.multi == true) {
							var multiSelect = ce('div', [
								'multiselect'
							]);
							input = <HTMLInputElement>ce('select', [
								'input',
								'multiinput'
							]);

							input.onmouseup = (e: MouseEvent) => {
								e.preventDefault();
								e.stopPropagation();

								dropdown.style.display = 'block';
								input.classList.add('open');

								var handleWindowClick = (e: MouseEvent) => {
									if (e.target) {
										var target = <HTMLElement>e.target;

										if (
											!target.classList.contains('msdp') &&
											!target.classList.contains('multiinput')
										) {
											window.removeEventListener('click', handleWindowClick);
											dropdown.style.display = 'none';
											input.classList.remove('open');
										}
									}
								};

								window.addEventListener('click', handleWindowClick);
							};

							input.onmousedown = (e: MouseEvent) => {
								e.preventDefault();
								e.stopPropagation();
							};

							multiSelect.appendChild(input);

							var dropdown = ce('div', [
								'dropdown',
								'msdp'
							]);
							var dropdowninner = <HTMLDivElement>ce('div', [
								'inner',
								'msdp'
							]);

							dropdown.appendChild(dropdowninner);
							multiSelect.appendChild(dropdown);

							if (field.values != undefined && field.values.length > 0)
								for (let i = 0; i < field.values.length; i++) {
									((value) => {
										valueNameMap[value.id] = field.values[i].text;

										var item = ce(
											'div',
											[
												'option',
												'msdp'
											],
											{ optionID: value.id }
										);
										var checkbox = <HTMLInputElement>ce('input', 'msdp', {
											type: 'checkbox'
										});

										checkbox.checked = field.value != undefined && field.value.includes(value.id);

										item.onclick = () => {
											checkbox.checked = !checkbox.checked;
											updateMulti(getMultiValues(dropdowninner), input);
											this.emit('fields', this.export());
										};

										item.appendChild(checkbox);
										item.appendChild(
											ce(
												'div',
												[
													'text',
													'msdp'
												],
												null,
												value.text
											)
										);
										dropdowninner.appendChild(item);
									})(field.values[i]);
								}

							updateMulti(field.value, input);

							fieldcontainer.appendChild(multiSelect);
						} else {
							input = <HTMLInputElement>ce('select', 'input');
							if (field.values != undefined && field.values.length > 0)
								for (let i = 0; i < field.values.length; i++) {
									var option = ce(
										'option',
										null,
										{ value: field.values[i].id },
										field.values[i].text
									);
									if (field.value == field.values[i].id) option.setAttribute('selected', '');
									input.appendChild(option);
								}

							fieldcontainer.appendChild(input);
						}

						break;
				}

				if (input) input.oninput = () => this.emit('fields', this.export());
			} else {
				input.value = field.value;

				if (field.type == 'select' || field.type == 'connection') {
					var parsedOptionValues = [];
					if (field.multi != undefined && field.multi == true) {
						var dropdown_inner = <HTMLDivElement>input.parentElement.querySelector('.inner');

						if (field.values != undefined && field.values.length > 0)
							for (let i = 0; i < field.values.length; i++) {
								var selectValue = field.values[i];
								var existingOption: HTMLDivElement = dropdown_inner.querySelector(
									`.option[optionid="${selectValue.id}"]`
								);

								parsedOptionValues.push(selectValue.id);

								if (existingOption) {
									var text: HTMLDivElement = existingOption.querySelector('.text');
									text.innerText = selectValue.text;
								} else {
									((value) => {
										valueNameMap[value.id] = field.values[i].text;

										var item = ce(
											'div',
											[
												'option',
												'msdp'
											],
											{ optionID: value.id }
										);
										var checkbox = <HTMLInputElement>ce('input', 'msdp', {
											type: 'checkbox'
										});

										checkbox.checked = field.value != undefined && field.value.includes(value.id);

										item.onclick = () => {
											checkbox.checked = !checkbox.checked;
											updateMulti(getMultiValues(dropdown_inner), input);
											this.emit('fields', this.export());
										};

										item.appendChild(checkbox);
										item.appendChild(
											ce(
												'div',
												[
													'text',
													'msdp'
												],
												null,
												value.text
											)
										);
										dropdown_inner.appendChild(item);
									})(selectValue);
								}
							}
						else dropdown_inner.innerHTML = '';

						dropdown_inner.querySelectorAll('.option').forEach((option: HTMLOptionElement) => {
							var optionValue = option.getAttribute('optionID');
							var checkbox: HTMLInputElement = option.querySelector('input');

							if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
							else checkbox.checked = field.value.includes(optionValue);
						});
					} else {
						if (field.values != undefined && field.values.length > 0)
							for (let i = 0; i < field.values.length; i++) {
								var selectValue = field.values[i];
								var existing: HTMLOptionElement = input.querySelector(
									`option[value="${selectValue.id}"]`
								);

								if (existing) existing.innerText = selectValue.text;
								else {
									existing = <HTMLOptionElement>ce(
										'option',
										null,
										{ value: selectValue.id },
										selectValue.text
									);
									input.appendChild(existing);
								}
								parsedOptionValues.push(selectValue.id);
							}
						else input.innerHTML = '';

						input.querySelectorAll('option').forEach((option: HTMLOptionElement) => {
							var optionValue = option.getAttribute('value');
							option.removeAttribute('selected');

							if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
							else if (field.value == option) option.setAttribute('selected', '');
						});
					}

					// if (field.value != undefined && input.value.length > 0) input.value = field.value;
				}
			}
		});
		this.lastFields = fields;
	}

	export(): EditorAPI_Field[] {
		var copyOfLast = JSON.parse(JSON.stringify(this.lastFields));
		for (let i = 0; i < copyOfLast.length; i++) {
			var field = copyOfLast[i];
			var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
			if (fieldcontainer) {
				var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
					'.input'
				);
				if (input) {
					if (!input.classList.contains('multiinput')) copyOfLast[i].value = input.value;
					else copyOfLast[i].value = input.value.split(',');
				}
			}
		}

		return copyOfLast;
	}
};

interface Action_Settings {
	integrationID: string;
	actionID: string;
	actionInstanceID: string;
	pageID: string;
	actionType: 'up' | 'down';
	keyX: number | string;
	keyY: number | string;
}

interface ActionEditor {
	elements: {
		up: {
			container: HTMLDivElement;
			selector: HTMLInputElement;
			actions: HTMLDivElement;
		};
		down: {
			container: HTMLDivElement;
			selector: HTMLInputElement;
			actions: HTMLDivElement;
		};
		latch: {
			container: HTMLDivElement;
			selector: HTMLInputElement;
			actions: HTMLDivElement;
		};
		unlatch: {
			container: HTMLDivElement;
			selector: HTMLInputElement;
			actions: HTMLDivElement;
		};
	};

	openEditors: {
		up: { [actionInstanceID: string]: typeof Action };
		down: { [actionInstanceID: string]: typeof Action };
		latch: { [actionInstanceID: string]: typeof Action };
		unlatch: { [actionInstanceID: string]: typeof Action };
	};

	listeners: () => void;

	open: (actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) => void;
	openAction: (actionInstance: Page_Key_Action, type: string) => void;
	close: () => void;
}

interface Page_Key_ActionTypes {
	up: Page_Key_Actions;
	down: Page_Key_Actions;
}
interface Page_Key_Actions {
	[actionInstanceID: string]: Page_Key_Action;
}

interface Page_Key_Action {
	integrationID: string;
	actionID: string;
	actionInstanceID: string;
	properties: { [property: string]: any };
	logs: { timestamp: number; type: 'error' | 'info' | 'warning'; text: string }[];
}

interface EditorAPI_Field {
	id: string;
	name: string;
	type: 'text' | 'number' | 'select' | 'connection' | 'color';
	value: any;
	values?: { id: string; text: string }[];
	required?: boolean;
	multi?: boolean;
}
