// SPDX-License-Identifier: MIT
// Copyright © 2021 fvtt-lib-wrapper Rui Pinheiro

"use strict";

// A shim for the libWrapper library
export let libWrapper = undefined;

export const VERSIONS = [1, 11, 0];
export const TGT_SPLIT_RE = new RegExp(
	"([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|\"([^\"\\\\]|\\\\.)+?\")\\])",
	"g",
);
export const TGT_CLEANUP_RE = new RegExp("(^\\['|'\\]$|^\\[\"|\"\\]$)", "g");

// Main shim code
Hooks.once("init", () => {
	// Check if the real module is already loaded - if so, use it
	if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
		libWrapper = globalThis.libWrapper;
		return;
	}

	// Fallback implementation
	libWrapper = class {
		static get is_fallback() {
			return true;
		}

		static get WRAPPER() {
			return "WRAPPER";
		}
		static get MIXED() {
			return "MIXED";
		}
		static get OVERRIDE() {
			return "OVERRIDE";
		}

		static register(package_id, target, fn, type = "MIXED", {chain = undefined} = {}) {
			const is_setter = target.endsWith("#set");
			target = !is_setter ? target : target.slice(0, -4);
			const split = target
				.match(TGT_SPLIT_RE)
				.map(x => x.replace(/\\(.)/g, "$1").replace(TGT_CLEANUP_RE, ""));
			const root_nm = split.splice(0, 1)[0];

			let obj, fn_name;
			if (split.length == 0) {
				obj = globalThis;
				fn_name = root_nm;
			} else {
				const _eval = eval;
				fn_name = split.pop();
				obj = split.reduce((x, y) => x[y], globalThis[root_nm] ?? _eval(root_nm));
			}

			let iObj = obj;
			let descriptor = null;
			while (iObj) {
				descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
				if (descriptor) break;
				iObj = Object.getPrototypeOf(iObj);
			}
			if (!descriptor || descriptor?.configurable === false)
				throw `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`;

			let original = null;
			const wrapper =
				chain ?? (type.toUpperCase?.() != "OVERRIDE" && type != 3)
					? function () {
							return fn.call(this, original.bind(this), ...arguments);
					  }
					: function () {
							return fn.apply(this, arguments);
					  };

			if (!is_setter) {
				if (descriptor.value) {
					original = descriptor.value;
					descriptor.value = wrapper;
				} else {
					original = descriptor.get;
					descriptor.get = wrapper;
				}
			} else {
				if (!descriptor.set) throw `libWrapper Shim: '${target}' does not have a setter`;
				original = descriptor.set;
				descriptor.set = wrapper;
			}

			descriptor.configurable = true;
			Object.defineProperty(obj, fn_name, descriptor);
		}
	};
});
