const dragcontainer = document.getElementById("dragcontainer");
const actions = document.getElementById("actions");
const addBtn = document.getElementById("add-btn");
const edit = document.getElementById("edit");
const fieldTypes = document.querySelector(".field-types");

let dragged = null;
let highlighted = null;
let editElement = null;
let fieldId = Date.now();

function on(element, type, handler) {
	if (element) element.addEventListener(type, handler);
}

if (dragcontainer) {
	on(dragcontainer, 'dragstart', dragStart);
	on(dragcontainer, 'dragend', dragEnd);
	on(dragcontainer, 'dragover', dragOver);
	on(dragcontainer, 'dragenter', dragEnter);
	on(dragcontainer, 'dragleave', dragLeave);
	on(dragcontainer, 'drop', drop);
	on(dragcontainer, 'mouseover', over);
}

on(actions, 'click', actionsClick);
on(edit, 'click', editClick);
on(edit, 'change', editChange);
on(addBtn, 'click', addRow);
on(fieldTypes, 'click', addRow);

function newFieldElement() {
	let node = document.getElementById("template")?.firstElementChild?.cloneNode(true);
	if (!node) return null;

	let newId = fieldId++;
	let html = node.outerHTML.replaceAll('[0]', '[' + newId + ']').replaceAll('#', String(newId));

	return generateElements(html)[0];
}

function addRow(event) {
	let link = event.target.closest("a");
	let type = link?.dataset.type ?? (event.currentTarget === addBtn ? "text" : null);
	if (!type) return;

	if (link) event.preventDefault();

	let template = newFieldElement();
	if (!template) return;

	dragcontainer.querySelector(".dragzone")?.append(template);

	if (type != "text") {
		editElement = template.querySelector(".col");
		if (editElement) {
			highlighted = editElement;
			let typeSelect = highlighted.querySelector("[data-v-field-type]");
			if (typeSelect) typeSelect.value = type;
			renderField(template);
		}
	}
}

function addCol(event) {
	let template = newFieldElement();
	if (!template) return;

	dragcontainer.querySelector(".dragzone")?.append(template);
}

function buildParams(prefix, obj, add) {
	let rbracket = /\[\]$/;

	if (Array.isArray(obj)) {

		// Serialize array item.
		for (const key in obj) {
			let v = obj[key];
			if (rbracket.test(prefix)) {

				// Treat each array item as a scalar.
				add(prefix, v);

			} else {

				// Item is non-scalar (array or object), encode its numeric index.
				buildParams(
					prefix + "[" + (typeof v === "object" && v != null ? key : "") + "]",
					v,
					add
				);
			}
		}

	} else if (typeof obj === "object") {

		// Serialize object item.
		for (const name in obj) {
			buildParams(prefix + "[" + name + "]", obj[name], add);
		}

	} else {

		// Serialize scalar item.
		add(prefix, obj);
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
function nestedFormData(a) {
	let s = [],
		add = function (key, valueOrFunction) {

			// If value is a function, invoke it and use its return value
			let value = typeof valueOrFunction === "function" ?
				valueOrFunction() :
				valueOrFunction;

			s[s.length] = encodeURIComponent(key) + "=" +
				encodeURIComponent(value == null ? "" : value);
		};

	if (a == null) {
		return "";
	}

	// If an array was passed in, assume that it is an array of form elements.
	if (Array.isArray(a)) {
		// Serialize the form elements
		for (const key in a) {
			let v = a[key];
			add(key, v);
		}
	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for (const prefix in a) {
			buildParams(prefix, a[prefix], add);
		}
	}

	// Return the resulting serialization
	return s.join("&");
}

function serialize(parentElement) {
	let data = {};
	parentElement.querySelectorAll("input[type=text],input[type=number],input[type=hidden],input[type=checkbox]:checked,input[type=radio]:checked,input[name=image],select:not(:disabled), textarea").forEach((el) => {
		let name = el.name;
		data[name] = el.value;
	});

	return data;
}

function editChange(event) {
	if (!highlighted) return;

	let element = event.target;
	let name = element.name;
	let value = element.value;
	let fieldName = name.replace(/.+\[|\]/g, '');

	if (fieldName == "col") {
		highlighted.setAttribute("class", "col " + value);
	}

	renderField(edit);
}

function renderField(editContainer) {
	if (!highlighted || !editContainer) return;

	let data = {};

	editContainer.querySelectorAll('.tab-pane').forEach((e) => {
		let name = e.getAttribute('aria-labelledby');
		if (name == 'conditions') return;
		data = { ...data, ...serialize(e) };
	});

	data["field_id"] = highlighted.dataset.id;
	data["type"] = editContainer.querySelector('[data-v-field-type]')?.value;
	data["csrf"] = document.querySelector('[data-v-csrf]')?.value;

	fetch(window.location.pathname + "?module=field/field-group&action=field", {
		method: "POST",
		headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
		body: nestedFormData(data)
	})
		.then((response) => {
			if (!response.ok) throw response;
			return response.json();
		})
		.then((data) => {
			let focusName = document.activeElement?.name ?? "";

			let input = editElement?.querySelector(".input");
			if (input) input.innerHTML = data['field'];

			editContainer.querySelectorAll(".tab-pane").forEach(e => {
				let name = e.getAttribute('aria-labelledby');
				if (name && data[name]) {
					e.innerHTML = data[name];
				}
			});

			if (focusName) {
				let focusElement = null;
				editContainer.querySelectorAll("[name]").forEach(el => {
					if (!focusElement && el.name === focusName) focusElement = el;
				});
				focusElement?.focus();
			}
		})
		.catch(error => {
			console.error(error);
			let message = "Error saving field!";
			if (error && typeof error.text === "function") {
				error.text()
					.then(text => notify((text || message).substr(0, 200)))
					.catch(() => notify(message));
			} else {
				notify(error?.statusText || message);
			}
		});
}

function notify(message) {
	if (typeof displayToast === "function") {
		displayToast("danger", "Error", message);
	}
}

function editClick(event) {
	let element = event.target.closest(".nav-item > .nav-link");
	if (!element) return;

	let id = element.getAttribute("aria-controls");
	if (!id) return;

	let tab = edit.querySelector('[aria-labelledby="' + id + '"]');
	element.closest(".nav-tabs")?.querySelectorAll(".nav-link")?.forEach(e => e.classList.remove("active"));
	element.classList.add("active");
	tab?.parentNode.querySelectorAll(".tab-pane")?.forEach(e => e.classList.remove("active"));
	tab?.classList.add("active");
}

function actionsClick(event) {
	if (!highlighted) return;

	let element;

	if (element = event.target.closest(".btn-edit")) {
		let editContainer = highlighted.querySelector(".edit");
		if (edit.classList.contains("show")) {
			element.querySelector("i").setAttribute("class", "la la-angle-down");
			edit.classList.remove("show");
			if (editContainer) editContainer.replaceChildren(...edit.children);
			editElement = null;
		} else {
			element.querySelector("i").setAttribute("class", "la la-angle-up");
			edit.classList.add("show");
			if (editContainer) edit.replaceChildren(...editContainer.children);
			editElement = highlighted;
		}

		event.preventDefault();
	}

	if (element = event.target.closest(".btn-add")) {
		let template = newFieldElement();
		if (!template) return;

		let col = template.querySelector(".col") ?? template;
		highlighted.after(col);
	}

	if (element = event.target.closest(".btn-clone")) {
		let clone = highlighted.cloneNode(true);
		let newId = fieldId++;
		if (highlighted.dataset.id) {
			clone.innerHTML = clone.innerHTML.split('[' + highlighted.dataset.id + ']').join('[' + newId + ']');
		}
		clone.dataset.id = String(newId);
		highlighted.after(clone);
	}

	if (element = event.target.closest(".btn-remove")) {
		if (confirm("Are you sure?")) {
			let form = document.getElementById('field-form');
			if (form) {
				let input = document.createElement('input');
				input.type = 'hidden';
				input.name = 'delete[field_id][]';
				input.value = highlighted.dataset.id ?? '';
				form.append(input);
			}

			highlighted.remove();
			highlighted = null;
			actions.style.display = "none";
		}
	}
}

function over(event) {
	let classList = event.target.classList;
	if (!(classList.contains("col"))) {
		return;
	}

	if (edit.classList.contains("show")) {
		return;
	}

	highlighted = event.target;
	const rect = highlighted.getBoundingClientRect();
	actions.style.opacity = "0";
	actions.style.top = (rect.top + window.scrollY) + "px";
	actions.style.left = (rect.x + rect.width - actions.clientWidth + window.scrollX - 1) + "px";
	edit.style.top = (rect.bottom + window.scrollY) + "px";

	if (rect.top < 80) {
		actions.style.display = "none";
		highlighted = null;
	} else {
		actions.style.display = "block";
		actions.style.opacity = "1";
	}
}

function dragStart(event) {
	event.target.classList.add("dragstart");
	dragged = event.target;
	actions.style.display = "none";
}

function dragEnd(event) {
	event.target.classList.remove("dragstart");
}

function dragEnter(event) {
	event.target.classList.add("drop");
}

function dragLeave(event) {
	event.target.classList.remove("drop");
}

function dragOver(event) {
	event.preventDefault();
}

function drop(event) {
	if (!dragged) return;

	let container = event.target;
	let parentRow = dragged.parentNode;
	event.target.classList.remove("drop");

	if (dragged.classList.contains("row")) {
		if (container.classList.contains("container-fluid")) {
			container.appendChild(dragged);
		} else if (container.classList.contains("row")) {
			container.after(dragged);
		} else if (container.classList.contains("col")) {
			let col = generateElements('<div class="col" draggable="true"></div>')[0];
			col.append(dragged);
			container.after(col);
		}
	} else if (dragged.classList.contains("col")) {
		if (container.classList.contains("row")) {
			container.appendChild(dragged);
		} else if (container.classList.contains("col")) {
			container.after(dragged);
		} else if (container.classList.contains("container-fluid")) {
			let row = generateElements('<div class="row" draggable="true"></div>')[0];
			row.append(dragged);
			container.append(row);
		}
	}

	if (parentRow && !parentRow.childElementCount) {
		parentRow.remove();
	}
}
