import { Physics_internal } from "@hitplay/playable_template";
import { Box3, Object3D, Vector3 } from "three";
import { Body, Box, Quaternion, Sphere, Vec3 } from "cannon-es";

// Collision layers. Each body has a `group` (what it IS) and a `mask` (what it
// COLLIDES WITH); two bodies interact only if each one's group is in the other's
// mask. Values are bit flags so masks can be OR-combined (e.g. Wall | Trigger).
export enum PhysicsLayer {
  Player = 1,
  Wall = 2,
  Trigger = 4,
  Enemy = 8,
}

/**
 * Physics world API facade. All direct Physics_internal access goes through here.
 */
export class PhysicsC {
  /** Add a body to the physics world. */
  static addBody(body: Body) {
    Physics_internal.physicsWorld?.addBody(body);
  }

  /** Remove a body from the physics world. */
  static removeBody(body: Body) {
    Physics_internal.physicsWorld?.removeBody(body);
  }

  /** Listen for collision begin events. */
  static onContactBegin(callback: (event: any) => void) {
    Physics_internal.physicsWorld?.addEventListener("beginContact", callback);
  }

  /** Listen for collision end events. */
  static onContactEnd(callback: (event: any) => void) {
    Physics_internal.physicsWorld?.addEventListener("endContact", callback);
  }

  /** Get the raw physics world (for advanced usage). */
  static get world() {
    return Physics_internal.physicsWorld;
  }
}

/**
 * Wraps a three.js object in a cannon-es rigid body. The shape is derived from
 * the object: the player gets a Sphere (rolls smoothly along walls/floor),
 * everything else gets a Box sized to the object's bounding box.
 */
export class PhysicsBody {
  private body: Body;

  constructor(
    object: Object3D,
    isTrigger: boolean,
    mass: number,
    collisionGroup: PhysicsLayer,
    collisionMask: PhysicsLayer,
    sphereRadius = 0.3,
  ) {
    const isPlayer = collisionGroup === PhysicsLayer.Player;

    // Measure the bounding box with rotation temporarily zeroed, so the box
    // half-extents match the object's un-rotated size; the body's own
    // quaternion (set below) then applies the real orientation.
    const savedRotation = object.quaternion.clone();
    object.quaternion.copy(new Quaternion());
    const size = new Box3().setFromObject(object).getSize(new Vector3());
    object.quaternion.copy(savedRotation);

    this.body = new Body({
      isTrigger,
      mass,
      shape: isPlayer
        ? new Sphere(sphereRadius)
        : new Box(new Vec3(size.x / 2, size.y / 2, size.z / 2)),
      collisionFilterGroup: collisionGroup,
      collisionFilterMask: collisionMask,
    });

    const worldPos = object.getWorldPosition(new Vector3());
    this.body.position.set(worldPos.x, worldPos.y, worldPos.z);
    this.body.quaternion.setFromEuler(
      object.rotation.x,
      object.rotation.y,
      object.rotation.z,
      "XYZ",
    );

    PhysicsC.addBody(this.body);
  }

  /** The underlying cannon body (for direct velocity/position control). */
  getPhysicsBody(): Body {
    return this.body;
  }

  /** Remove the body from the physics world (e.g. when a crate breaks). */
  destroy() {
    PhysicsC.removeBody(this.body);
  }
}
