/* eslint-disable no-unused-vars */
/**
 * @file _gameModuleLoader.js
 * @description Game Module System for the MATTIE Mod Engine.
 *
 * Provides a registration, selection, and application system that decouples
 * game-specific data (map IDs, actor IDs, switch arrays, etc.) from the
 * generic RPG Maker MV mod engine.
 *
 * A "game module" is a plain object that describes one specific game's data
 * and must implement the interface defined below. When loaded, it calls
 * MATTIE.static.registerGameModule(module) to register itself.
 *
 * The engine selects the correct module at runtime by calling each module's
 * versionMatch() function and applying the first match.
 *
 * @see MATTIE.static.registerGameModule
 * @see MATTIE.static._applyGameModule
 */

var MATTIE = MATTIE || {};
MATTIE.static = MATTIE.static || {};
MATTIE.compat = MATTIE.compat || {};

// ─────────────────────────────────────────────────────────────────────────────
// Registry
// ─────────────────────────────────────────────────────────────────────────────

/**
 * @description All registered game modules. Populated by calls to registerGameModule().
 * @type {Object[]}
 */
MATTIE.static._registeredModules = MATTIE.static._registeredModules || [];

/**
 * @description The currently active game module, set after selection and application.
 * @type {Object|null}
 */
MATTIE.static._activeModule = MATTIE.static._activeModule || null;

// ─────────────────────────────────────────────────────────────────────────────
// Registration
// ─────────────────────────────────────────────────────────────────────────────

/**
 * @description Register a game module with the engine.
 * Each module must provide at minimum: id, name, versionMatch.
 *
 * Full interface contract:
 * {
 *   id: string,                          // e.g. "fearandhunger1"
 *   name: string,                        // e.g. "Fear & Hunger"
 *   versionMatch: function() -> boolean, // returns true when this game is running
 *
 *   actors: { mercenaryId, girlId, ... },
 *   maps:   { menuMaps, blockingMaps, charCreationMap, startMap, fortress, ... },
 *   teleports: [ { id, name, cmd, bool, btn }, ... ],
 *   switches: {
 *     ignored, synced, syncedSelfSwitches, ignoredSelfSwitches,
 *     characterLimbs, logical, hardMode, starvation,
 *     crowMaulerCanSpawn, crowMaulerDead, crowMaulerDisabled, ...
 *   },
 *   variables: { ignored, synced, secondarySynced, godAffinityAndPrayerVars, ... },
 *   states:    { knockout, resistDeath, armCut, legCut, bleeding, ... },
 *   troops:    { crowMauler, ... },
 *   items:     function($dataItems) -> { emptyScroll, silverCoin, icons },
 *   skills:    function($dataSkills) -> { hurting, ... },
 *   commonEvents: { smallFood, medFood, largeFood, lootTables, ... },
 *   events:    { images: { shiny, coin }, crowMauler },
 *
 *   multiplayer: {
 *     ghost:           { mapId, eventId, troopId, troopIndex },
 *     pvpActorTroopMap: { [actorId]: troopId },
 *     gameOverText:    { death, place, spectate, rebirth },
 *     spawnMap:        { mapId, x, y },
 *     charPortraitMap: { [actorId]: faceName },
 *   },
 *
 *   features:     { hasCrowMauler, hasLighting },
 *   compat:       { blockedMods, ignoredPlugins, menuIconMap },
 *   hooks:        { onStaticUpdate, onMultiplayerInit },
 *   dependencies: [],   // commonLib paths to load, e.g. "_common/betterCrowMauler"
 * }
 *
 * @param {Object} module - The game module to register
 */
MATTIE.static.registerGameModule = function (module) {
	if (!module || !module.id || !module.name || typeof module.versionMatch !== 'function') {
		console.error('[GameModule] Cannot register — missing required fields (id, name, versionMatch):', module);
		return;
	}
	// Avoid duplicate registration (e.g. if update() is called multiple times)
	const existing = this._registeredModules.findIndex((m) => m.id === module.id);
	if (existing !== -1) {
		this._registeredModules[existing] = module;
	} else {
		this._registeredModules.push(module);
	}
	console.log('[GameModule] Registered:', module.name);
};

// ─────────────────────────────────────────────────────────────────────────────
// Selection
// ─────────────────────────────────────────────────────────────────────────────

/**
 * @description Find the first registered module whose versionMatch() returns true.
 * @returns {Object|null} The matching module, or null if none found.
 */
MATTIE.static._selectGameModule = function () {
	for (let i = 0; i < this._registeredModules.length; i++) {
		const module = this._registeredModules[i];
		try {
			if (module.versionMatch()) return module;
		} catch (e) {
			console.warn('[GameModule] versionMatch() threw for module:', module.name, e);
		}
	}
	return null;
};

// ─────────────────────────────────────────────────────────────────────────────
// Application
// ─────────────────────────────────────────────────────────────────────────────

/**
 * @description Apply a game module's data into MATTIE.static.* namespaces.
 * Called automatically by MATTIE.static.update() when a module is selected.
 * @param {Object} module - The game module to apply.
 */
MATTIE.static._applyGameModule = function (module) {
	if (!module) return;

	this._activeModule = module;
	console.log('[GameModule] Applying:', module.name);

	// ── Actors ──────────────────────────────────────────────────────────────
	if (module.actors) {
		Object.assign(MATTIE.static.actors, module.actors);
	}

	// ── Maps ────────────────────────────────────────────────────────────────
	if (module.maps) {
		Object.keys(module.maps).forEach((key) => {
			const val = module.maps[key];
			if (val && typeof val === 'object' && !Array.isArray(val)) {
				// Deep merge nested map objects (e.g. maps.termina)
				MATTIE.static.maps[key] = Object.assign(MATTIE.static.maps[key] || {}, val);
			} else {
				MATTIE.static.maps[key] = val;
			}
		});
		// menuMaps may need rangeParser if provided as ranges
		if (module.maps.menuMaps) {
			MATTIE.static.maps.menuMaps = MATTIE.static.rangeParser(MATTIE.static.maps.menuMaps);
		}
	}

	// ── Teleports ───────────────────────────────────────────────────────────
	if (module.teleports !== undefined) {
		MATTIE.static.teleports = module.teleports;
	}

	// ── Switches ────────────────────────────────────────────────────────────
	if (module.switches) {
		const sw = module.switches;

		if (sw.ignored !== undefined) {
			MATTIE.static.switch.ignoredSwitches = MATTIE.static.rangeParser(sw.ignored);
		}
		if (sw.synced !== undefined) {
			MATTIE.static.switch.syncedSwitches = MATTIE.static.rangeParser(sw.synced);
		}
		if (sw.characterLimbs !== undefined) {
			MATTIE.static.switch.characterLimbs = MATTIE.static.rangeParser(sw.characterLimbs);
		}
		if (sw.logical !== undefined) {
			MATTIE.static.switch.logical = sw.logical;
		}
		if (sw.godAffinitySwitches !== undefined) {
			MATTIE.static.switch.godAffinitySwitches = MATTIE.static.rangeParser(sw.godAffinitySwitches);
		}
		// Self-switch arrays: game module provides raw arrays, we stringify them
		if (sw.syncedSelfSwitches !== undefined) {
			MATTIE.static.switch.syncedSelfSwitches = sw.syncedSelfSwitches.map((arr) => JSON.stringify(arr));
		}
		if (sw.ignoredSelfSwitches !== undefined) {
			MATTIE.static.switch.ignoredSelfSwitches = sw.ignoredSelfSwitches.map((arr) => JSON.stringify(arr));
		}
		// Named switch IDs (scalar values)
		const scalarKeys = ['crowMaulerCanSpawn', 'crowMaulerDead', 'crowMaulerDisabled',
			'hardMode', 'starvation', 'legardAliveSwitch'];
		scalarKeys.forEach((key) => {
			if (sw[key] !== undefined) MATTIE.static.switch[key] = sw[key];
		});
		// Any additional switch properties not in the known list
		const knownSwitchKeys = new Set(['ignored', 'synced', 'characterLimbs', 'logical',
			'godAffinitySwitches', 'syncedSelfSwitches', 'ignoredSelfSwitches',
			...scalarKeys]);
		Object.keys(sw).forEach((key) => {
			if (!knownSwitchKeys.has(key)) MATTIE.static.switch[key] = sw[key];
		});
	}

	// ── Variables ───────────────────────────────────────────────────────────
	if (module.variables) {
		const v = module.variables;

		if (v.ignored !== undefined) {
			MATTIE.static.variable.ignoredVars = MATTIE.static.rangeParser(v.ignored);
		}
		if (v.synced !== undefined) {
			MATTIE.static.variable.syncedVars = MATTIE.static.rangeParser(v.synced);
		}
		if (v.secondarySynced !== undefined) {
			MATTIE.static.variable.secondarySyncedVars = MATTIE.static.rangeParser(v.secondarySynced);
		}
		if (v.godAffinityAndPrayerVars !== undefined) {
			MATTIE.static.variable.godAffinityAndPrayerVars = MATTIE.static.rangeParser(v.godAffinityAndPrayerVars);
		}
		// Additional variable properties
		const knownVarKeys = new Set(['ignored', 'synced', 'secondarySynced', 'godAffinityAndPrayerVars']);
		Object.keys(v).forEach((key) => {
			if (!knownVarKeys.has(key)) MATTIE.static.variable[key] = v[key];
		});
	}

	// ── States ──────────────────────────────────────────────────────────────
	if (module.states) {
		Object.assign(MATTIE.static.states, module.states);
	}

	// ── Troops ──────────────────────────────────────────────────────────────
	if (module.troops) {
		Object.assign(MATTIE.static.troops, module.troops);
	}

	// ── Items (deferred — requires $dataItems to be loaded) ─────────────────
	if (typeof module.items === 'function') {
		try {
			const items = module.items();
			if (items) Object.assign(MATTIE.static.items, items);
		} catch (e) {
			console.warn('[GameModule] items() threw:', e);
		}
	}

	// ── Skills (deferred — requires $dataSkills) ─────────────────────────────
	if (typeof module.skills === 'function') {
		try {
			const skills = module.skills();
			if (skills) Object.assign(MATTIE.static.skills, skills);
		} catch (e) {
			console.warn('[GameModule] skills() threw:', e);
		}
	}

	// ── Common Events ────────────────────────────────────────────────────────
	if (module.commonEvents !== undefined) {
		const ce = (typeof module.commonEvents === 'function') ? module.commonEvents() : module.commonEvents;
		if (ce) Object.assign(MATTIE.static.commonEvents, ce);
	}

	// ── Events ──────────────────────────────────────────────────────────────
	if (module.events) {
		if (module.events.images) {
			Object.assign(MATTIE.static.events.images, module.events.images);
		}
		if (module.events.crowMauler !== undefined) {
			MATTIE.static.events.crowMauler = module.events.crowMauler;
		}
	}

	// ── Compatibility ────────────────────────────────────────────────────────
	if (module.compat) {
		if (module.compat.blockedMods !== undefined) {
			MATTIE.compat.blockedMods = module.compat.blockedMods;
		}
		if (module.compat.menuIconMap !== undefined) {
			MATTIE.compat.menuIconMap = module.compat.menuIconMap;
		}
		if (module.compat.ignoredPlugins !== undefined) {
			MATTIE.compat.moduleIgnoredPlugins = module.compat.ignoredPlugins;
		}
	}

	// ── God Affinity / Multiplayer Merge ─────────────────────────────────────
	// Same logic exists in both F&H1 and Termina branches — handled generically here.
	if (MATTIE.static.switch.godAffinitySwitches && MATTIE.static.switch.godAffinitySwitches.length > 0) {
		if (MATTIE.multiplayer && MATTIE.multiplayer.params) {
			if (MATTIE.multiplayer.params.sharedAffinity) {
				MATTIE.static.switch.syncedSwitches = MATTIE.static.switch.syncedSwitches.concat(MATTIE.static.switch.godAffinitySwitches);
				MATTIE.static.variable.syncedVars = MATTIE.static.variable.syncedVars.concat(MATTIE.static.variable.godAffinityAndPrayerVars);
			} else {
				MATTIE.static.switch.ignoredSwitches = MATTIE.static.switch.ignoredSwitches.concat(MATTIE.static.switch.godAffinitySwitches);
				MATTIE.static.variable.ignoredVars = MATTIE.static.variable.ignoredVars.concat(MATTIE.static.variable.godAffinityAndPrayerVars);
			}
		}
	}

	// ── onStaticUpdate Hook ───────────────────────────────────────────────────
	// Runs after all data is applied. Game modules use this to register teleport
	// functions, apply compatibility patches, etc.
	if (module.hooks && typeof module.hooks.onStaticUpdate === 'function') {
		try {
			module.hooks.onStaticUpdate();
		} catch (e) {
			console.error('[GameModule] hooks.onStaticUpdate threw:', e);
		}
	}
};