var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Object3D, Quaternion, Vector3 } from "three";
import { AssetReference } from "../../engine/engine_addressables.js";
import { ObjectUtils, PrimitiveType } from "../../engine/engine_create_objects.js";
import { serializable } from "../../engine/engine_serialization_decorator.js";
import { getParam, PromiseAllWithErrors } from "../../engine/engine_utils.js";
import { setCustomVisibility } from "../../engine/js-extensions/Layers.js";
import { NeedleXRSession, NeedleXRUtils } from "../../engine/xr/api.js";
import { PlayerState } from "../../engine-components-experimental/networking/PlayerSync.js";
import { Behaviour, GameObject } from "../Component.js";
import { SyncedTransform } from "../SyncedTransform.js";
import { AvatarMarker } from "./WebXRAvatar.js";
import { XRFlag } from "./XRFlag.js";
const debug = getParam("debugwebxr");
const flipForwardQuaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
/**
 * Avatar component to setup a WebXR avatar with head and hand objects.
 *
 * The avatar will automatically synchronize the head and hand objects with the XR rig when entering an XR session.
 *
 * @summary WebXR Avatar component for head and hands synchronization
 * @category XR
 * @category Networking
 * @group Components
 */
export class Avatar extends Behaviour {
    head;
    leftHand;
    rightHand;
    _leftHandMeshes;
    _rightHandMeshes;
    _syncTransforms;
    async onEnterXR(_args) {
        if (!this.activeAndEnabled)
            return;
        if (debug)
            console.warn("AVATAR ENTER XR", this.guid, this.sourceId, this, this.activeAndEnabled);
        if (this._syncTransforms)
            this._syncTransforms.length = 0;
        await this.prepareAvatar();
        const playerstate = PlayerState.getFor(this);
        if (playerstate?.owner) {
            const marker = this.gameObject.addComponent(AvatarMarker);
            marker.avatar = this.gameObject;
            marker.connectionId = playerstate.owner;
        }
        else if (this.context.connection.isConnected)
            console.error("No player state found for avatar", this);
        // don't destroy the avatar when entering XR and not connected to a networking backend
        else if (playerstate && !this.context.connection.isConnected)
            playerstate.dontDestroy = true;
    }
    onLeaveXR(_args) {
        const marker = this.gameObject.getComponent(AvatarMarker);
        if (marker) {
            marker.destroy();
        }
    }
    onUpdateXR(args) {
        if (!this.activeAndEnabled)
            return;
        const isLocalPlayer = PlayerState.isLocalPlayer(this);
        if (!isLocalPlayer)
            return;
        const xr = args.xr;
        // make sure the avatar is inside the active rig
        if (xr.rig && xr.rig.gameObject !== this.gameObject.parent) {
            this.gameObject.position.set(0, 0, 0);
            this.gameObject.rotation.set(0, 0, 0);
            this.gameObject.scale.set(1, 1, 1);
            xr.rig.gameObject.add(this.gameObject);
        }
        // this.gameObject.position.copy(xr.rig!.gameObject.position);
        // this.gameObject.quaternion.copy(xr.rig!.gameObject.quaternion);
        // this.gameObject.scale.set(1, 1, 1);
        if (this._syncTransforms && isLocalPlayer) {
            for (const sync of this._syncTransforms) {
                sync.fastMode = true;
                if (!sync.isOwned())
                    sync.requestOwnership();
            }
        }
        // synchronize head
        if (this.head && this.context.mainCamera) {
            const headObj = this.head.asset;
            headObj.position.copy(this.context.mainCamera.position);
            headObj.position.x *= -1;
            headObj.position.z *= -1;
            headObj.quaternion.copy(this.context.mainCamera.quaternion);
            headObj.quaternion.x *= -1;
            // HACK: XRFlag limitation workaround to make sure first person user head is never rendered
            if (this.context.time.frameCount % 10 === 0 && this.head.asset) {
                const xrflags = GameObject.getComponentsInChildren(this.head.asset, XRFlag);
                for (const flag of xrflags) {
                    flag.enabled = false;
                    flag.gameObject.visible = false;
                }
            }
        }
        // synchronize hands
        const leftCtrl = args.xr.leftController;
        const leftObj = this.leftHand?.asset;
        if (leftCtrl && leftObj) {
            leftObj.position.copy(leftCtrl.gripPosition);
            leftObj.quaternion.copy(leftCtrl.gripQuaternion);
            leftObj.quaternion.multiply(flipForwardQuaternion);
            leftObj.visible = leftCtrl.isTracking;
            this.updateHandVisibility(leftCtrl, leftObj, this._leftHandMeshes);
        }
        else if (leftObj && leftObj.visible) {
            leftObj.visible = false;
        }
        const right = args.xr.rightController;
        const rightObj = this.rightHand?.asset;
        if (right && rightObj) {
            rightObj.position.copy(right.gripPosition);
            rightObj.quaternion.copy(right.gripQuaternion);
            rightObj.quaternion.multiply(flipForwardQuaternion);
            rightObj.visible = right.isTracking;
            this.updateHandVisibility(right, rightObj, this._rightHandMeshes);
        }
        else if (rightObj && rightObj.visible) {
            rightObj.visible = false;
        }
    }
    onBeforeRender() {
        if (this.context.xr) {
            if (this.context.time.frame % 10 === 0)
                this.updateRemoteAvatarVisibility();
        }
    }
    updateHandVisibility(controller, avatarHand, meshes) {
        if (meshes) {
            // Hide the hand meshes for the local user if another model (e.g. the controller model) is being rendered
            // We don't set the visible flag here because it would also disable SyncedTransforms networking
            const hasOtherRenderingModel = controller.model && controller.model.visible && controller.model !== avatarHand;
            meshes.forEach(mesh => { setCustomVisibility(mesh, !hasOtherRenderingModel); });
        }
    }
    updateRemoteAvatarVisibility() {
        if (this.context.connection.isConnected) {
            const state = PlayerState.getFor(this);
            if (state && state.isLocalPlayer == false) {
                const sync = NeedleXRSession.getXRSync(this.context);
                if (sync) {
                    if (sync.hasState(state.owner)) {
                        this.tryFindAvatarObjectsIfMissing();
                        const leftObj = this.leftHand?.asset;
                        if (leftObj) {
                            leftObj.visible = sync?.isTracking(state.owner, "left") ?? false;
                        }
                        const rightObj = this.rightHand?.asset;
                        if (rightObj) {
                            rightObj.visible = sync?.isTracking(state.owner, "right") ?? false;
                        }
                    }
                }
                // HACK: XRFlag limitation workaround to make sure first person user head of OTHER users is ALWAYS rendered
                if (this.head?.asset) {
                    const xrflags = GameObject.getComponentsInChildren(this.head.asset, XRFlag);
                    for (const flag of xrflags) {
                        flag.enabled = false;
                        flag.gameObject.visible = true;
                    }
                }
            }
        }
    }
    tryFindAvatarObjectsIfMissing() {
        // if no avatar objects are set, try to find them
        if (!this.head || !this.leftHand || !this.rightHand) {
            const res = { head: this.head, leftHand: this.leftHand, rightHand: this.rightHand };
            NeedleXRUtils.tryFindAvatarObjects(this.gameObject, this.sourceId || "", res);
            if (res.head)
                this.head = res.head;
            if (res.leftHand)
                this.leftHand = res.leftHand;
            if (res.rightHand)
                this.rightHand = res.rightHand;
        }
    }
    async prepareAvatar() {
        // if no avatar objects are set, try to find them
        this.tryFindAvatarObjectsIfMissing();
        if (!this.head) {
            const head = new Object3D();
            head.name = "Head";
            const cube = ObjectUtils.createPrimitive(PrimitiveType.Cube);
            head.add(cube);
            this.gameObject.add(head);
            this.head = new AssetReference("", this.sourceId, head);
            if (debug)
                console.log("Create head", head);
        }
        else if (this.head instanceof Object3D) {
            this.head = new AssetReference("", this.sourceId, this.head);
        }
        if (!this.rightHand) {
            const rightHand = new Object3D();
            rightHand.name = "Right Hand";
            this.gameObject.add(rightHand);
            this.rightHand = new AssetReference("", this.sourceId, rightHand);
            if (debug)
                console.log("Create right hand", rightHand);
        }
        else if (this.rightHand instanceof Object3D) {
            this.rightHand = new AssetReference("", this.sourceId, this.rightHand);
        }
        if (!this.leftHand) {
            const leftHand = new Object3D();
            leftHand.name = "Left Hand";
            this.gameObject.add(leftHand);
            this.leftHand = new AssetReference("", this.sourceId, leftHand);
            if (debug)
                console.log("Create left hand", leftHand);
        }
        else if (this.leftHand instanceof Object3D) {
            this.leftHand = new AssetReference("", this.sourceId, this.leftHand);
        }
        await this.loadAvatarObjects(this.head, this.leftHand, this.rightHand);
        this._leftHandMeshes = [];
        this.leftHand.asset?.traverse((obj) => { if (obj?.isMesh)
            this._leftHandMeshes.push(obj); });
        this._rightHandMeshes = [];
        this.rightHand.asset?.traverse((obj) => { if (obj?.isMesh)
            this._rightHandMeshes.push(obj); });
        if (PlayerState.isLocalPlayer(this.gameObject)) {
            this._syncTransforms = GameObject.getComponentsInChildren(this.gameObject, SyncedTransform);
        }
    }
    async loadAvatarObjects(head, left, right) {
        const pHead = head.loadAssetAsync();
        const pHandLeft = left.loadAssetAsync();
        const pHandRight = right.loadAssetAsync();
        const promises = new Array();
        if (pHead)
            promises.push(pHead);
        if (pHandLeft)
            promises.push(pHandLeft);
        if (pHandRight)
            promises.push(pHandRight);
        const res = await PromiseAllWithErrors(promises);
        if (debug)
            console.log("Avatar loaded results:", res);
    }
}
__decorate([
    serializable(AssetReference)
], Avatar.prototype, "head", void 0);
__decorate([
    serializable(AssetReference)
], Avatar.prototype, "leftHand", void 0);
__decorate([
    serializable(AssetReference)
], Avatar.prototype, "rightHand", void 0);
//# sourceMappingURL=Avatar.js.map