/**
 * 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 {
	hasObjectLink,
	getLinkedObjects,
	addToObjectLink,
} from "../dom/attributes.mjs";
import { getLocaleOfDocument } from "../dom/locale.mjs";
import { BaseWithOptions } from "../types/basewithoptions.mjs";
import { Locale } from "./locale.mjs";
import { Translations } from "./translations.mjs";

export { Provider, translationsLinkSymbol };

/**
 * @type {symbol}
 * @license AGPLv3
 * @since 3.9.0
 * @private
 */
const translationsLinkSymbol = Symbol.for(
	"@schukai/monster/i18n/translations@@link",
);

/**
 * A provider makes a translation object available.
 *
 * @fragments /fragments/libraries/i18n/provider/
 *
 * @example /examples/libraries/i18n/provider/simple/ Assign translations to a document
 * @example /examples/libraries/i18n/provider/merge-existing/ Merge a second translation payload into an already linked element
 * @example /examples/libraries/i18n/provider/subtree-assignment/ Attach translations to a dedicated subtree instead of the whole document
 * @example /examples/libraries/i18n/provider/locale-switch/ Resolve different translation objects for different locales
 * @license AGPLv3
 * @since 1.13.0
 * @copyright Volker Schukai
 * @summary A translation provider base class for loading and attaching locale-dependent text sources.
 * @see {@link https://datatracker.ietf.org/doc/html/rfc3066}
 */
class Provider extends BaseWithOptions {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 * @since 3.27.0
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/i18n/provider@@instance");
	}

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

		return new Promise((resolve, reject) => {
			try {
				resolve(new Translations(locale));
			} catch (e) {
				reject(e);
			}
		});
	}

	/**
	 * @param {Locale|string} locale
	 * @param {HTMLElement} element
	 * @return {Provider}
	 */
	assignToElement(locale, element) {
		if (locale === undefined) {
			locale = getLocaleOfDocument();
		}

		if (!(locale instanceof Locale)) {
			throw new Error("Locale is not an instance of Locale");
		}

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

		if (!(element instanceof HTMLElement)) {
			throw new Error("Element is not an HTMLElement");
		}

		return this.getTranslations(locale).then((obj) => {
			let translations = null;
			if (hasObjectLink(element, translationsLinkSymbol)) {
				const objects = getLinkedObjects(element, translationsLinkSymbol);
				for (const o of objects) {
					if (o instanceof Translations) {
						translations = o;
						break;
					}
				}

				if (!(translations instanceof Translations)) {
					throw new Error("Object is not an instance of Translations");
				}

				translations.assignTranslations(obj);
			} else {
				addToObjectLink(element, translationsLinkSymbol, obj);
			}

			return obj;
		});
	}
}
