/**
 * Copyright © schukai GmbH 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 schukai GmbH.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { instanceSymbol } from "../../constants.mjs";
import {
	assembleMethodSymbol,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { PopperButton } from "./popper-button.mjs";
import { ActionButtonStyleSheet } from "./stylesheet/action-button.mjs";
import { isObject, isIterable, isString, isFunction } from "../../types/is.mjs";
import { Observer } from "../../types/observer.mjs";
import { getDocumentTranslations } from "../../i18n/translations.mjs";
import {
	findTargetElementFromEvent,
	fireCustomEvent,
} from "../../dom/events.mjs";
import { addAttributeToken } from "../../dom/attributes.mjs";
import {
	ATTRIBUTE_ERRORMESSAGE,
	ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import { addErrorAttribute } from "../../dom/error.mjs";

export { ActionButton };

/**
 * @private
 * @type {symbol}
 */
const containerElementSymbol = Symbol("containerElement");

/**
 * A button that opens a popper element with possible actions.
 *
 * @fragments /fragments/components/form/action-button
 *
 * @example /examples/components/form/action-button
 *
 * @issue https://localhost.alvine.dev:8443/development/issues/closed/264.html
 *
 * @since 3.32.0
 * @copyright schukai GmbH
 * @summary The ActionButton is a button that opens a popper element with possible actions
 */
class ActionButton extends PopperButton {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/form/action-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} labels
	 * @property {string} labels.button The label of the button
	 * @property {object[]} buttons
	 * @property {string} buttons[].label The label of the button
	 * @property {string} buttons[].class The CSS class of the button
	 * @property {function} buttons[].action The action of the button
	 * @property {object} templates
	 * @property {string} templates.main The template of the button
	 * @extends {PopperButton.defaults}
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			labels: {
				button: "<slot></slot>",
			},
			classes: {
				button: "monster-button-primary",
			},
			buttons: [],
		});
	}

	/**
	 *
	 * @return {ActionButton}
	 * @fires monster-action-button-show-dialog
	 */
	showDialog() {
		if (this.getOption("buttons").length === 0) {
			return this;
		}

		return super.showDialog();
	}

	/**
	 *
	 * @return {ActionButton}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initControlReferences.call(this);
		initEventHandler.call(this);

		return this;
	}

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

	/**
	 * @return {CSSStyleSheet[]}
	 */
	static getCSSStyleSheet() {
		const styles = super.getCSSStyleSheet();
		styles.push(ActionButtonStyleSheet);
		return styles;
	}
}

/**
 * @private
 * @return {ActionButton}
 */
function initEventHandler() {
	this[containerElementSymbol].addEventListener("click", (event) => {
		const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "button");
		const attr = element.getAttribute("data-monster-insert-reference");
		if (attr) {
			const index = attr.split("-")[1];
			const b = this.getOption("buttons." + index);

			if (isObject(b) && isFunction(b?.action)) {
				b.action(event, element, this);
			}
		}
	});

	let memButtons = "";

	this.attachObserver(
		new Observer(() => {
			if (JSON.stringify(this.getOption("buttons")) !== memButtons) {
				try {
					updateButtonsI18n.call(this);
				} catch (e) {
					addErrorAttribute(this, e.message);
				}

				memButtons = JSON.stringify(this.getOption("buttons"));
			}
		}),
	);

	return this;
}

/**
 * @private
 * @returns {updateButtonsI18n}
 */
function updateButtonsI18n() {
	const translations = getDocumentTranslations();
	if (!translations) {
		return this;
	}

	const buttons = this.getOption("buttons");
	if (!isIterable(buttons)) {
		return this;
	}

	for (const key in buttons) {
		const def = buttons[key]["label"];

		if (isString(def)) {
			const text = translations.getText(def, def);
			if (text !== def) {
				this.setOption(`buttons.${key}.label`, text);
			}
			continue;
		}

		throw new Error("Invalid labels definition");
	}
	return this;
}

/**
 * @private
 * @return {ActionButton}
 */
function initControlReferences() {
	this[containerElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=container]`,
	);
	return this;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <template id="btn">
            <monster-message-state-button data-monster-role="button" part="button"
                                          data-monster-attributes="id path:btn.id, 
                                          data-monster-option-classes-button path:btn.class, 
                                          disabled path:btn.disabled | if:true, 
                                          data-monster-url path:btn.url"
                                          data-monster-option-actions-click=""
                                          data-monster-option-popper-placement="right"
                                          data-monster-replace="path:btn.label"></monster-message-state-button>
        </template>

        <div data-monster-role="control" part="control">
            <button data-monster-attributes="class path:classes.button"
                    data-monster-role="button" part="button"
                    data-monster-replace="path:labels.button"
            ></button>

            <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
                <div data-monster-role="arrow"></div>
                <div part="content" class="flex" data-monster-role="container">
                    <div part="buttons" data-monster-role="buttons" data-monster-insert="btn path:buttons"
                         tabindex="-1"></div>
                </div>
            </div>
        </div>
    `;
}

registerCustomElement(ActionButton);