// @ts-check
import { Player, world } from "@minecraft/server";
import { log } from "./debug";
import { ANGER_RANGES } from "./config.js";

/**
 * Represents the data about a stalker's state
 */
export class StalkerData {
	/**
	 * The current anger level of the stalker
	 * @type {number}
	 */
	angerLevel;
	/**
	 * The current health of the stalker. Value of -1 indicates that the stalker
	 * is at full health.
	 * @type {number}
	 */
	health;

	/**
	 * @param {number} angerLevel
	 * @param {number} health
	 */
	constructor(angerLevel, health) {
		this.angerLevel = angerLevel;
		this.health = health;
	}

	/**
	 * Creates a StalkerData instance from a plain object
	 * @param {unknown} data - The object to create from
	 * @returns {StalkerData | undefined} - The created instance or undefined if invalid
	 */
	static fromObject(data) {
		if (typeof data !== "object" || data === null) {
			return undefined;
		}
		const obj = /** @type {Record<string, unknown>} */ (data);
		if (typeof obj.angerLevel !== "number" || typeof obj.health !== "number") {
			return undefined;
		}
		return new StalkerData(obj.angerLevel, obj.health);
	}

	/**
	 * Whether the stalking entity can perform creepy actions.
	 */
	canBeCreepy() {
		return this.angerLevel >= ANGER_RANGES.CREEPY.min && this.angerLevel <= ANGER_RANGES.CREEPY.max;
	}

	/**
	 * Whether the stalking entity can attack pets.
	 */
	canAttackPets() {
		return this.angerLevel >= ANGER_RANGES.ATTACK_PETS.min && this.angerLevel <= ANGER_RANGES.ATTACK_PETS.max;
	}

	/**
	 * Whether the stalking entity can attack the player once.
	 */
	canAttackPlayerOnce() {
		return this.angerLevel >= ANGER_RANGES.ATTACK_PLAYER_ONCE.min && this.angerLevel <= ANGER_RANGES.ATTACK_PLAYER_ONCE.max;
	}

	/**
	 * Whether the stalking entity can kill the player.
	 */
	canKillPlayer() {
		return this.angerLevel >= ANGER_RANGES.KILL_PLAYER.min && this.angerLevel <= ANGER_RANGES.KILL_PLAYER.max;
	}
}

/**
 * A class that provides information about the players that are stalked by undead
 * golems.
 */
export class StalkedPlayerManager {
	/**
	 * @type {Record<string, StalkerData> | undefined}
	 */
	static stalkedPlayers;

	/**
	 * Adds player to the set of the stalked players
	 * @param {string} playerId
	 * @param {StalkerData} data
	 */
	static setStalkedPlayer(playerId, data) {
		if (StalkedPlayerManager.stalkedPlayers === undefined) {
			StalkedPlayerManager.stalkedPlayers =
				StalkedPlayerManager.getStalkedPlayers();
		}
		StalkedPlayerManager.stalkedPlayers[playerId] = data;
		world.setDynamicProperty(
			"undead_golem_stalked_players",
			JSON.stringify(StalkedPlayerManager.stalkedPlayers)
		);
	}

	/**
	 * Removes player from the set of stalked players
	 * @param {Player} player
	 */
	static removeStalkedPlayer(player) {
		if (StalkedPlayerManager.stalkedPlayers === undefined) {
			StalkedPlayerManager.stalkedPlayers =
				StalkedPlayerManager.getStalkedPlayers();
		}
		if (player.id in StalkedPlayerManager.stalkedPlayers) {
			delete StalkedPlayerManager.stalkedPlayers[player.id];
			world.setDynamicProperty(
				"undead_golem_stalked_players",
				JSON.stringify(StalkedPlayerManager.stalkedPlayers)
			);
		}
	}

	/**
	 * Returns the record of stalked player IDs and their stalker data
	 * @private
	 * @returns {Record<string, StalkerData>}
	 */
	static getStalkedPlayers() {
		if (StalkedPlayerManager.stalkedPlayers !== undefined) {
			return StalkedPlayerManager.stalkedPlayers;
		}
		// Fallback to dynamic property
		const stalkedPlayersProperty = world.getDynamicProperty(
			"undead_golem_stalked_players"
		);
		StalkedPlayerManager.stalkedPlayers = {};
		if (typeof stalkedPlayersProperty !== "string") {
			// Invalid property, return empty object
			return StalkedPlayerManager.stalkedPlayers;
		}
		try {
			// Parse JSON and convert each entry to StalkerData
			const stalkedPlayersObj = JSON.parse(stalkedPlayersProperty);
			if (typeof stalkedPlayersObj === "object" && stalkedPlayersObj !== null) {
				for (const id in stalkedPlayersObj) {
					const stalkerData = StalkerData.fromObject(stalkedPlayersObj[id]);
					if (stalkerData !== undefined) {
						StalkedPlayerManager.stalkedPlayers[id] = stalkerData;
					}
				}
			}
		} catch {
			// Ignore parse errors and keep empty object
		}
		// At this point stalkedPlayers is definitely defined
		return StalkedPlayerManager.stalkedPlayers;
	}

	/**
	 * Function that prints the current state of the stalked players. Can be used
	 * for debugging purposes.
	 */
	static printStalkedPlayers() {
		if (StalkedPlayerManager.stalkedPlayers === undefined) {
			log("No stalked players");
			return;
		}
		const keys = Object.keys(StalkedPlayerManager.stalkedPlayers);
		log(`Stalked players: ${keys}`);
	}

	/**
	 * Returns the stalker data of a player.
	 * @param {string} playerId
	 * @returns {StalkerData | undefined}
	 */
	static getStalkedPlayer(playerId) {
		return StalkedPlayerManager.getStalkedPlayers()[playerId];
	}
}
