/**
 * 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 {
	assembleMethodSymbol,
	CustomElement,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { findElementWithSelectorUpwards } from "../../dom/util.mjs";
import { ThemeStyleSheet } from "../stylesheet/theme.mjs";
import { Datasource } from "./datasource.mjs";
import { SpinnerStyleSheet } from "../stylesheet/spinner.mjs";
import { isString } from "../../types/is.mjs";
import { instanceSymbol } from "../../constants.mjs";
import "../form/select.mjs";

import "./datasource/dom.mjs";
import "./datasource/rest.mjs";
import "../form/popper.mjs";
import "../form/context-error.mjs";
import { StatusStyleSheet } from "./stylesheet/status.mjs";

export { DatasourceStatus };

/**
 * @private
 * @type {symbol}
 */
const errorElementSymbol = Symbol.for("errorElement");

/**
 * @private
 * @type {symbol}
 */
const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement");

/**
 * The Status component is used to show the current status of a datasource.
 *
 * <img src="./images/datasource-status.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-datatable-status />` directly in the HTML or using
 * Javascript via the `document.createElement('monster-pagination');` method.
 *
 * ```html
 * <monster-pagination></monster-pagination>
 * ```
 *
 * Or you can create this CustomControl directly in Javascript:
 *
 * ```js
 * import '@schukai/component-datatable/source/pagination.mjs';
 * document.createElement('monster-pagination');
 * ```
 *
 * @startuml datasource-status.png
 * skinparam monochrome true
 * skinparam shadowing false
 * HTMLElement <|-- CustomElement
 * CustomElement <|-- Pagination
 * @enduml
 *
 * @copyright schukai GmbH
 * @memberOf Monster.Components.Datatable
 * @summary A datatable
 */
class DatasourceStatus extends CustomElement {
	/**
	 */
	constructor() {
		super();
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @returns {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/datatables/status@@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 Datasource configuration
	 * @property {string} datasource.selector The selector of the datasource
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},

			datasource: {
				selector: null,
			},

			timeouts: {
				message: 4000,
			},

			state: {
				spinner: "hide",
			},
		});
	}

	/**
	 *
	 * @return {string}
	 */
	static getTag() {
		return "monster-datasource-status";
	}

	/**
	 * @private
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();

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

	/**
	 *
	 * @return [CSSStyleSheet]
	 */
	static getCSSStyleSheet() {
		return [StatusStyleSheet, SpinnerStyleSheet, ThemeStyleSheet];
	}
}

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

	this[errorElementSymbol] = this.shadowRoot.querySelector(
		"monster-context-error",
	);
}

/**
 * @private
 */
function initEventHandler() {
	const selector = this.getOption("datasource.selector", "");
	const self = this;

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

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

		this[datasourceLinkedElementSymbol] = element;
		element.addEventListener("monster-datasource-fetched", function () {
			self.setOption("state.spinner", "hide");
		});

		element.addEventListener("monster-datasource-fetch", function () {
			self.setOption("state.spinner", "show");
		});

		element.addEventListener("monster-datasource-error", function (event) {
			self.setOption("state.spinner", "hide");

			const timeout = self.getOption("timeouts.message", 4000);
			let msg = "Cannot load data";

			try {
				if (event.detail.error instanceof Error) {
					msg = event.detail.error.message;
				} else if (event.detail.error instanceof Object) {
					msg = JSON.stringify(event.detail.error);
				} else if (event.detail.error instanceof String) {
					msg = event.detail.error;
				} else if (event.detail.error instanceof Number) {
					msg = event.detail.error.toString();
				} else {
					msg = event.detail.error;
				}
			} catch (e) {
			} finally {
				self[errorElementSymbol].setErrorMessage(msg, timeout);
			}
		});
	}
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
	<div data-monster-role="control" part="control"
    	     data-monster-attributes="disabled path:disabled | if:true">
            <monster-context-error
                    data-monster-option-classes-button="monster-theme-error-2 monster-theme-background-inherit"></monster-context-error>
            <div class="monster-theme-control-element"
                 data-monster-attributes="data-monster-state-loader path:state.spinner"></div>
        </div>
    `;
}

registerCustomElement(DatasourceStatus);