/**
 * Cannon.js Rigidbody Adapter for PlayCanvas
 * 
 * This adapter creates a compatibility layer between PlayCanvas's native rigidbody API
 * and Cannon.js physics engine. It allows existing PlayCanvas projects using
 * Ammo.js to seamlessly switch to Cannon.js without code changes.
 * 
 * Usage: This script is automatically injected when using Cannon.js replacement.
 */

(function() {
    'use strict';

    // Wait for PlayCanvas to be ready
    if (typeof pc === 'undefined' || typeof CANNON === 'undefined') {
        console.error('[Cannon Adapter] PlayCanvas or Cannon.js not loaded');
        return;
    }

    console.log('[Cannon Adapter] Initializing Cannon.js rigidbody adapter...');

    // Store references
    var cannonWorld = null;
    var rigidBodyMap = []; // Array of { entity, body } pairs
    var triggerBodyMap = []; // Array of { entity, body } pairs for trigger volumes
    var initialized = false;

    /**
     * Initialize Cannon World
     */
    function initCannonWorld(app) {
        if (cannonWorld) return cannonWorld;

        // Create Cannon world
        var world = new CANNON.World();
        
        // Get gravity from settings or use default
        var gravity = (window.SCENE_SETTINGS && window.SCENE_SETTINGS.physics && window.SCENE_SETTINGS.physics.gravity) 
            || [0, -9.8, 0];
        
        world.gravity.set(gravity[0], gravity[1], gravity[2]);
        
        // Setup collision events
        world.addEventListener('beginContact', function(event) {
            var bodyA = event.bodyA;
            var bodyB = event.bodyB;
            
            // Check if either body is a trigger
            if (bodyA.isTrigger || bodyB.isTrigger) {
                var triggerBody = bodyA.isTrigger ? bodyA : bodyB;
                var otherBody = bodyA.isTrigger ? bodyB : bodyA;
                
                if (triggerBody.entity && otherBody.entity) {
                    // Fire triggerenter on the trigger entity's collision component
                    if (triggerBody.entity.collision) {
                        triggerBody.entity.collision.fire('triggerenter', otherBody.entity);
                    }
                }
                return;
            }
            
            if (bodyA.entity && bodyB.entity) {
                bodyA.entity.fire('collisionstart', { other: bodyB.entity });
                bodyB.entity.fire('collisionstart', { other: bodyA.entity });
            }
        });
        
        world.addEventListener('endContact', function(event) {
            var bodyA = event.bodyA;
            var bodyB = event.bodyB;
            
            // Check if either body is a trigger
            if (bodyA.isTrigger || bodyB.isTrigger) {
                var triggerBody = bodyA.isTrigger ? bodyA : bodyB;
                var otherBody = bodyA.isTrigger ? bodyB : bodyA;
                
                if (triggerBody.entity && otherBody.entity) {
                    if (triggerBody.entity.collision) {
                        triggerBody.entity.collision.fire('triggerleave', otherBody.entity);
                    }
                }
                return;
            }
            
            if (bodyA.entity && bodyB.entity) {
                bodyA.entity.fire('collisionend', { other: bodyB.entity });
                bodyB.entity.fire('collisionend', { other: bodyA.entity });
            }
        });

        cannonWorld = world;

        // Set default contact material
        world.defaultContactMaterial.friction = 0.5;
        world.defaultContactMaterial.restitution = 0.3;

        console.log('[Cannon Adapter] Cannon world created with gravity:', gravity);
        return world;
    }

    /**
     * Create a collision shape for mesh type by extracting AABB from the render asset.
     */
    function createMeshShape(entity, onShapeReady) {
        var collision = entity.collision;
        var renderAssetId = collision.renderAsset;

        if (renderAssetId) {
            var app = pc.Application.getApplication();
            if (app) {
                var asset = app.assets.get(renderAssetId);

                if (asset && !asset.resource) {
                    // Asset exists but not yet loaded — defer shape creation
                    asset.ready(function() {
                        var shape = extractShapeFromAsset(asset, entity);
                        if (shape && onShapeReady) {
                            onShapeReady(shape);
                        }
                    });
                    app.assets.load(asset);
                    return null;
                }

                if (asset && asset.resource) {
                    return extractShapeFromAsset(asset, entity);
                }
            }
        }

        // Fallback: try entity's own render/model component
        return extractShapeFromMeshInstances(entity);
    }

    /**
     * Extract a box shape from an asset's mesh instances AABB.
     */
    function extractShapeFromAsset(asset, entity) {
        var meshInstances = null;

        if (asset.resource.meshInstances) {
            meshInstances = asset.resource.meshInstances;
        } else if (asset.resource.renders && asset.resource.renders.length > 0) {
            meshInstances = asset.resource.renders[0].meshInstances;
        }

        if (meshInstances && meshInstances.length > 0) {
            return buildBoxFromMeshInstances(meshInstances, entity);
        }
        return null;
    }

    /**
     * Extract a box shape from entity's own render/model mesh instances.
     */
    function extractShapeFromMeshInstances(entity) {
        var mi = null;
        if (entity.render && entity.render.meshInstances) {
            mi = entity.render.meshInstances;
        } else if (entity.model && entity.model.meshInstances) {
            mi = entity.model.meshInstances;
        }
        if (mi && mi.length > 0) {
            return buildBoxFromMeshInstances(mi, entity);
        }
        return null;
    }

    /**
     * Build a CANNON.Box from merged AABB of mesh instances.
     */
    function buildBoxFromMeshInstances(meshInstances, entity) {
        var mergedAabb = null;
        for (var j = 0; j < meshInstances.length; j++) {
            var instanceAabb = meshInstances[j].aabb;
            if (instanceAabb) {
                if (!mergedAabb) {
                    mergedAabb = instanceAabb.clone();
                } else {
                    mergedAabb.add(instanceAabb);
                }
            }
        }
        if (mergedAabb) {
            var he = mergedAabb.halfExtents;
            var scale = entity.getLocalScale();
            return new CANNON.Box(new CANNON.Vec3(
                he.x * scale.x,
                he.y * scale.y,
                he.z * scale.z
            ));
        }
        return null;
    }

    /**
     * Create Cannon shape from PlayCanvas collision component
     */
    function createCannonShape(entity) {
        var collision = entity.collision;
        if (!collision) return null;

        var shape;
        var type = collision.type;

        if (type === 'box') {
            var halfExtents = collision.halfExtents;
            shape = new CANNON.Box(new CANNON.Vec3(
                halfExtents.x,
                halfExtents.y,
                halfExtents.z
            ));
        } else if (type === 'sphere') {
            shape = new CANNON.Sphere(collision.radius);
        } else if (type === 'capsule') {
            // Cannon doesn't have capsule, use cylinder
            shape = new CANNON.Cylinder(
                collision.radius,
                collision.radius,
                collision.height,
                8
            );
        } else if (type === 'cylinder') {
            shape = new CANNON.Cylinder(
                collision.radius,
                collision.radius,
                collision.height,
                8
            );
        } else if (type === 'mesh') {
            // Pass a deferred callback — if the asset isn't loaded yet, the shape
            // will be swapped in once the asset finishes loading.
            var deferredEntity = entity;
            shape = createMeshShape(entity, function(loadedShape) {
                // Find the body that was created for this entity and swap the shape
                for (var idx = 0; idx < rigidBodyMap.length; idx++) {
                    if (rigidBodyMap[idx].entity === deferredEntity) {
                        var body = rigidBodyMap[idx].body;
                        // Remove old shapes and add the loaded one
                        while (body.shapes.length > 0) {
                            body.removeShape(body.shapes[0]);
                        }
                        body.addShape(loadedShape);
                        console.log('[Cannon Adapter] Deferred mesh shape loaded for:', deferredEntity.name);
                        break;
                    }
                }
            });
            if (!shape) {
                console.warn('[Cannon Adapter] Mesh collision: asset not ready, using temporary default box for:', entity.name);
                shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
            }
        } else {
            console.warn('[Cannon Adapter] Unsupported collision type:', type);
            shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5));
        }

        return shape;
    }

    /**
     * Create Cannon body from PlayCanvas rigidbody component
     */
    function createCannonBody(entity, world) {
        if (!entity.rigidbody || !entity.collision) {
            return null;
        }

        var rb = entity.rigidbody;
        var shape = createCannonShape(entity);
        if (!shape) return null;

        var pos = entity.getPosition();
        var rot = entity.getRotation();
        
        var mass = rb.mass || 0;
        var bodyType = rb.type;
        
        // Map PlayCanvas body types to Cannon
        // PlayCanvas: 'static' | 'dynamic' | 'kinematic'
        if (bodyType === 'static') {
            mass = 0;
        }

        var bodyMaterial = new CANNON.Material();
        if (rb.friction !== undefined) bodyMaterial.friction = rb.friction;
        if (rb.restitution !== undefined) bodyMaterial.restitution = rb.restitution;

        var body = new CANNON.Body({
            mass: mass,
            position: new CANNON.Vec3(pos.x, pos.y, pos.z),
            quaternion: new CANNON.Quaternion(rot.x, rot.y, rot.z, rot.w),
            shape: shape,
            material: bodyMaterial,
            linearDamping: rb.linearDamping || 0,
            angularDamping: rb.angularDamping || 0
        });

        if (bodyType === 'kinematic') {
            body.type = CANNON.Body.KINEMATIC;
        }

        // Apply linearFactor and angularFactor
        if (rb.linearFactor) {
            var lf = rb.linearFactor;
            if (Array.isArray(lf)) body.linearFactor.set(lf[0], lf[1], lf[2]);
            else if (lf.x !== undefined) body.linearFactor.set(lf.x, lf.y, lf.z);
        }
        if (rb.angularFactor) {
            var af = rb.angularFactor;
            if (Array.isArray(af)) body.angularFactor.set(af[0], af[1], af[2]);
            else if (af.x !== undefined) body.angularFactor.set(af.x, af.y, af.z);
        }

        // Store entity reference
        body.entity = entity;

        // Add to world
        world.addBody(body);
        rigidBodyMap.push({ entity: entity, body: body });

        console.log('[Cannon Adapter] Created body for entity:', entity.name, 'type:', bodyType);
        return body;
    }

    /**
     * Create a trigger body for entities with collision but no rigidbody
     * In PlayCanvas, entities with collision but no rigidbody act as triggers
     */
    function createTriggerBody(entity, world) {
        if (!entity.collision || entity.rigidbody) {
            return null;
        }

        var shape = createCannonShape(entity);
        if (!shape) return null;

        var pos = entity.getPosition();
        var rot = entity.getRotation();

        var body = new CANNON.Body({
            mass: 0,
            position: new CANNON.Vec3(pos.x, pos.y, pos.z),
            quaternion: new CANNON.Quaternion(rot.x, rot.y, rot.z, rot.w),
            shape: shape,
            collisionResponse: false  // Trigger bodies don't produce collision response
        });

        body.entity = entity;
        body.isTrigger = true;

        world.addBody(body);
        triggerBodyMap.push({ entity: entity, body: body });
        rigidBodyMap.push({ entity: entity, body: body });

        console.log('[Cannon Adapter] Created trigger body for entity:', entity.name);
        return body;
    }

    /**
     * Create compatibility layer for rigidbody API
     */
    function wrapRigidbodyAPI(entity, cannonBody) {
        var rb = entity.rigidbody;
        if (!rb) return;

        // Store original rigidbody component
        var originalRb = {};
        for (var key in rb) {
            if (rb.hasOwnProperty(key)) {
                originalRb[key] = rb[key];
            }
        }

        // Override rigidbody methods to use Cannon.js
        // applyForce supports two calling conventions:
        //   1. applyForce(forceVec3, [pointVec3])       — two Vec3 objects
        //   2. applyForce(fx, fy, fz, [px, py, pz])     — six numbers
        rb.applyForce = function() {
            var force, point;
            var a = arguments;

            if (a.length >= 1 && typeof a[0] === 'object' && a[0] !== null && a[0].x !== undefined) {
                // Vec3 calling convention
                force = new CANNON.Vec3(a[0].x, a[0].y, a[0].z);
                if (a.length >= 2 && typeof a[1] === 'object' && a[1] !== null && a[1].x !== undefined) {
                    point = new CANNON.Vec3(a[1].x, a[1].y, a[1].z);
                }
            } else if (a.length >= 3) {
                // Number calling convention
                force = new CANNON.Vec3(a[0], a[1], a[2]);
                if (a.length >= 6) {
                    point = new CANNON.Vec3(a[3], a[4], a[5]);
                }
            } else {
                return;
            }

            if (point) {
                cannonBody.applyForce(force, point);
            } else {
                cannonBody.applyForce(force);
            }
        };

        rb.applyImpulse = function(impulse, relativePoint) {
            var cannonImpulse;
            if (impulse.x !== undefined) {
                cannonImpulse = new CANNON.Vec3(impulse.x, impulse.y, impulse.z);
            } else {
                cannonImpulse = new CANNON.Vec3(impulse, relativePoint || 0, arguments[2] || 0);
            }

            if (relativePoint && relativePoint.x !== undefined) {
                var point = new CANNON.Vec3(relativePoint.x, relativePoint.y, relativePoint.z);
                cannonBody.applyImpulse(cannonImpulse, point);
            } else {
                cannonBody.applyImpulse(cannonImpulse);
            }
        };

        rb.applyTorque = function(x, y, z) {
            var torque = new CANNON.Vec3(x, y, z);
            cannonBody.applyTorque(torque);
        };

        rb.applyTorqueImpulse = function(x, y, z) {
            // Cannon doesn't have applyTorqueImpulse, use velocity directly
            cannonBody.angularVelocity.x += x / cannonBody.mass;
            cannonBody.angularVelocity.y += y / cannonBody.mass;
            cannonBody.angularVelocity.z += z / cannonBody.mass;
        };

        rb.teleport = function(x, y, z, rx, ry, rz, rw) {
            if (typeof x === 'object') {
                // Called with Vec3
                cannonBody.position.set(x.x, x.y, x.z);
                if (y && y.x !== undefined) {
                    // Second argument is rotation Quat
                    cannonBody.quaternion.set(y.x, y.y, y.z, y.w);
                }
            } else {
                cannonBody.position.set(x, y, z);
                if (rx !== undefined) {
                    cannonBody.quaternion.set(rx, ry, rz, rw);
                }
            }
            cannonBody.velocity.set(0, 0, 0);
            cannonBody.angularVelocity.set(0, 0, 0);
        };

        // Velocity properties
        Object.defineProperty(rb, 'linearVelocity', {
            get: function() {
                return new pc.Vec3(
                    cannonBody.velocity.x,
                    cannonBody.velocity.y,
                    cannonBody.velocity.z
                );
            },
            set: function(v) {
                cannonBody.velocity.set(v.x, v.y, v.z);
            }
        });

        Object.defineProperty(rb, 'angularVelocity', {
            get: function() {
                return new pc.Vec3(
                    cannonBody.angularVelocity.x,
                    cannonBody.angularVelocity.y,
                    cannonBody.angularVelocity.z
                );
            },
            set: function(v) {
                cannonBody.angularVelocity.set(v.x, v.y, v.z);
            }
        });

        // Store cannon body reference
        entity.cannonBody = cannonBody;
    }

    /**
     * Sync Cannon bodies with PlayCanvas entities
     */
    function syncBodies() {
        if (!cannonWorld) return;

        for (var i = 0; i < rigidBodyMap.length; i++) {
            var pair = rigidBodyMap[i];
            var entity = pair.entity;
            var cannonBody = pair.body;

            if (cannonBody.type === CANNON.Body.STATIC) {
                // Sync static bodies from entity to cannon
                var pos = entity.getPosition();
                var rot = entity.getRotation();
                cannonBody.position.set(pos.x, pos.y, pos.z);
                cannonBody.quaternion.set(rot.x, rot.y, rot.z, rot.w);
            } else if (cannonBody.sleepState !== CANNON.Body.SLEEPING) {
                // Sync dynamic/kinematic bodies from cannon to entity
                entity.setPosition(
                    cannonBody.position.x,
                    cannonBody.position.y,
                    cannonBody.position.z
                );
                entity.setRotation(
                    cannonBody.quaternion.x,
                    cannonBody.quaternion.y,
                    cannonBody.quaternion.z,
                    cannonBody.quaternion.w
                );
            }
        }
    }

    /**
     * Initialize all rigidbodies in the scene
     */
    function initializeRigidbodies(app) {
        if (initialized) return;

        console.log('[Cannon Adapter] Scanning for rigidbody components...');
        
        var world = initCannonWorld(app);
        var count = 0;
        var triggerCount = 0;

        // Find all entities with rigidbody component or trigger collision
        function processEntity(entity) {
            if (entity.rigidbody && entity.collision) {
                // Entity with both rigidbody and collision = physics body
                var body = createCannonBody(entity, world);
                if (body) {
                    wrapRigidbodyAPI(entity, body);
                    count++;
                }
            } else if (entity.collision && !entity.rigidbody) {
                // Entity with collision but no rigidbody = trigger volume
                var triggerBody = createTriggerBody(entity, world);
                if (triggerBody) {
                    triggerCount++;
                }
            }

            // Process children
            var children = entity.children;
            for (var i = 0; i < children.length; i++) {
                processEntity(children[i]);
            }
        }

        processEntity(app.root);
        
        console.log('[Cannon Adapter] Initialized', count, 'rigidbodies and', triggerCount, 'triggers');
        initialized = true;

        // Hook into update loop
        var fixedTimeStep = 1/60;
        var maxSubSteps = 3;

        app.on('update', function(dt) {
            if (cannonWorld) {
                cannonWorld.step(fixedTimeStep, dt, maxSubSteps);
                syncBodies();
            }
        });

        // Expose world on app for debugging
        app.cannonWorld = cannonWorld;
    }

    /**
     * Wait for app to be ready and initialize
     */
    function waitForApp() {
        if (typeof pc !== 'undefined' && pc.Application && pc.Application.getApplication) {
            var app = pc.Application.getApplication();
            if (app && app.root && app.root.children.length > 0) {
                // Scene is loaded. Wait for assets (mesh colliders need render assets loaded)
                setTimeout(function() {
                    initializeRigidbodies(app);
                }, 200);
                return;
            }
        }
        
        // Retry after a short delay
        setTimeout(waitForApp, 50);
    }

    // Start initialization
    waitForApp();

    console.log('[Cannon Adapter] Adapter loaded, waiting for app initialization...');
})();
