/**
 * 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 { internalSymbol } from "../../constants.mjs";
import { extend } from "../../data/extend.mjs";
import { getDocument } from "../../dom/util.mjs";
import { isString } from "../../types/is.mjs";
import { validateObject, validateString } from "../../types/validate.mjs";
import { parseLocale } from "../locale.mjs";
import { Provider } from "../provider.mjs";
import { Translations } from "../translations.mjs";

export { Embed };

/**
 * The Embed provider retrieves a JSON file from the given Script Tag.
 *
 * @externalExample ../../../example/i18n/providers/embed.mjs
 * @license AGPLv3
 * @since 1.13.0
 * @copyright Volker Schukai
 * @summary A translation provider that reads embedded locale resources from the current document.
 * @see {@link https://datatracker.ietf.org/doc/html/rfc3066}
 */
class Embed extends Provider {
	/**
	 * ```html
	 * <script id="translations" type="application/json">
	 * {
	 *     "hello": "Hallo"
	 * }
	 * </script>
	 * ```
	 *
	 *
	 * ```javascript
	 * new Embed('translations')
	 * ```
	 *
	 * @param {HTMLElement|string} elementOrId
	 * @param {Object} options
	 */
	constructor(elementOrId, options) {
		super(options);

		if (options === undefined) {
			options = {};
		}

		if (elementOrId instanceof HTMLElement) {
			/**
			 * @property {HTMLElement|string}
			 */
			this.translateElement = elementOrId;
		} else {
			/**
			 * @property {HTMLElement|string}
			 */
			this.translateElement = getDocument().getElementById(
				validateString(elementOrId),
			);
		}

		/**
		 * @private
		 * @property {Object} options
		 */
		this[internalSymbol] = extend(
			{},
			super.defaults,
			this.defaults,
			validateObject(options),
		);
	}

	/**
	 * Defaults
	 *
	 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API}
	 */
	get defaults() {
		return extend({}, super.defaults);
	}

	/**
	 *
	 * @param {Locale|string} locale
	 * @return {Promise}
	 */
	getTranslations(locale) {
		if (isString(locale)) {
			locale = parseLocale(locale);
		}

		return new Promise((resolve, reject) => {
			if (this.translateElement === null) {
				reject(new Error("Text not found"));
				return;
			}

			if (!(this.translateElement instanceof HTMLScriptElement)) {
				reject(new Error("Element is not a script tag"));
				return;
			}

			if (this.translateElement.type !== "application/json") {
				reject(
					new Error("Element is not a script tag with type application/json"),
				);
				return;
			}

			let translations = null;
			try {
				translations = JSON.parse(this.translateElement.innerHTML.trim());
			} catch (e) {
				reject(e);
				return;
			}

			if (translations === null) {
				reject(new Error("Translations not found or invalid"));
				return;
			}

			const t = new Translations(locale);
			t.assignTranslations(translations);

			resolve(t);
		});
	}

	/**
	 * Initializes the translations for the current document.
	 *
	 * `script[data-monster-role=translations]` is searched for and the translations are assigned to the element.
	 *
	 * @param element
	 * @returns {Promise<Awaited<*[]>>|Promise<Awaited<unknown>[]>}
	 */
	static assignTranslationsToElement(element) {
		const d = getDocument();

		if (!(element instanceof HTMLElement)) {
			element = d.querySelector("body");
		}

		const list = d.querySelectorAll("script[data-monster-role=translations]");
		if (list === null) {
			return Promise.resolve([]);
		}

		const promises = [];

		list.forEach((translationElement) => {
			const p = new Embed(translationElement, {});
			promises.push(p.assignToElement(undefined, element));
		});

		return Promise.all(promises);
	}
}
