/**
 * 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 { Base } from "../types/base.mjs";
import { getGlobalFunction, getGlobalObject } from "../types/global.mjs";
import { validateInstance, validateString } from "../types/validate.mjs";
import { ATTRIBUTE_TEMPLATE_PREFIX } from "./constants.mjs";
import { getDocumentTheme } from "./theme.mjs";
import { instanceSymbol } from "../constants.mjs";
import { findElementWithIdUpwards } from "./util.mjs";

export { Template };

/**
 * The template class provides methods for creating templates.
 *
 * @fragments /fragments/libraries/dom/template/
 *
 * @example /examples/libraries/dom/template/simple/ Clone a named template fragment
 * @example /examples/libraries/dom/template/theme-specific/ Resolve theme specific templates by id suffix
 * @example /examples/libraries/dom/template/prefixed-lookup/ Resolve prefixed templates near the current node
 * @example /examples/libraries/dom/template/repeated-clones/ Clone the same template multiple times without mutating the source
 * @license AGPLv3
 * @since 1.6.0
 * @copyright Volker Schukai
 * @summary A template helper for resolving, cloning and preparing reusable DOM templates.
 */
class Template extends Base {
	/**
	 *
	 * @param {HTMLTemplateElement} template
	 * @throws {TypeError} value is not an instance of
	 * @throws {TypeError} value is not a function
	 * @throws {Error} the function is not defined
	 */
	constructor(template) {
		super();
		const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement");
		validateInstance(template, HTMLTemplateElement);
		this.template = template;
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 * @since 2.1.0
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/dom/resource/template");
	}

	/**
	 *
	 * @return {HTMLTemplateElement}
	 */
	getTemplateElement() {
		return this.template;
	}

	/**
	 *
	 * @return {DocumentFragment}
	 * @throws {TypeError} value is not an instance of
	 */
	createDocumentFragment() {
		return this.template.content.cloneNode(true);
	}
}

/**
 * This method loads a template with the given ID and returns it.
 *
 * To do this, it first reads the theme of the document and looks for the `data-monster-theme-name` attribute in the HTML tag.
 *
 * ```
 * <html data-monster-theme-name="my-theme">
 * ```
 *
 * If no theme was specified, the default theme is `monster`.
 *
 * Now it is looked if there is a template with the given ID and theme `id-theme` and if yes it is returned.
 * If there is no template a search for a template with the given ID `id` is done. If this is also not found, an error is thrown.
 *
 * You can call the method via the monster namespace `Monster.DOM.findDocumentTemplate()`.
 *
 * ```
 * <script type="module">
 * import {findTemplate} from '@schukai/monster/source/dom/template.mjs';
 * findDocumentTemplate()
 * </script>
 * ```
 *
 * @example
 *
 * import { findDocumentTemplate } from "https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs";
 *
 * const template = document.createElement("template");
 * template.id = "myTemplate";
 * template.innerHTML = "<p>my default template</p>";
 * document.body.appendChild(template);
 *
 * const themedTemplate = document.createElement("template");
 * themedTemplate.id = "myTemplate-myTheme";
 * themedTemplate.innerHTML = "<p>my themed template</p>";
 * document.body.appendChild(themedTemplate);
 *
 * // loads the temple and since no theme is set the default template
 * const template1 = findDocumentTemplate("myTemplate");
 * console.log(template1.createDocumentFragment());
 * // ↦ '<p>my default template</p>'
 *
 * // now we set our own theme
 * document
 * .querySelector("html")
 * .setAttribute("data-monster-theme-name", "myTheme");
 *
 * // now we don't get the default template,
 * // but the template with the theme in the id
 * const template2 = findDocumentTemplate("myTemplate");
 * console.log(template2.createDocumentFragment());
 * // ↦ '<p>my themed template</p>'
 *
 * @param {string} id
 * @param {Node} currentNode
 * @return {Monster.DOM.Template}
 * @license AGPLv3
 * @since 1.7.0
 * @copyright Volker Schukai
 * @throws {Error} template id not found.
 * @throws {TypeError} value is not a string
 */
export function findDocumentTemplate(id, currentNode) {
	validateString(id);

	const document = getGlobalObject("document");
	const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement");
	const DocumentFragment = getGlobalFunction("DocumentFragment");
	const Document = getGlobalFunction("Document");

	let prefixID;

	if (
		!(
			currentNode instanceof Document || currentNode instanceof DocumentFragment
		)
	) {
		if (currentNode instanceof Node) {
			if (currentNode.hasAttribute(ATTRIBUTE_TEMPLATE_PREFIX)) {
				prefixID = currentNode.getAttribute(ATTRIBUTE_TEMPLATE_PREFIX);
			}

			currentNode = currentNode.getRootNode();

			if (
				!(
					currentNode instanceof Document ||
					currentNode instanceof DocumentFragment
				)
			) {
				currentNode = currentNode.ownerDocument;
			}
		}

		if (
			!(
				currentNode instanceof Document ||
				currentNode instanceof DocumentFragment
			)
		) {
			currentNode = document;
		}
	}

	let template;
	const theme = getDocumentTheme();

	if (prefixID) {
		const themedPrefixID = `${prefixID}-${id}-${theme.getName()}`;

		template = findElementWithIdUpwards(currentNode, themedPrefixID);
		if (template instanceof HTMLTemplateElement) {
			return new Template(template);
		}
	}

	const themedID = `${id}-${theme.getName()}`;

	template = findElementWithIdUpwards(currentNode, themedID);
	if (template instanceof HTMLTemplateElement) {
		return new Template(template);
	}

	template = findElementWithIdUpwards(currentNode, id);
	if (template instanceof HTMLTemplateElement) {
		return new Template(template);
	}

	throw new Error(`template ${id} not found.`);
}
