/**
 * 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 { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
import {
	assembleMethodSymbol,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { isArray, isString } from "../../types/is.mjs";
import { validateString } from "../../types/validate.mjs";
import { Popper } from "./popper.mjs";
import { MessageStateButtonStyleSheet } from "./stylesheet/message-state-button.mjs";
import { StateButtonStyleSheet } from "./stylesheet/state-button.mjs";
import "./state-button.mjs";
import { isFunction } from "../../types/is.mjs";

export { MessageStateButton };

/**
 * @private
 * @type {symbol}
 */
const buttonElementSymbol = Symbol("buttonElement");

/**
 * A select control that can be used to select one or more options from a list.
 *
 * @fragments /fragments/components/form/message-state-button/
 *
 * @example /examples/components/form/message-state-button-simple
 *
 * @since 2.11.0
 * @copyright schukai GmbH
 * @summary A beautiful select control that can make your life easier and also looks good.
 * @fires monster-options-set
 * @fires monster-selected
 * @fires monster-change
 * @fires monster-changed
 */
class MessageStateButton extends Popper {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/form/message-state-button@@instance",
		);
	}

	/**
	 *
	 * @param {string} state
	 * @param {number} timeout
	 * @return {Monster.Components.Form.MessageStateButton}
	 * @throws {TypeError} value is not a string
	 * @throws {TypeError} value is not an instance
	 */
	setState(state, timeout) {
		return this[buttonElementSymbol].setState(state, timeout);
	}

	/**
	 *
	 * @return {Monster.Components.Form.MessageStateButton}
	 */
	removeState() {
		return this[buttonElementSymbol].removeState();
	}

	/**
	 * @return {Monster.Components.Form.Types.State|undefined}
	 */
	getState() {
		return this[buttonElementSymbol].getState();
	}

	/**
	 * 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} labels Label definitions
	 * @property {string} labels.button Button label
	 * @property {Object} mode Mode definitions (manual, submit)
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			message: {
				title: undefined,
				content: undefined,
				icon: undefined,
			},
			templates: {
				main: getTemplate(),
			},
			mode: "manual",
			labels: {
				button: "<slot></slot>",
			},
			classes: {
				button: "monster-button-primary",
			},
			actions: {
				click: (e) => {
					throw new Error("the click action is not defined");
				},
			},
			features: {
				disableButton: false,
			},
		});
	}

	/**
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initControlReferences.call(this);

		let modes = null;
		const modeOption = this.getOption("mode");
		if (typeof modeOption === "string") {
			modes = modeOption.split(" ");
		}

		if (
			modes === null ||
			modes === undefined ||
			isArray(modes) === false ||
			modes.length === 0
		) {
			modes = ["manual"];
		}

		for (const [, mode] of Object.entries(modes)) {
			initEventHandlerByMode.call(this, mode);
		}

		return this;
	}

	/**
	 * Sets the message
	 *
	 * @param {string|HTMLElement}message
	 * @param {string} title
	 * @param {string} icon
	 * @return {Monster.Components.Form.MessageStateButton}
	 */
	setMessage(message, title, icon) {
		if (isString(message)) {
			if (message === "") {
				throw new TypeError("message must not be empty");
			}

			const containerDiv = document.createElement("div");
			const messageDiv = document.createElement("div");
			const titleDiv = document.createElement("div");
			titleDiv.setAttribute("data-monster-role", "message-title-box");

			let titleElement, iconElement;
			if (title !== undefined) {
				title = validateString(title);
				titleElement = document.createElement("div");
				titleElement.setAttribute("class", "");
				titleElement.innerHTML = title;
				titleElement.setAttribute("data-monster-role", "message-title");
				titleDiv.appendChild(titleElement);
			}

			if (icon !== undefined) {
				icon = validateString(icon);
				iconElement = document.createElement("div");
				iconElement.setAttribute("class", "");
				iconElement.innerHTML = icon;
				iconElement.setAttribute("data-monster-role", "message-icon");
				titleDiv.appendChild(iconElement);
			}

			messageDiv.innerHTML = message;
			containerDiv.appendChild(titleDiv);
			containerDiv.appendChild(messageDiv);

			this.setOption("message.content", containerDiv);
		} else if (message instanceof HTMLElement) {
			this.setOption("message.content", message);
		} else {
			throw new TypeError(
				"message must be a string or an instance of HTMLElement",
			);
		}

		return this;
	}

	/**
	 * clears the Message
	 *
	 * @return {Monster.Components.Form.MessageStateButton}
	 */
	clearMessage() {
		this.setOption("message.title", undefined);
		this.setOption("message.content", undefined);
		this.setOption("message.icon", undefined);
		return this;
	}

	/**
	 * With this method you can show the popper with timeout feature.
	 *
	 * @param {number} timeout
	 * @return {MessageStateButton}
	 */
	showMessage(timeout) {
		this.showDialog.call(this);

		if (timeout !== undefined) {
			setTimeout(() => {
				super.hideDialog();
			}, timeout);
		}

		return this;
	}

	/**
	 * With this method you can show the popper.
	 *
	 * @return {MessageStateButton}
	 */
	showDialog() {
		if (this.getOption("message.content") === undefined) {
			return;
		}
		super.showDialog();
		return this;
	}

	/**
	 *
	 * @return {Monster.Components.Form.MessageStateButton}
	 */
	hideMessage() {
		super.hideDialog();
		return this;
	}

	/**
	 *
	 * @return {Monster.Components.Form.MessageStateButton}
	 */
	toggleMessage() {
		super.toggleDialog();
		return this;
	}

	/**
	 *
	 * @return {Object}
	 */
	getMessage() {
		return this.getOption("message");
	}

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

	/**
	 *
	 * @return {Array<CSSStyleSheet>}
	 */
	static getCSSStyleSheet() {
		const styles = Popper.getCSSStyleSheet();
		styles.push(StateButtonStyleSheet);
		styles.push(MessageStateButtonStyleSheet);
		return styles;
	}

	/**
	 * The Button.click() method simulates a click on the internal button element.
	 *
	 * @since 3.27.0
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click}
	 */
	click() {
		if (this.getOption("disabled") === true) {
			return;
		}

		if (
			this[buttonElementSymbol] &&
			isFunction(this[buttonElementSymbol].click)
		) {
			this[buttonElementSymbol].click();
		}
	}

	/**
	 * The Button.focus() method sets focus on the internal button element.
	 *
	 * @since 3.27.0
	 * @param {Object} options
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus}
	 */
	focus(options) {
		if (this.getOption("disabled") === true) {
			return;
		}

		if (
			this[buttonElementSymbol] &&
			isFunction(this[buttonElementSymbol].focus)
		) {
			this[buttonElementSymbol].focus(options);
		}
	}

	/**
	 * The Button.blur() method removes focus from the internal button element.
	 */
	blur() {
		if (
			this[buttonElementSymbol] &&
			isFunction(this[buttonElementSymbol].blur)
		) {
			this[buttonElementSymbol].blur();
		}
	}
}

function initEventHandlerByMode(mode) {
	switch (mode) {
		case "manual":
			this[buttonElementSymbol].setOption("actions.click", (e) => {
				const callback = this.getOption("actions.click");
				if (isFunction(callback)) {
					callback(e);
				}
			});

			break;
		case "submit":
			this[buttonElementSymbol].setOption("actions.click", (e) => {
				const form = this.form;

				if (form instanceof HTMLFormElement) {
					form.requestSubmit();
				}
			});

			break;
	}
}

/**
 * @private
 * @return {Select}
 */
function initControlReferences() {
	this[buttonElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}=button]`,
	);
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control">

            <monster-state-button exportparts="button:button-button,control:button-control"
                                  data-monster-attributes="data-monster-option-classes-button path:classes.button, disabled path:features.disableButton | if:true"
                                  part="button"
                                  name="button"
                                  data-monster-role="button">
                <span data-monster-replace="path:labels.button"></span>
            </monster-state-button>


            <div data-monster-role="popper" part="popper" tabindex="-1" class="monster-color-primary-1">
                <div data-monster-role="arrow"></div>
                <div data-monster-role="message" part="message" class="flex"
                     data-monster-replace="path:message.content"></div>
            </div>
        </div>
        </div>
    `;
}

registerCustomElement(MessageStateButton);