/**
 * 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 { registerCustomElement } from "../../dom/customelement.mjs";
import { isInteger } from "../../types/is.mjs";
import { validateInstance, validateString } from "../../types/validate.mjs";
import { Button } from "./button.mjs";
import { StateButtonStyleSheet } from "./stylesheet/state-button.mjs";
import { getStateInstanceFor, State } from "./types/state.mjs";

export { StateButton };

/**
 * A state button with icons
 *
 * @fragments /fragments/components/form/state-button/
 *
 * @example /examples/components/form/state-button-simple Simple state cycle
 * @example /examples/components/form/state-button-deployment Deployment state sequence
 *
 * @since 1.5.0
 * @copyright Volker Schukai
 * @summary A beautiful button with icons
 */
class StateButton extends Button {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 * @since 2.1.0
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/form/state-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} states Available status
	 * @property {State} states.successful= successful
	 * @property {State} states.activity= activity
	 * @property {State} states.failed= failed
	 * @property {State} current current status
	 * @property {object} aria Aria attributes
	 * @property {string} aria.role Aria role, only if the button is not a button
	 * @property {string} aria.label Aria label
	 * @extends {Button}
	 * @see {@link https://github.com/twbs/icons/blob/main/LICENSE.md|Bootstrap icons license}
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},

			states: {
				successful: getStateInstanceFor("successful"),
				activity: getStateInstanceFor("activity"),
				failed: getStateInstanceFor("failed"),
			},

			current: getStateInstanceFor("stateless"),

			aria: {
				role: null,
				label: null,
			},
		});
	}

	/**
	 * This method sets the current state of the button.
	 * If a timeout is set, the state is automatically removed after the
	 * specified time.
	 *
	 * @since 3.18.0 a previously set timeout is cleared
	 *
	 * @param {string} state
	 * @param {number} timeout
	 * @return {StateButton}
	 * @throws {TypeError} value is not a string
	 * @throws {TypeError} value is not an instance
	 */
	setState(state, timeout) {
		const timeoutSymbol = Symbol.for("timeout");
		if (this[timeoutSymbol] !== undefined) {
			clearTimeout(this[timeoutSymbol]);
			delete this[timeoutSymbol];
		}

		const obj = this.getOption(`states.${validateString(state)}`);
		if (obj === undefined) {
			throw new Error("state not found");
		}

		this.setOption("current", validateInstance(obj, State));

		if (isInteger(timeout) && timeout > 0) {
			this[timeoutSymbol] = setTimeout(() => {
				this.removeState();
				delete this[timeoutSymbol];
			}, timeout);
		}

		return this;
	}

	/**
	 *
	 * @return {StateButton}
	 */
	removeState() {
		this.setOption("current", getStateInstanceFor("stateless"));
		return this;
	}

	/**
	 * @return {State|undefined}
	 */
	getState() {
		return this.getOption("current");
	}

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

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

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `<div data-monster-role="control" part="control">
    <button data-monster-attributes="disabled path:disabled | if:true, class path:classes.button, aria-role path:aria.role"
            data-monster-role="button"
			aria-labelledby="monster-state-button-aria-label"
            part="button">
        <div data-monster-role="label" data-monster-replace="path:labels.button"></div>
        <div data-monster-role="state"
             data-monster-attributes="class path:current.state"
             data-monster-replace="path:current.presentation"></div>
    </button>
		<div id="monster-state-button-aria-label"
			 class="visually-hidden" data-monster-replace="path:aria.label">click me
		</div>
</div>`;
}

registerCustomElement(StateButton);
