// @ts-check
import { Entity, Player, system, TicksPerSecond, world } from "@minecraft/server";
import { isBeingWatchedBy } from "../utilities/helpers";
import { FogDwellerManager } from "./fog_dweller_manager";
import { log } from "./debug";
import { getVibrantVisuals } from "../biome/ambient_handler";

/**
 * A simple wrapper class for Fog Dweller entity
 */
export class FogDweller {
  /**
   * @readonly
   * @type {Entity}
   */
  entity;

  /**
   * The target player being attacked by this entity.
   * @type {Player | undefined}
   */
  target;

  /**
   * Creates a new FogDweller instance and registers it in the entity registry
   * @param {Entity} entity - The entity to wrap
   */
  constructor(entity) {
    this.entity = entity;
    FogDwellerManager.setInRegistry(this);
  }

  /**
   * Initializes the FogDweller class.
   */
  static init() {
    // Register entity removal handler to clean up the registry
    world.afterEvents.entityRemove.subscribe((event) => {
      const entityId = event.removedEntityId;
      const fogDweller = FogDwellerManager.getFromRegistry(entityId);
      if (fogDweller === undefined) {
        return; // Nothing to do
      }
      FogDwellerManager.removeFromRegistry(entityId);
    });

    // Listen for the observer check tick event
    system.afterEvents.scriptEventReceive.subscribe(
      (event) => {
        if (event.id !== "hb_hr:fog_dweller_tick") {
          return;
        }
        const entity = event.sourceEntity;
        if (entity === undefined) {
          return;
        }

        // Get or create the fog dweller instance
        let fogDweller = FogDwellerManager.getFromRegistry(entity.id) ?? new FogDweller(entity);

        // Check if the fog dweller is being observed
        const target = fogDweller.target;
        if (target === undefined || !target.isValid()) {
          return;
        }
        const targetLocation = target.location;
        const fogDwellerLocation = entity.location;
        const targetDistance = Math.hypot(targetLocation.x - fogDwellerLocation.x, targetLocation.y - fogDwellerLocation.y, targetLocation.z - fogDwellerLocation.z);
        if (targetDistance < 7) {
          // The test for target looking at the fog dweller only works if the target is not too close
          return;
        }

        if (fogDweller.isBeingObserved([target])) {
          // log(`Slowing down the dweller who is observed by its target!`);
          fogDweller.entity.triggerEvent("hb_hr:movement_super_slow_from_being_observed");
        }
      },
      {
        namespaces: ["hb_hr"],
      },
    );
    // Handle fog ticks (ticks that spawn the fog dweller's fog)
    system.afterEvents.scriptEventReceive.subscribe(
      (event) => {
        if (event.id !== "hb_hr:fog_dweller_fog_tick") {
          return;
        }
        const entity = event.sourceEntity;
        if (entity === undefined) {
          return;
        }

        // Get or create the fog dweller instance
        let fogDweller = FogDwellerManager.getFromRegistry(entity.id) ?? new FogDweller(entity);

        // Spawn the fog
        const { dimension, location } = fogDweller.entity;
        // try {
        //   dimension.spawnParticle("hb_hr:fog_dweller_fog_spawner", location);
        // } catch (error) {}
        // If dweller has target also spawn the fog on the target
        const target = fogDweller.target;
        if (target !== undefined && target.isValid()) {
          const { dimension, location } = target;
          const vv = getVibrantVisuals(target);
          if (vv) return;
          try {
            target.spawnParticle("hb_hr:fog_dweller_fog_spawner", location);
          } catch (error) {}
        }
      },
      {
        namespaces: ["hb_hr"],
      },
    );
    // Handle adding and removing fog to the players
    /** @type {Map<string, boolean>} */
    const playerFogState = new Map();
    system.runInterval(() => {
      for (const player of world.getPlayers()) {
        if (FogDwellerManager.isFogDwellerTarget(player.id)) {
          // Trigger if false or undefined
          if (playerFogState.get(player.id) !== true) {
            player.runCommand("fog @s push hb_hr:fog_dweller fod_dweller_fog");
          }
          playerFogState.set(player.id, true);
        } else {
          // Trigger if true or undefined
          if (playerFogState.get(player.id) !== false) {
            player.runCommand("fog @s remove fod_dweller_fog");
          }
          playerFogState.set(player.id, false);
        }
      }
    }, 1 * TicksPerSecond);
    // Track target of the fog dweller
    system.afterEvents.scriptEventReceive.subscribe(
      (event) => {
        if (event.id !== "hb_hr:entity_is_fog_dweller_target") {
          return;
        }
        if (event.sourceEntity === undefined || event.sourceEntity.typeId !== "minecraft:player") {
          log(`Tried to set target for fog dweller but source entity was not a player`);
          return;
        }
        const player = /** @type {Player} */ (event.sourceEntity);
        const dimension = player.dimension;

        // Get the fog dweller that forced the player to send this event
        const senders = dimension.getEntities({
          type: "hb_hr:fog_dweller",
          tags: ["hb_hr_target_info_sender"],
        });
        if (senders.length === 0) {
          log(`Tried to set target for fog dweller but no fog dweller found`);
          return;
        }
        const fogDwellerEntity = senders[0];

        // Get or create the fog dweller instance
        let fogDweller = FogDwellerManager.getFromRegistry(fogDwellerEntity.id) ?? new FogDweller(fogDwellerEntity);

        // Set the target
        log(`Setting target of the fog dweller ${fogDwellerEntity.id} to ${player.id}`);
        fogDweller.target = player;
      },
      {
        namespaces: ["hb_hr"],
      },
    );

    // Handle target removal when fog dweller loses its target
    system.afterEvents.scriptEventReceive.subscribe(
      (event) => {
        if (event.id !== "hb_hr:remove_fog_dweller_target") {
          return;
        }
        const entity = event.sourceEntity;
        if (entity === undefined || entity.typeId !== "hb_hr:fog_dweller") {
          return;
        }

        // Get the fog dweller instance
        const fogDweller = FogDwellerManager.getFromRegistry(entity.id);
        if (fogDweller === undefined) {
          return;
        }
        log(`Removing target of the fog dweller ${entity.id}`);

        // Clear the target
        fogDweller.target = undefined;
      },
      {
        namespaces: ["hb_hr"],
      },
    );
  }

  /**
   * Tests whether the fog dweller is being observed by players.
   * @param {Player[]} players
   */
  isBeingObserved(players) {
    const location = {
      x: this.entity.location.x,
      // offset too target center of the entity body
      y: this.entity.location.y + 2,
      z: this.entity.location.z,
    };
    const BASE_CONE_ANGLE = 5; // degrees
    const FOG_DWELLER_SIZE = 4; // blocks

    return isBeingWatchedBy(players, location, BASE_CONE_ANGLE, FOG_DWELLER_SIZE);
  }
}

// Initialize the FogDweller class when the script loads
FogDweller.init();
