import {
  EasyEvent,
  UpdateController,
} from "@24tools/playable_template";
import { Box3, Object3D, Vector3 } from "three";
import { Body } from "cannon-es";

const PLAYER_RADIUS = 0.3;

export type TriggerEventPayload = {
  lootableObject: Object3D;
  triggerObject: Object3D;
  playerBody: Body;
};

type TriggerRecord = {
  lootableObject: Object3D;
  triggerObject: Object3D;
  center: Vector3;
  radius: number;
};

export class PhysicsTriggerC {
  private static inited = false;
  private static triggers: TriggerRecord[] = [];
  private static activeTriggers = new Set<TriggerRecord>();
  private static playerBody: Body | null = null;

  static onTriggerEnter = new EasyEvent<TriggerEventPayload>();
  static onTriggerExit = new EasyEvent<TriggerEventPayload>();

  static init() {
    if (this.inited) return;
    this.inited = true;

    UpdateController.Instance.onUpdate.addDelegate(() => this.update());
  }

  static setPlayerBody(body: Body) {
    this.playerBody = body;
  }

  static register(triggerObject: Object3D, lootableObject: Object3D) {
    triggerObject.updateWorldMatrix(true, true);

    const center = new Vector3();
    const size = new Vector3();
    const bounds = new Box3().setFromObject(triggerObject);
    bounds.getCenter(center);
    bounds.getSize(size);

    this.triggers.push({
      lootableObject,
      triggerObject,
      center,
      radius: Math.max(size.x, size.y, size.z) / 2,
    });
  }

  static getActiveLootableObjects(): Object3D[] {
    const lootables: Object3D[] = [];
    for (const record of this.activeTriggers) {
      lootables.push(record.lootableObject);
    }
    return lootables;
  }

  static unregister(lootableObject: Object3D) {
    const playerBody = this.playerBody;

    this.triggers = this.triggers.filter((record) => {
      if (record.lootableObject !== lootableObject) return true;

      if (this.activeTriggers.has(record) && playerBody) {
        this.onTriggerExit.Invoke({
          lootableObject: record.lootableObject,
          triggerObject: record.triggerObject,
          playerBody,
        });
      }

      this.activeTriggers.delete(record);
      return false;
    });
  }

  private static update() {
    const playerBody = this.playerBody;
    if (!playerBody) return;

    for (const trigger of this.triggers) {
      const dx = playerBody.position.x - trigger.center.x;
      const dy = playerBody.position.y - trigger.center.y;
      const dz = playerBody.position.z - trigger.center.z;
      const distanceSq = dx * dx + dy * dy + dz * dz;
      const isInside = distanceSq <= (trigger.radius + PLAYER_RADIUS) ** 2;
      const wasInside = this.activeTriggers.has(trigger);

      if (isInside === wasInside) continue;

      const payload: TriggerEventPayload = {
        lootableObject: trigger.lootableObject,
        triggerObject: trigger.triggerObject,
        playerBody,
      };

      if (isInside) {
        this.activeTriggers.add(trigger);
        this.onTriggerEnter.Invoke(payload);
      } else {
        this.activeTriggers.delete(trigger);
        this.onTriggerExit.Invoke(payload);
      }
    }
  }
}
