// src/core/PhysicsManager.ts
import * as AmmoModule from 'ammojs3';

export interface AmmoInstance {
  btDefaultCollisionConfiguration: any;
  btCollisionDispatcher: any;
  btDbvtBroadphase: any;
  btSequentialImpulseConstraintSolver: any;
  btDiscreteDynamicsWorld: any;
  btTransform: any;
  btVector3: any;
  btQuaternion: any;
  btDefaultMotionState: any;
  btBoxShape: any;
  btRigidBodyConstructionInfo: any;
  btRigidBody: any;
  btTypedConstraint: any;
  btHingeConstraint: any;
  btConeTwistConstraint: any;
  btPoint2PointConstraint: any;
  destroy: (obj: any) => void;
}

declare global {
  const Ammo: AmmoInstance;
}

let ammoInstance: AmmoInstance;
import { Vector3, Quaternion, Matrix4 } from 'three';
import { EventEmitter } from 'events';

export interface PhysicsObject {
  id: string;
  body: AmmoInstance['btRigidBody'];
  mass: number;
  position: Vector3;
  rotation: Quaternion;
  scale: Vector3;
}

export interface PhysicsProperties {
  mass?: number;
  friction?: number;
  restitution?: number;
  linearDamping?: number;
  angularDamping?: number;
  isKinematic?: boolean;
}

export class PhysicsManager extends EventEmitter {
  private physicsWorld: AmmoInstance['btDiscreteDynamicsWorld'];
  private collisionConfiguration: AmmoInstance['btDefaultCollisionConfiguration'];
  private dispatcher: AmmoInstance['btCollisionDispatcher'];
  private broadphase: AmmoInstance['btDbvtBroadphase'];
  private solver: AmmoInstance['btSequentialImpulseConstraintSolver'];
  private objects: Map<string, PhysicsObject> = new Map();
  private isInitialized: boolean = false;
  private gravity: Vector3 = new Vector3(0, -9.81, 0);
  private timeStep: number = 1 / 60;

  constructor() {
    super();
  }

  /**
   * Initializes the physics engine
   */
  public async initialize(): Promise<boolean> {
    try {
      // Initialize Ammo.js
      const ammo = await AmmoModule.default();
      // Assign to global Ammo variable
      (globalThis as any).Ammo = ammo;

      this.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
      this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);
      this.broadphase = new Ammo.btDbvtBroadphase();
      this.solver = new Ammo.btSequentialImpulseConstraintSolver();
      this.physicsWorld = new Ammo.btDiscreteDynamicsWorld(
        this.dispatcher,
        this.broadphase,
        this.solver,
        this.collisionConfiguration
      );

      // Set default gravity
      const gravity = new Ammo.btVector3(this.gravity.x, this.gravity.y, this.gravity.z);
      this.physicsWorld.setGravity(gravity);

      this.isInitialized = true;
      this.emit('initialized');
      return true;
    } catch (error) {
      this.emit('error', error);
      throw error;
    }
  }

  /**
   * Creates a physics object and adds it to the physics world
   */
  public createObject(
    id: string,
    position: Vector3,
    rotation: Quaternion,
    scale: Vector3,
    properties: PhysicsProperties = {}
  ): void {
    if (!this.isInitialized) {
      throw new Error('PhysicsManager must be initialized first');
    }

    const transform = new Ammo.btTransform();
    transform.setIdentity();
    transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
    transform.setRotation(new Ammo.btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w));

    const motionState = new Ammo.btDefaultMotionState(transform);
    const shape = this.createBoxShape(scale);

    const mass = properties.mass ?? 0;
    const localInertia = new Ammo.btVector3(0, 0, 0);
    if (mass > 0) {
      shape.calculateLocalInertia(mass, localInertia);
    }

    const rbInfo = new Ammo.btRigidBodyConstructionInfo(
      mass,
      motionState,
      shape,
      localInertia
    );

    const body = new Ammo.btRigidBody(rbInfo);

    // Set additional properties
    body.setFriction(properties.friction ?? 0.5);
    body.setRestitution(properties.restitution ?? 0.2);
    body.setDamping(
      properties.linearDamping ?? 0.1,
      properties.angularDamping ?? 0.1
    );

    if (properties.isKinematic) {
      body.setCollisionFlags(body.getCollisionFlags() | 2); // CF_KINEMATIC_OBJECT
    }

    this.physicsWorld.addRigidBody(body);

    this.objects.set(id, {
      id,
      body,
      mass,
      position: position.clone(),
      rotation: rotation.clone(),
      scale: scale.clone()
    });

    this.emit('object-created', id);
  }

  /**
   * Updates the physics simulation
   */
  public update(deltaTime: number = this.timeStep): void {
    if (!this.isInitialized) return;

    this.physicsWorld.stepSimulation(deltaTime, 10);

    // Update object positions and rotations
    this.objects.forEach((object) => {
      const transform = new Ammo.btTransform();
      object.body.getMotionState().getWorldTransform(transform);

      const pos = transform.getOrigin();
      const rot = transform.getRotation();

      object.position.set(pos.x(), pos.y(), pos.z());
      object.rotation.set(rot.x(), rot.y(), rot.z(), rot.w());

      this.emit('object-updated', {
        id: object.id,
        position: object.position,
        rotation: object.rotation
      });
    });
  }

  /**
   * Applies a force to an object
   */
  public applyForce(id: string, force: Vector3, point?: Vector3): void {
    const object = this.objects.get(id);
    if (!object) {
      throw new Error(`Physics object ${id} not found`);
    }

    const btForce = new Ammo.btVector3(force.x, force.y, force.z);
    if (point) {
      const btPoint = new Ammo.btVector3(point.x, point.y, point.z);
      object.body.applyForce(btForce, btPoint);
    } else {
      object.body.applyCentralForce(btForce);
    }
  }

  /**
   * Sets the gravity of the physics world
   */
  public setGravity(gravity: Vector3): void {
    this.gravity = gravity.clone();
    const btGravity = new Ammo.btVector3(gravity.x, gravity.y, gravity.z);
    this.physicsWorld.setGravity(btGravity);
  }

  /**
   * Creates a box collision shape
   */
  private createBoxShape(scale: Vector3): any {
    const shape = new Ammo.btBoxShape(new Ammo.btVector3(
      scale.x * 0.5,
      scale.y * 0.5,
      scale.z * 0.5
    ));
    return shape;
  }

  /**
   * Removes an object from the physics world
   */
  public removeObject(id: string): void {
    const object = this.objects.get(id);
    if (!object) return;

    this.physicsWorld.removeRigidBody(object.body);
    this.objects.delete(id);
    this.emit('object-removed', id);
  }

  /**
   * Cleans up physics resources
   */
  /**
   * Creates a rigid body with specified position, rotation and mass
   */
  public createRigidBody(position: { x: number; y: number; z: number }, rotation: { x: number; y: number; z: number; w: number }, mass: number): any {
    if (!this.isInitialized) {
      throw new Error('PhysicsManager must be initialized first');
    }

    const transform = new Ammo.btTransform();
    transform.setIdentity();
    transform.setOrigin(new Ammo.btVector3(position.x, position.y, position.z));
    transform.setRotation(new Ammo.btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w));

    const motionState = new Ammo.btDefaultMotionState(transform);
    const shape = new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));

    const localInertia = new Ammo.btVector3(0, 0, 0);
    if (mass > 0) {
      shape.calculateLocalInertia(mass, localInertia);
    }

    const rbInfo = new Ammo.btRigidBodyConstructionInfo(
      mass,
      motionState,
      shape,
      localInertia
    );

    const body = new Ammo.btRigidBody(rbInfo);
    body.setMass(mass);

    this.physicsWorld.addRigidBody(body);
    return body;
  }

  /**
   * Gets the physics world instance
   */
  public getWorld(): AmmoInstance['btDiscreteDynamicsWorld'] {
    if (!this.isInitialized) {
      throw new Error('PhysicsManager must be initialized first');
    }
    return this.physicsWorld;
  }

  /**
   * Cleans up physics resources
   */
  public cleanup(): void {
    Array.from(this.objects.keys()).forEach(id => this.removeObject(id));
    
    // Clean up Ammo.js resources
    Ammo.destroy(this.physicsWorld);
    Ammo.destroy(this.solver);
    Ammo.destroy(this.broadphase);
    Ammo.destroy(this.dispatcher);
    Ammo.destroy(this.collisionConfiguration);

    this.isInitialized = false;
    this.emit('cleanup-complete');
  }
}