/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { instanceSymbol } from "../../constants.mjs";
import {
	assembleMethodSymbol,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { addErrorAttribute } from "../../dom/error.mjs";
import { getDocument } from "../../dom/util.mjs";
import { isArray, isObject, isString } from "../../types/is.mjs";
import { CustomElement } from "../../dom/customelement.mjs";

export { SelectLink };

const sourceElementSymbol = Symbol("sourceElement");
const targetElementSymbol = Symbol("targetElement");
const observerSymbol = Symbol("observer");
const handlerSymbol = Symbol("handler");
const boundSymbol = Symbol("bound");

class SelectLink extends CustomElement {
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/components/form/select-link@@instance");
	}

	static getTag() {
		return "monster-select-link";
	}

	get defaults() {
		return Object.assign({}, super.defaults, {
			shadowMode: false,
			source: "",
			target: "",
			param: "",
			emptyValue: "",
			disableTarget: true,
			clearSelection: true,
			clearOptions: true,
			clearTotalMessage: true,
			clearMessageOnValue: true,
			emptyMessage: "",
			autoFetch: true,
			autoFetchOnEmpty: false,
			syncOnInit: true,
			events: [
				"monster-selected",
				"monster-changed",
				"monster-selection-removed",
				"monster-selection-cleared",
			],
			debug: false,
		});
	}

	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		bindSelects.call(this);
		return this;
	}

	disconnectedCallback() {
		unbindSelects.call(this);
	}
}

function resolveElement(value) {
	if (value instanceof HTMLElement) {
		return value;
	}

	if (!isString(value) || value.trim() === "") {
		return null;
	}

	const selector = value.trim();
	const doc = getDocument();
	let element = null;

	try {
		element = doc.querySelector(selector);
	} catch (e) {
		element = null;
	}

	if (!element) {
		element = doc.getElementById(selector.replace(/^#/, ""));
	}

	return element instanceof HTMLElement ? element : null;
}

function bindSelects() {
	const source = resolveElement(this.getOption("source"));
	const target = resolveElement(this.getOption("target"));

	if (!(source && target)) {
		if (!this[observerSymbol] && getDocument().body) {
			const observer = new MutationObserver(() => {
				bindSelects.call(this);
			});
			observer.observe(getDocument().body, {
				childList: true,
				subtree: true,
			});
			this[observerSymbol] = observer;
		}
		return;
	}

	if (this[observerSymbol]) {
		this[observerSymbol].disconnect();
		delete this[observerSymbol];
	}

	this[sourceElementSymbol] = source;
	this[targetElementSymbol] = target;

	if (this[boundSymbol]) {
		return;
	}

	const events = this.getOption("events");
	const handler = () => syncTarget.call(this);
	this[handlerSymbol] = handler;

	if (isArray(events)) {
		for (const eventName of events) {
			if (isString(eventName) && eventName !== "") {
				source.addEventListener(eventName, handler);
			}
		}
	}

	this[boundSymbol] = true;

	if (this.getOption("syncOnInit") === true) {
		queueMicrotask(() => {
			syncTarget.call(this);
		});
	}
}

function unbindSelects() {
	if (this[observerSymbol]) {
		this[observerSymbol].disconnect();
		delete this[observerSymbol];
	}

	const source = this[sourceElementSymbol];
	const handler = this[handlerSymbol];
	const events = this.getOption("events");

	if (source && handler && isArray(events)) {
		for (const eventName of events) {
			if (isString(eventName) && eventName !== "") {
				source.removeEventListener(eventName, handler);
			}
		}
	}

	delete this[sourceElementSymbol];
	delete this[targetElementSymbol];
	delete this[handlerSymbol];
	delete this[boundSymbol];
}

function syncTarget() {
	const source = this[sourceElementSymbol];
	const target = this[targetElementSymbol];
	const param = this.getOption("param");

	if (!(source && target)) {
		return;
	}

	if (!isString(param) || param.trim() === "") {
		addErrorAttribute(this, "Missing param option.");
		return;
	}

	let value = source.value;
	if (isArray(value)) {
		value = value.join(",");
	}

	const isEmpty = value === null || value === undefined || value === "";
	const emptyValue = this.getOption("emptyValue");
	const autoFetch = this.getOption("autoFetch") === true;
	const autoFetchOnEmpty = this.getOption("autoFetchOnEmpty") === true;
	const disableTarget = this.getOption("disableTarget") === true;
	const debug = this.getOption("debug") === true;

	if (debug) {
		console.log("[select-link]", {
			source: source.id || source.tagName,
			target: target.id || target.tagName,
			param,
			value,
			isEmpty,
			disableTarget,
			autoFetch,
			autoFetchOnEmpty,
		});
	}

	if (isEmpty) {
		if (disableTarget === true) {
			target.setAttribute("disabled", "");
			if (debug) {
				console.log("[select-link] target disabled (empty)");
			}
		}

		if (this.getOption("clearSelection") === true) {
			target.setOption("selection", []);
		}
		if (this.getOption("clearOptions") === true) {
			target.setOption("options", []);
		}
		if (this.getOption("clearTotalMessage") === true) {
			target.setOption("messages.total", "");
		}
		const emptyMessage = this.getOption("emptyMessage");
		if (isString(emptyMessage) && emptyMessage !== "") {
			target.setOption("messages.control", emptyMessage);
		}

		const params = Object.assign({}, target.getOption("filter.params", {}));
		params[param] = emptyValue;
		target.setOption("filter.params", params);

		if (autoFetchOnEmpty && typeof target.fetch === "function") {
			target.fetch().catch((e) => {
				addErrorAttribute(target, e);
			});
		}
		return;
	}

	if (disableTarget === true) {
		target.removeAttribute("disabled");
		if (debug) {
			console.log("[select-link] target enabled (value)");
		}
	}

	if (this.getOption("clearMessageOnValue") === true) {
		target.setOption("messages.control", "");
	}

	const params = Object.assign({}, target.getOption("filter.params", {}));
	params[param] = value;
	target.setOption("filter.params", params);

	if (autoFetch && typeof target.fetch === "function") {
		target.fetch().catch((e) => {
			addErrorAttribute(target, e);
		});
	}
}

registerCustomElement(SelectLink);
