/**
 * 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 {
	validateFunction,
	validateObject,
	validateString,
} from "./validate.mjs";

export { getGlobal, getGlobalObject, getGlobalFunction };

/**
 * @type {object}
 * @private
 */
let globalReference;

/**
 * @private
 * @throws {Error} unsupported environment.
 */
(function () {
	if (typeof globalThis === "object") {
		globalReference = globalThis;
		return;
	}

	if (typeof self !== "undefined") {
		globalReference = self;
		return;
	} else if (typeof window !== "undefined") {
		globalReference = window;
		return;
	}

	Object.defineProperty(Object.prototype, "__monster__", {
		get: function () {
			return this;
		},
		configurable: true,
	});

	if (typeof __monster__ === "object") {
		__monster__.globalThis = __monster__;
		delete Object.prototype.__monster__;

		globalReference = globalThis;
		return;
	}

	try {
		globalReference = Function("return this")();
	} catch (e) {}

	throw new Error("unsupported environment.");
})();

/**
 * Return globalThis
 *
 * If globalThis is not available, it will be polyfilled
 *
 * @license AGPLv3
 * @since 1.6.0
 * @return {object} globalThis
 */
function getGlobal() {
	return globalReference;
}

/**
 * Return global object or throw Error
 *
 * You can call the method via the monster namespace `Monster.Types.getGlobalObject()`.
 *
 * ```
 * <script type="module">
 * import {Monster} from '@schukai/monster/source/monster.mjs';
 * Monster.Types.getGlobalObject('document')
 * // ↦ { }
 * </script>
 * ```
 *
 * Alternatively, you can also integrate this function individually.
 *
 * ```
 * <script type="module">
 * import {getGlobalObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs';
 * getGlobalObject('document')
 * // ↦ { }
 * </script>
 * ```
 *
 * @license AGPLv3
 * @since 1.6.0
 * @param {string} name
 * @return {object}
 * @throws {Error} the object is not defined
 * @throws {TypeError} value is not a object
 * @throws {TypeError} value is not a string
 */
function getGlobalObject(name) {
	validateString(name);
	const o = globalReference?.[name];
	if (typeof o === "undefined")
		throw new Error(`the object ${name} is not defined`);
	validateObject(o);
	return o;
}

/**
 * Return global function or throw Error
 *
 * You can call the method via the monster namespace `Monster.Types.getGlobalFunction()`.
 *
 * ```
 * <script type="module">
 * import {Monster} from '@schukai/monster/source/monster.mjs';
 * console.log(Monster.Types.getGlobalFunction('parseInt')) // ↦ f parseInt() { }
 * </script>
 * ```
 *
 * Alternatively, you can also integrate this function individually.
 *
 * ```
 * <script type="module">
 * import {getGlobalFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs';
 * console.log(getGlobalFunction('parseInt')) // ↦ f parseInt() { }
 * </script>
 * ```
 *
 * @license AGPLv3
 * @since 1.6.0
 * @param {string} name
 * @return {object}
 * @throws {TypeError} value is not a function
 * @throws {Error} the function is not defined
 * @throws {TypeError} value is not a string
 */
function getGlobalFunction(name) {
	validateString(name);
	const f = globalReference?.[name];
	if (typeof f === "undefined") {
		throw new Error(`the function ${name} is not defined`);
	}
	validateFunction(f);
	return f;
}
