import { EventBus } from '../core/EventBus.js';

/* ────────────────────────────────────────────────────────────────
   3D Runtime — World3D, Camera3D, Renderer3D, Physics3D
   
   Phase 5: 3D support with abstraction layer for Three.js /
   custom renderers and Rapier.js physics
──────────────────────────────────────────────────────────────── */
const v3 = {
    create: (x = 0, y = 0, z = 0) => ({ x, y, z }),
    zero: () => ({ x: 0, y: 0, z: 0 }),
    one: () => ({ x: 1, y: 1, z: 1 }),
    clone: (a) => ({ x: a.x, y: a.y, z: a.z }),
    add: (a, b) => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }),
    sub: (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }),
    scale: (a, s) => ({ x: a.x * s, y: a.y * s, z: a.z * s }),
    dot: (a, b) => a.x * b.x + a.y * b.y + a.z * b.z,
    cross: (a, b) => ({
        x: a.y * b.z - a.z * b.y,
        y: a.z * b.x - a.x * b.z,
        z: a.x * b.y - a.y * b.x,
    }),
    length: (a) => Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z),
    normalize: (a) => {
        const len = Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) || 1;
        return { x: a.x / len, y: a.y / len, z: a.z / len };
    },
    dist: (a, b) => {
        const dx = a.x - b.x, dy = a.y - b.y, dz = a.z - b.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    },
    lerp: (a, b, t) => ({
        x: a.x + (b.x - a.x) * t,
        y: a.y + (b.y - a.y) * t,
        z: a.z + (b.z - a.z) * t,
    }),
    negate: (a) => ({ x: -a.x, y: -a.y, z: -a.z }),
    min: (a, b) => ({
        x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), z: Math.min(a.z, b.z),
    }),
    max: (a, b) => ({
        x: Math.max(a.x, b.x), y: Math.max(a.y, b.y), z: Math.max(a.z, b.z),
    }),
};
const quat = {
    identity: () => ({ x: 0, y: 0, z: 0, w: 1 }),
    fromEuler: (x, y, z) => {
        const hx = x * 0.5, hy = y * 0.5, hz = z * 0.5;
        const cx = Math.cos(hx), sx = Math.sin(hx);
        const cy = Math.cos(hy), sy = Math.sin(hy);
        const cz = Math.cos(hz), sz = Math.sin(hz);
        return {
            x: sx * cy * cz - cx * sy * sz,
            y: cx * sy * cz + sx * cy * sz,
            z: cx * cy * sz - sx * sy * cz,
            w: cx * cy * cz + sx * sy * sz,
        };
    },
    multiply: (a, b) => ({
        x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
        y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
        z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
        w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
    }),
    normalize: (q) => {
        const len = Math.sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w) || 1;
        return { x: q.x / len, y: q.y / len, z: q.z / len, w: q.w / len };
    },
    slerp: (a, b, t) => {
        let dot = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
        const bSign = dot < 0 ? -1 : 1;
        dot = Math.abs(dot);
        if (dot > 0.9999) {
            return {
                x: a.x + (b.x * bSign - a.x) * t,
                y: a.y + (b.y * bSign - a.y) * t,
                z: a.z + (b.z * bSign - a.z) * t,
                w: a.w + (b.w * bSign - a.w) * t,
            };
        }
        const theta = Math.acos(dot);
        const sinT = Math.sin(theta);
        const wA = Math.sin((1 - t) * theta) / sinT;
        const wB = Math.sin(t * theta) / sinT * bSign;
        return {
            x: a.x * wA + b.x * wB, y: a.y * wA + b.y * wB,
            z: a.z * wA + b.z * wB, w: a.w * wA + b.w * wB,
        };
    },
    /** Rotate a Vec3 by quaternion. */
    rotateVec3: (q, v) => {
        const qv = { x: q.x, y: q.y, z: q.z };
        const uv = v3.cross(qv, v);
        const uuv = v3.cross(qv, uv);
        return v3.add(v, v3.add(v3.scale(uv, 2 * q.w), v3.scale(uuv, 2)));
    },
};
/* ══════════════════════════════════════════════════════════════
   Sprint 5.1 — World3D
   ══════════════════════════════════════════════════════════════ */
class World3D {
    constructor() {
        this.events = new EventBus();
        this.transforms = new Map();
        this.meshes = new Map();
        this.lights = new Map();
        this.cameras = new Map();
        this.materials = new Map();
        this._nextId = 1;
    }
    /* ── Entity creation ────────────────────────────────────────── */
    createTransform(overrides) {
        const id = this._nextId++;
        const t = {
            position: v3.zero(),
            rotation: quat.identity(),
            scale: v3.one(),
            ...overrides,
            entityId: id, // always canonical
        };
        this.transforms.set(id, t);
        this.events.emit('transform:added', id);
        return t;
    }
    addMesh(entityId, primitive = 'box', assetId = null) {
        const mesh = {
            entityId, primitive, assetId,
            castShadow: true, receiveShadow: true, visible: true,
        };
        this.meshes.set(entityId, mesh);
        this.events.emit('mesh:added', entityId);
        return mesh;
    }
    addLight(entityId, lightType = 'point', color = { r: 1, g: 1, b: 1, a: 1 }) {
        const light = {
            entityId, lightType, color, intensity: 1,
            range: 10, spotAngle: Math.PI / 6, castShadow: false,
        };
        this.lights.set(entityId, light);
        this.events.emit('light:added', entityId);
        return light;
    }
    addCamera(entityId, mode = 'perspective') {
        const cam = {
            entityId, mode, fov: 60, near: 0.1, far: 1000, orthoSize: 10,
        };
        this.cameras.set(entityId, cam);
        this.events.emit('camera:added', entityId);
        return cam;
    }
    /* ── Getters ────────────────────────────────────────────────── */
    getTransform(id) { return this.transforms.get(id); }
    getMesh(id) { return this.meshes.get(id); }
    getLight(id) { return this.lights.get(id); }
    getCamera(id) { return this.cameras.get(id); }
    allTransforms() { return Array.from(this.transforms.values()); }
    allMeshes() { return Array.from(this.meshes.values()); }
    allLights() { return Array.from(this.lights.values()); }
    allCameras() { return Array.from(this.cameras.values()); }
    /* ── Materials ──────────────────────────────────────────────── */
    registerMaterial(mat) {
        this.materials.set(mat.id, mat);
    }
    getMaterial(id) {
        return this.materials.get(id);
    }
    allMaterials() {
        return Array.from(this.materials.values());
    }
    static createDefaultMaterial(name = 'default') {
        return {
            id: `mat_${Date.now()}_${Math.random().toString(36).slice(2, 5)}`,
            name,
            albedoColor: { r: 0.8, g: 0.8, b: 0.8, a: 1 },
            metallic: 0,
            roughness: 0.5,
            emissiveColor: { r: 0, g: 0, b: 0, a: 1 },
            emissiveIntensity: 0,
            albedoTextureId: null,
            normalTextureId: null,
            metallicRoughnessTextureId: null,
            transparent: false,
            opacity: 1,
        };
    }
    /* ── Remove ─────────────────────────────────────────────────── */
    removeEntity(id) {
        this.transforms.delete(id);
        this.meshes.delete(id);
        this.lights.delete(id);
        this.cameras.delete(id);
        this.events.emit('entity:removed', id);
    }
    clear() {
        this.transforms.clear();
        this.meshes.clear();
        this.lights.clear();
        this.cameras.clear();
        this.materials.clear();
    }
}
function createDefaultCamera3DSettings(controller = 'orbit') {
    return {
        controller,
        position: v3.create(0, 5, 10),
        target: v3.zero(),
        up: v3.create(0, 1, 0),
        fov: 60,
        near: 0.1,
        far: 1000,
        distance: 10,
        yaw: 0,
        pitch: -0.3,
        minPitch: -Math.PI / 2 + 0.1,
        maxPitch: Math.PI / 2 - 0.1,
        sensitivity: 0.003,
        smoothing: 0.1,
    };
}
function updateOrbitCamera(settings, dt) {
    settings.pitch = Math.max(settings.minPitch, Math.min(settings.maxPitch, settings.pitch));
    const targetPos = {
        x: settings.target.x + settings.distance * Math.cos(settings.pitch) * Math.sin(settings.yaw),
        y: settings.target.y + settings.distance * Math.sin(settings.pitch),
        z: settings.target.z + settings.distance * Math.cos(settings.pitch) * Math.cos(settings.yaw),
    };
    const lerp = Math.min(1, settings.smoothing > 0 ? dt / settings.smoothing : 1);
    settings.position = v3.lerp(settings.position, targetPos, lerp);
}
/** Simple physics world that delegates to a backend. */
class PhysicsWorld3D {
    constructor(config) {
        var _a, _b;
        this.events = new EventBus();
        this.backend = null;
        this.config = {
            gravity: (_a = config === null || config === void 0 ? void 0 : config.gravity) !== null && _a !== void 0 ? _a : v3.create(0, -9.81, 0),
            timestep: (_b = config === null || config === void 0 ? void 0 : config.timestep) !== null && _b !== void 0 ? _b : 1 / 60,
        };
    }
    setBackend(backend) {
        this.backend = backend;
        backend.init(this.config);
        this.events.emit('physics3d:ready', null);
    }
    step(dt) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.step(dt);
    }
    addBody(entityId, body, position, rotation) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.addRigidBody(entityId, body, position, rotation);
    }
    addCollider(entityId, def) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.addCollider(entityId, def);
    }
    removeBody(entityId) {
        var _a, _b;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.removeRigidBody(entityId);
        (_b = this.backend) === null || _b === void 0 ? void 0 : _b.removeCollider(entityId);
    }
    getTransform(entityId) {
        var _a, _b;
        const pos = (_a = this.backend) === null || _a === void 0 ? void 0 : _a.getPosition(entityId);
        const rot = (_b = this.backend) === null || _b === void 0 ? void 0 : _b.getRotation(entityId);
        if (!pos || !rot)
            return null;
        return { position: pos, rotation: rot };
    }
    applyForce(entityId, force) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.applyForce(entityId, force);
    }
    applyImpulse(entityId, impulse) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.applyImpulse(entityId, impulse);
    }
    raycast(origin, direction, maxDist = 100) {
        var _a, _b;
        return (_b = (_a = this.backend) === null || _a === void 0 ? void 0 : _a.raycast(origin, direction, maxDist)) !== null && _b !== void 0 ? _b : null;
    }
    destroy() {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.destroy();
        this.backend = null;
    }
}
/** 3D Renderer that delegates to a swappable backend (Three.js, etc). */
class Renderer3D {
    constructor() {
        this.backend = null;
        this.canvas = null;
    }
    setBackend(backend) {
        this.backend = backend;
        if (this.canvas)
            backend.init(this.canvas);
    }
    init(canvas) {
        var _a;
        this.canvas = canvas;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.init(canvas);
    }
    resize(width, height) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.resize(width, height);
    }
    render(world, camera) {
        if (!this.backend)
            return;
        // Sync all transforms + meshes
        for (const t of world.allTransforms()) {
            const mesh = world.getMesh(t.entityId);
            this.backend.syncEntity(t.entityId, t, mesh);
        }
        // Sync lights
        for (const l of world.allLights()) {
            const t = world.getTransform(l.entityId);
            if (t)
                this.backend.syncLight(l.entityId, t, l);
        }
        this.backend.render(world, camera);
    }
    setMaterial(entityId, material) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.setMaterial(entityId, material);
    }
    removeEntity(entityId) {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.removeEntity(entityId);
    }
    dispose() {
        var _a;
        (_a = this.backend) === null || _a === void 0 ? void 0 : _a.dispose();
        this.backend = null;
    }
}
const DEFAULT_CHARACTER_CONFIG = {
    height: 1.8,
    radius: 0.3,
    stepHeight: 0.3,
    slopeLimit: Math.PI / 4,
    moveSpeed: 5,
    jumpForce: 8,
    gravity: 20,
    groundCheckDistance: 0.1,
};
/** Simple character controller for FPS / third-person games. */
function updateCharacterController(state, input, config, dt) {
    // Horizontal movement
    const move = v3.create(input.moveX * config.moveSpeed, 0, input.moveZ * config.moveSpeed);
    // Gravity
    if (!state.grounded) {
        state.velocity.y -= config.gravity * dt;
    }
    else {
        state.velocity.y = 0;
        if (input.jump) {
            state.velocity.y = config.jumpForce;
            state.jumping = true;
            state.grounded = false;
        }
    }
    // Combine
    const displacement = v3.create(move.x * dt, state.velocity.y * dt, move.z * dt);
    return displacement;
}

export { DEFAULT_CHARACTER_CONFIG, PhysicsWorld3D, Renderer3D, World3D, createDefaultCamera3DSettings, quat, updateCharacterController, updateOrbitCamera, v3 };
//# sourceMappingURL=Runtime3D.js.map
