/**
 * 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 { addAttributeToken } from "../../dom/attributes.mjs";
import { ATTRIBUTE_ERRORMESSAGE } from "../../dom/constants.mjs";
import {
	assembleMethodSymbol,
	CustomElement,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { findElementWithSelectorUpwards } from "../../dom/util.mjs";
import { isString } from "../../types/is.mjs";
import { State } from "../form/types/state.mjs";
import "../form/state-button.mjs";
import { ATTRIBUTE_DATASOURCE_SELECTOR } from "./constants.mjs";
import { ChangeButtonStyleSheet } from "./stylesheet/change-button.mjs";

export { ChangeButton };

/**
 * @private
 * @type {symbol}
 */
const stateButtonElementSymbol = Symbol("stateButtonElement");

/**
 * @private
 * @type {symbol}
 */
const datasetLinkedElementSymbol = Symbol("datasetLinkedElement");
/**
 * @private
 * @type {symbol}
 */
const overlayLinkedElementSymbol = Symbol("overlayLinkedElement");

/**
 * The change button component is used to change the data of a dataset.
 *
 * @fragments /fragments/components/datatable/change-button/
 *
 * @example /examples/components/datatable/change-button-simple Simple change button
 *
 * @issue https://localhost.alvine.dev:8440/development/issues/closed/274.html
 *
 * @copyright Volker Schukai
 * @summary The Status component is used to show the current status of a datasource.
 */
class ChangeButton extends CustomElement {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/datasource/change-button@@instance",
		);
	}

	/**
	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
	 *
	 * The individual configuration values can be found in the table.
	 *
	 * @property {Object} templates Template definitions
	 * @property {string} templates.main Main template
	 * @property {object} datasource The datasource
	 * @property {string} datasource.selector The selector of the datasource
	 * @property {object} mapping The mapping
	 * @property {string} mapping.data The data
	 * @property {number} mapping.index The index
	 * @property {Array} data The data
	 * @return {Object}
	 */
	get defaults() {
		const obj = Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},

			labels: {
				button: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
                             class="bi bi-grid" viewBox="0 0 16 16">
                            <path d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5z"/>
                        </svg>`,
			},

			classes: {
				bar: "monster-button-primary",
			},

			dataset: {
				selector: null,
			},

			overlay: {
				selector: null,
			},

			mapping: {
				data: "dataset",
				index: 0,
			},

			data: {},

			disabled: false,
		});

		updateOptionsFromArguments.call(this, obj);
		return obj;
	}

	/**
	 *
	 * @return {string}
	 */
	static getTag() {
		return "monster-datatable-change-button";
	}

	/**
	 * This method is responsible for assembling the component.
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();

		initControlReferences.call(this);
		initEventHandler.call(this);
	}

	/**
	 *
	 * @return [CSSStyleSheet]
	 */
	static getCSSStyleSheet() {
		return [ChangeButtonStyleSheet];
	}
}

/**
 * @private
 * @return {ChangeButton}
 * @throws {Error} no shadow-root is defined
 * @throws {TypeError} the element must be a dataset
 * @throws {Error} the selector must match exactly one element
 * @throws {TypeError} the element must be a overlay
 * @throws {Error} the selector must match exactly one element
 * @throws {Error} no reference found
 * @throws {Error} reference is missing or empty
 * @throws {Error} index is not a number
 * @throws {TypeError} this must be a HTMLElement
 */
function initControlReferences() {
	if (!this.shadowRoot) {
		throw new Error("no shadow-root is defined");
	}

	const selector = this.getOption("dataset.selector");

	if (isString(selector)) {
		const element = findElementWithSelectorUpwards(this, selector);
		if (element === null) {
			throw new Error("the selector must match exactly one element");
		}

		if (!(element instanceof HTMLElement)) {
			throw new TypeError("the element must be a dataset");
		}

		this[datasetLinkedElementSymbol] = element;
	}

	const selector2 = this.getOption("overlay.selector");

	if (isString(selector2)) {
		const element = findElementWithSelectorUpwards(this, selector2);
		if (element === null) {
			throw new Error("the selector must match exactly one element");
		}

		if (!(element instanceof HTMLElement)) {
			throw new TypeError("the element must be a overlay");
		}

		this[overlayLinkedElementSymbol] = element;
	}

	this[stateButtonElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=state-button]",
	);

	if (this[stateButtonElementSymbol]) {
		queueMicrotask(() => {
			const states = {
				changed: new State(
					"changed",
					`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-record-circle" viewBox="0 0 16 16">
                                  <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
                                  <path d="M11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
                               </svg>`,
				),
			};

			this[stateButtonElementSymbol].removeState();
			this[stateButtonElementSymbol].setOption("states", states);
			this[stateButtonElementSymbol].setOption(
				"labels.button",
				this.getOption("labels.button"),
			);
		});
	}

	return this;
}

/**
 * @private
 * @returns {number}
 * @throws {TypeError}
 * @throws {Error}
 */
function getIndex() {
	if (!(this instanceof HTMLElement)) {
		throw new TypeError("this must be a HTMLElement");
	}

	const row = this.closest("[data-monster-insert-reference]");
	if (!row) {
		addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "no reference found");
		throw new Error("no reference found");
	}

	const ref = row.getAttribute("data-monster-insert-reference");
	if (!ref) {
		addAttributeToken(
			this,
			ATTRIBUTE_ERRORMESSAGE,
			"reference is missing or empty",
		);
		throw new Error("reference is missing or empty");
	}

	const index = Number(ref.split("-").pop());
	if (isNaN(index)) {
		throw new Error("index is not a number");
	}

	return index;
}

/**
 * @private
 */
function initEventHandler() {
	queueMicrotask(() => {
		this[stateButtonElementSymbol].setOption("actions.click", () => {
			try {
				const index = getIndex.call(this);

				if (!isNaN(index)) {
					this[datasetLinkedElementSymbol].setOption("mapping.index", index);
					this[overlayLinkedElementSymbol].open();
				}
			} catch (error) {
				addAttributeToken(
					this,
					ATTRIBUTE_ERRORMESSAGE,
					error.message || `${error}`,
				);
			}
		});
	});
}

/**
 * @private
 * @param {Object} options
 */
function updateOptionsFromArguments(options) {
	const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
	if (selector) {
		options.datasource.selector = selector;
	}
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control"
             data-monster-attributes="disabled path:disabled | if:true">
            <monster-state-button data-monster-role="state-button"></monster-state-button>

        </div>
    `;
}

registerCustomElement(ChangeButton);
