/**
 * 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 {
	assembleMethodSymbol,
	CustomElement,
	registerCustomElement,
	updaterTransformerMethodsSymbol,
} from "../../../dom/customelement.mjs";
import { HtmlStyleSheet } from "./stylesheet/html.mjs";
import { sanitizeHtml } from "../../../dom/sanitize-html.mjs";

export { HtmlContent };

/**
 * @private
 * @type {symbol}
 */
const htmlContentElementSymbol = Symbol("htmlContentElement");

/**
 * The HtmlContent component is used to display arbitrary HTML content within its shadow DOM.
 * It provides options for sanitization to prevent XSS attacks.
 *
 * @copyright Volker Schukai
 * @summary An HTML content component that can display sanitized HTML.
 */
class HtmlContent extends CustomElement {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/content/viewer/html-content@@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 {string} content The HTML string to be displayed.
	 * @property {Object} features Features to enable or disable specific functionalities.
	 * @property {boolean} features.sanitize Whether to sanitize the HTML content (removes scripts, etc.). Defaults to true.
	 * @property {object} sanitize Sanitization options.
	 * @property {function} sanitize.callback A callback function to sanitize the HTML content. Defaults to a built-in sanitizer. You can use libraries like DOMPurify for this purpose.
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			content: "", // Default content is an empty slot

			features: {
				sanitize: true,
			},

			sanitize: {
				callback: sanitizeHtml.bind(this),
			},
		});
	}

	/**
	 * Returns the updater transformer methods for this component.
	 * @returns {{sanitizeHtml: ((function(*): (*))|*)}}
	 */
	[updaterTransformerMethodsSymbol]() {
		return {
			sanitizeHtml: (value) => {
				if (this.getOption("features.sanitize")) {
					return this.getOption("sanitize.callback")(value);
				}
				return value;
			},
			addErrorHandler: (value) => {
				// Add error handling for images
				if (typeof value === "string") {
					const parser = new DOMParser();
					const doc = parser.parseFromString(value, "text/html");
					const images = doc.querySelectorAll("img");
					images.forEach((img) => {
						img.setAttribute(
							"onerror",
							"this.classList.add('notFoundImage'); this.src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';",
						);
						img.classList.add("notFoundError");
					});

					return doc.body.innerHTML;
				}
				return value;
			},
		};
	}

	/**
	 * Sets the content of the HtmlContent component.
	 * @param content
	 * @returns {HtmlContent}
	 */
	setContent(content) {
		this.setOption("content", content);
		return this;
	}

	/**
	 * @return {string}
	 */
	static getTag() {
		return "monster-html-content";
	}

	/**
	 * @return {HtmlContent}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initControlReferences.call(this);
		initEventHandler.call(this);
		grabChildren.call(this);
	}

	/**
	 * @return {Array}
	 */
	static getCSSStyleSheet() {
		return [HtmlStyleSheet];
	}
}

function grabChildren() {
	// If there are children, we grab them and set the content
	if (this.children.length > 0) {
		const content = Array.from(this.children)
			.map((child) => child.outerHTML)
			.join("");
		this.setContent(content);

		this.innerHTML = "";
	}
}

/**
 * @private
 * @return {HtmlContent}
 */
function initEventHandler() {
	return this;
}

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

	this[htmlContentElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=content-container]",
	);
	return this;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <div data-monster-role="content-container" part="content-container"
             data-monster-replace="path:content | call:sanitizeHtml | call:addErrorHandler | default: "
        ></div>
    `;
}

registerCustomElement(HtmlContent);
