/**
 * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
 * Node module: @schukai/monster
 * This file is licensed under the AGPLv3 License.
 * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
 */

import {
	ATTRIBUTE_ERRORMESSAGE,
	ATTRIBUTE_PREFIX,
	ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";
import {
	assembleMethodSymbol,
	CustomElement,
	initMethodSymbol,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { findTargetElementFromEvent } from "../../dom/events.mjs";
import { isString } from "../../types/is.mjs";
import { MessageStyleSheet } from "./stylesheet/message.mjs";

export { Message };

/**
 * @private
 * @type {symbol}
 */
const controlElementSymbol = Symbol("controlElement");

/**
 * @private
 * @type {symbol}
 */
const removeElementSymbol = Symbol("removeElement");

/**
 * @private
 * @type {symbol}
 */
const timerSymbol = Symbol("timer");

/**
 * @private
 * @type {symbol}
 */
const mouseenterEventHandlerSymbol = Symbol("mouseenterEventHandler");

/**
 * @private
 * @type {symbol}
 */
const mouseleaveEventHandlerSymbol = Symbol("mouseleaveEventHandler");

/**
 * @private
 * @type {symbol}
 */
const removeEventHandlerSymbol = Symbol("removeEventHandler");

/**
 * This CustomControl creates a notification element with a variety of options.
 *
 * <img src="./images/message.png">
 *
 * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library.
 *
 * You can create this control either by specifying the HTML tag `<monster-notify-message />` directly in the HTML
 *
 * ```html
 * <monster-notify-message></monster-notify-message>
 * ```
 *
 * or using Javascript via the `document.createElement('monster-notify');` method.
 *
 * ```javascript
 * import '@schukai/monster/source/components/notify/message.js';
 * document.createElement('monster-notify-message');
 * ```
 *
 * @externalExample ../../../example/components/notify/message.mjs
 * @startuml message.png
 * skinparam monochrome true
 * skinparam shadowing false
 * HTMLElement <|-- CustomElement
 * CustomElement <|-- Message
 * @enduml
 * @since 1.0.0
 * @copyright schukai GmbH
 * @memberOf Monster.Components.Notify
 * @summary A highly configurable select control
 * @fires Monster.Components.Notify.event:monster-xxxx
 */
class Message extends CustomElement {
	/**
	 * The defaults can be set either directly in the object or via an attribute in the HTML tag.
	 * The value of the attribute `data-monster-options` in the HTML tag must be a JSON string.
	 *
	 * ```
	 * <monster-message data-monster-options="{}"></monster-message>
	 * ```
	 *
	 * Since 1.18.0 the JSON can be specified as a DataURI.
	 *
	 * ```
	 * new Monster.Types.DataUrl(btoa(JSON.stringify({
	 *        timeout: 3000,
	 *        features: {
	 *          close: true,
	 *          disappear: true
	 *        }
	 *    })),'application/json',true).toString()
	 * ```
	 *
	 * @property {string} templates Template definitions
	 * @property {Object} templates Template definitions
	 * @property {integer} timeout time in milliseconds until the message should be removed. The timeout can be disabled via the feature `disappear`.
	 * @property {Object} features
	 * @property {boolean} features.close show close button
	 * @property {boolean} features.disappear automatically remove the message after the timeout
	 * @property {string} templates.main Main template
	 *
	 */
	get defaults() {
		return Object.assign(
			{},
			super.defaults,
			{
				timeout: 6000,
				features: {
					close: true,
					disappear: true,
				},
				content: "<slot></slot>",
				templates: {
					main: getTemplate(),
				},
			},
			initOptionsFromArguments.call(this),
		);
	}

	/**
	 *
	 * @return {Monster.Components.Notify.Message}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initControlReferences.call(this);
		initEventhandler.call(this);
		return this;
	}

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

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

	/**
	 *
	 */
	[initMethodSymbol]() {
		super[initMethodSymbol]();
	}

	/**
	 * @return {void}
	 */
	connectedCallback() {
		super.connectedCallback();

		if (this.getOption("features.disappear") === true) {
			startFadeout.call(this);
			this.addEventListener("mouseenter", this[mouseenterEventHandlerSymbol]);
		}
	}

	/**
	 * @return {void}
	 */
	disconnectedCallback() {
		super.disconnectedCallback();
		stopFadeout.call(this);

		if (this.getOption("features.disappear") === true) {
			this.removeEventListener(
				"mouseenter",
				this[mouseenterEventHandlerSymbol],
			);
			this.removeEventListener(
				"mouseenter",
				this[mouseleaveEventHandlerSymbol],
			);
		}
	}
}

/**
 * @private
 */
function startFadeout() {
	if (!this?.[timerSymbol]) {
		this[timerSymbol] = setTimeout(() => {
			removeSelf.call(this);
		}, this.getOption("timeout", 1000));
	}
}

function removeSelf() {
	stopFadeout();
	this.classList.add("fadeout");

	setTimeout(() => {
		this.remove();
	}, 200);
}

/**
 * @private
 */
function stopFadeout() {
	if (this?.[timerSymbol]) {
		clearTimeout(this[timerSymbol]);
		this[timerSymbol] = undefined;
	}
}

/**
 * This attribute can be used to pass a URL to this select.
 *
 * @private
 * @return {object}
 */
function initOptionsFromArguments() {
	const options = {};

	const timeout = this.getAttribute(ATTRIBUTE_PREFIX + "timeout");
	if (isString(timeout)) {
		try {
			options["timeout"] = parseInt(timeout, 10);
		} catch (e) {
			this.setAttribute(
				ATTRIBUTE_ERRORMESSAGE,
				this.getAttribute(ATTRIBUTE_ERRORMESSAGE + ", " + e.toString()),
			);
		}
	}

	return options;
}

/**
 * @private
 * @return {Message}
 * @throws {Error} no shadow-root is defined
 */
function initControlReferences() {
	if (!this.shadowRoot) {
		throw new Error("no shadow-root is defined");
	}

	this[controlElementSymbol] = this.shadowRoot.querySelector(
		"[" + ATTRIBUTE_ROLE + "=control]",
	);
	this[removeElementSymbol] = this.shadowRoot.querySelector(
		"[" + ATTRIBUTE_ROLE + "=close]",
	);
}

/**
 * @private
 */
function initEventhandler() {
	/**
	 * @param {Event} event
	 */
	this[mouseenterEventHandlerSymbol] = (event) => {
		const element = findTargetElementFromEvent(
			event,
			ATTRIBUTE_ROLE,
			"control",
		);

		if (element instanceof HTMLElement) {
			this.removeEventListener(
				"mouseenter",
				this[mouseenterEventHandlerSymbol],
			);
			this.addEventListener("mouseleave", this[mouseleaveEventHandlerSymbol]);
			stopFadeout.call(this);
		}
	};

	/**
	 * @param {Event} event
	 */
	this[mouseleaveEventHandlerSymbol] = (event) => {
		const element = findTargetElementFromEvent(
			event,
			ATTRIBUTE_ROLE,
			"control",
		);
		if (element instanceof HTMLElement) {
			this.removeEventListener(
				"mouseleave",
				this[mouseleaveEventHandlerSymbol],
			);
			this.addEventListener("mouseenter", this[mouseenterEventHandlerSymbol]);
			startFadeout.call(this);
		}
	};

	/**
	 * @param {Event} event
	 */
	this[removeEventHandlerSymbol] = (event) => {
		const element = findTargetElementFromEvent(event, ATTRIBUTE_ROLE, "close");

		if (element instanceof HTMLElement) {
			removeSelf.call(this);
		}
	};

	if (this.getOption("features.close") === true) {
		this[removeElementSymbol].addEventListener(
			"click",
			this[removeEventHandlerSymbol],
		);
		this[removeElementSymbol].addEventListener(
			"touch",
			this[removeEventHandlerSymbol],
		);
	}

	return this;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="control" part="control" class="center">
            <div data-monster-role="message" part="message"
                 data-monster-attributes="data-monster-orientation path:orientation">
                <div data-monster-replace="path:content" part="content"
                     data-monster-role="content">

                </div>
                <div part="remove"
                     data-monster-attributes="class path:features.close | ?::hidden "
                     data-monster-role="close" tabindex="-1"></div>
            </div>
        </div>
    `;
}

registerCustomElement(Message);