import util from "./util.js"
import InternalError from "./error/InternalError.js"
import EAvatarRelationRank from "./enum/EAvatarRelationRank.js"
import AvatarGroup from "./enum/AvatarGroup.js"
import MotionType from "./enum/MotionType.js"
import Queue from "./Queue.js"
import Logger from "./Logger.js"

const logger = new Logger('xverse-avatar')
export default class XverseAvatar extends EventEmitter {
    constructor({userId: e, isHost: t, room: r, avatarId: n, isSelf: o, group: a=AvatarGroup.Npc}) {
        super();
        E(this, "xAvatar");
        E(this, "_isHost", !1);
        E(this, "_room");
        E(this, "_withModel", !1);
        E(this, "_userId");
        E(this, "group", AvatarGroup.User);
        E(this, "state", "idle");
        E(this, "isLoading", !0);
        E(this, "_isMoving", !1);
        E(this, "_isRotating", !1);
        E(this, "_failed", !1);
        E(this, "disconnected", !1);
        E(this, "_avatarId");
        E(this, "prioritySync", !1);
        E(this, "priority", EAvatarRelationRank.Stranger);
        E(this, "_avatarModel");
        E(this, "_motionType", MotionType.Walk);
        E(this, "isSelf", !1);
        E(this, "_lastAnimTraceId", "");
        E(this, "statusSyncQueue", new Queue);
        E(this, "extraInfo", {});
        E(this, "setPosition", e=>{
            var t;
            !this._room.signal.isUpdatedYUV || (t = this.xAvatar) == null || t.setPosition(positionPrecisionProtect(e))
        }
        );
        E(this, "setRotation", e=>{
            var t;
            !this._room.signal.isUpdatedYUV || (t = this.xAvatar) == null || t.setRotation(rotationPrecisionProtect(e))
        }
        );
        E(this, "stopAnimation", ()=>{
            var e, t;
            (t = (e = this.xAvatar) == null ? void 0 : e.controller) == null || t.stopAnimation()
        }
        );
        E(this, "_playAnimation", async(e,t=!0,r=!1)=>{
            var o;
            if (!this._room.signal.isUpdatedYUV)
                return;
            if (this.state !== "idle" && !r)
                return logger.debug("_playAnimation", "state is not idle"),
                Promise.resolve("_playAnimation, state is not idle");
            const n = Date.now();
            try {
                if (!((o = this.xAvatar) != null && o.controller))
                    return Promise.reject(new InternalError(`[avatar: ${this.userId}] Play animation failed: ${e}, no controller`));
                this.isSelf && setTimeout(()=>{
                    logger.infoAndReportMeasurement({
                        tag: e,
                        startTime: n,
                        value: 0,
                        metric: "playAnimationStart"
                    })
                }
                );
                const a = util.uuid();
                this._lastAnimTraceId = a,
                await this.xAvatar.controller.playAnimation(e, t),
                a === this._lastAnimTraceId && !this.isMoving && !t && e !== "Idle" && this.xAvatar.controller.playAnimation("Idle", t).catch(s=>{
                    logger.error(`[avatar: ${this.userId}] Play animation failed [force idle]`, s)
                }
                ),
                this.isSelf && logger.infoAndReportMeasurement({
                    tag: e,
                    startTime: n,
                    extra: {
                        loop: t
                    },
                    metric: "playAnimationEnd"
                })
            } catch (a) {
                return logger.error(`[avatar: ${this.userId}] Play animation failed: ${e}`, a),
                this.isSelf && logger.infoAndReportMeasurement({
                    tag: e,
                    startTime: n,
                    metric: "playAnimationEnd",
                    error: a,
                    extra: {
                        loop: t
                    }
                }),
                Promise.reject(a)
            }
        }
        );
        E(this, "changeComponents", async e=>{
            const {mode: t, endAnimation: r=""} = e || {}
              , n = JSON.parse(JSON.stringify(e.avatarComponents));
            let o = avatarComponentsValidate(n, this._avatarModel);
            return !ChangeComponentsMode[t] && !o && (o = new ParamError(`changeComponents failed, mode: ${t} is invalid`)),
            o ? (logger.error(o),
            Promise.reject(o)) : this._changeComponents({
                avatarComponents: n,
                mode: t,
                endAnimation: r
            }).then(()=>{
                this.isSelf && t !== ChangeComponentsMode.Preview && this.avatarComponentsSync(this.avatarComponents)
            }
            )
        }
        );
        E(this, "_changeComponents", async e=>{
            var o;
            const {avatarComponents: t=[], mode: r} = e || {}
              , n = Date.now();
            try {
                if (!this.xAvatar)
                    return Promise.reject(new InternalError("changeComponents failed, without instance: xAvatar"));
                const a = await avatarComponentsModify(this._avatarModel, t)
                  , s = []
                  , l = await avatarComponentsParser(this._avatarModel, a, this.avatarComponents);
                if (l.length === 0)
                    return this.avatarComponents;
                await this.beforeChangeComponentsHook(e);
                for (const u of l) {
                    const {id: c, type: h, url: f, suitComb: d} = u;
                    s.push((o = this.xAvatar) == null ? void 0 : o.addComponent(c, h, f, d))
                }
                return await Promise.all(s),
                this.emit("componentsChanged", {
                    components: this.avatarComponents,
                    mode: r
                }),
                this.isSelf && logger.infoAndReportMeasurement({
                    tag: "changeComponents",
                    startTime: n,
                    metric: "changeComponents",
                    extra: {
                        inputComponents: t,
                        finalComponents: this.avatarComponents,
                        mode: ChangeComponentsMode[r]
                    }
                }),
                this.avatarComponents
            } catch (a) {
                return this.isSelf && logger.infoAndReportMeasurement({
                    tag: "changeComponents",
                    startTime: n,
                    metric: "changeComponents",
                    error: a,
                    extra: {
                        inputComponents: t,
                        finalComponents: this.avatarComponents,
                        mode: ChangeComponentsMode[r]
                    }
                }),
                Promise.reject(a)
            }
        }
        );
        E(this, "avatarComponentsSync", e=>{
            e = e.map(t=>({
                type: t.type,
                id: t.id
            })),
            this._room.actionsHandler.avatarComponentsSync(e)
        }
        );
        E(this, "hide", ()=>{
            var e;
            if ((e = this.xAvatar) != null && e.hide())
                return Promise.resolve(`avatar: ${this.userId} hide success`);
            {
                const t = `avatar: ${this.userId} hide failed ${!this.xAvatar && "without instance: xAvatar"}`;
                return logger.warn(t),
                Promise.reject(t)
            }
        }
        );
        E(this, "show", ()=>{
            var e;
            if ((e = this.xAvatar) != null && e.show())
                return Promise.resolve(`avatar: ${this.userId} show success`);
            {
                const t = `avatar: ${this.userId} show failed ${!this.xAvatar && "without instance: xAvatar"}`;
                return logger.warn(t),
                Promise.reject(t)
            }
        }
        );
        E(this, "sayTimer");
        this._userId = e,
        this._room = r,
        this.isSelf = o || !1,
        this._withModel = !!n,
        this._isHost = t || !1,
        this._avatarId = n,
        this.group = a,
        this._room.modelManager.getAvatarModelList().then(s=>{
            const l = s.find(u=>u.id === n);
            l && (this._avatarModel = l)
        }
        )
    }
    get avatarId() {
        return this._avatarId
    }
    get isRender() {
        var e;
        return !!((e = this.xAvatar) != null && e.isRender)
    }
    get isHidden() {
        var e;
        return !!((e = this.xAvatar) != null && e.isHide)
    }
    get motionType() {
        return this._motionType
    }
    set motionType(e) {
        this._motionType = e
    }
    get nickname() {
        var e;
        return (e = this.xAvatar) == null ? void 0 : e.nickName
    }
    get words() {
        var e;
        return (e = this.xAvatar) == null ? void 0 : e.words
    }
    get isHost() {
        return this._isHost
    }
    get failed() {
        return this._failed
    }
    get scale() {
        var e;
        return (e = this.xAvatar) == null ? void 0 : e.scale
    }
    get animations() {
        var e;
        return !this.xAvatar || !this.xAvatar.controller ? [] : ((e = this.xAvatar) == null ? void 0 : e.getAvaliableAnimations()) || []
    }
    get position() {
        var e;
        return (e = this.xAvatar) == null ? void 0 : e.position
    }
    get rotation() {
        var e;
        return (e = this.xAvatar) == null ? void 0 : e.rotation
    }
    get pose() {
        return {
            position: this.position,
            angle: this.rotation
        }
    }
    get id() {
        return this.userId
    }
    get isMoving() {
        return this._isMoving
    }
    set isMoving(e) {
        this._isMoving = e,
        this.state = e ? "moving" : "idle"
    }
    get isRotating() {
        return this._isRotating
    }
    set isRotating(e) {
        this._isRotating = e,
        this.state = e ? "rotating" : "idle"
    }
    get withModel() {
        return this._withModel
    }
    get avatarComponents() {
        var e;
        return JSON.parse(JSON.stringify(((e = this.xAvatar) == null ? void 0 : e.clothesList) || []))
    }
    get userId() {
        return this._userId
    }
    get removeWhenDisconnected() {
        return this.extraInfo && this.extraInfo.removeWhenDisconnected !== void 0 ? this.extraInfo.removeWhenDisconnected : !0
    }
    setConnectionStatus(e) {
        this.disconnected !== e && (this.disconnected = e,
        e ? this.emit("disconnected") : this.emit("reconnected"),
        logger.warn(`avatar ${this.userId} status changed, disconnected:`, e))
    }
    setScale(e) {
        var t;
        (t = this.xAvatar) == null || t.setScale(e > 0 ? e : 1)
    }
    async playAnimation(e) {
        const {animationName: t, loop: r, extra: n} = e || {};
        if (this.isSelf) {
            if (this.isMoving)
                try {
                    await this.stopMoving()
                } catch (a) {
                    return logger.error(`stopMoving error before playAnimation ${t}`, a),
                    Promise.reject(`stopMoving error before playAnimation ${t}`)
                }
            const o = {
                info: {
                    userId: this.userId,
                    animation: t,
                    loop: r,
                    extra: encodeURIComponent(n || "")
                },
                broadcastType: CoreBroadcastType.PlayAnimation
            };
            this._room.avatarManager.broadcast.broadcast({
                data: o
            })
        }
        return this.emit("animationStart", {
            animationName: t,
            extra: safeDecodeURIComponent(n || "")
        }),
        this._playAnimation(t, r).then(()=>{
            this.emit("animationEnd", {
                animationName: t,
                extra: safeDecodeURIComponent(n || "")
            })
        }
        )
    }
    async beforeChangeComponentsHook(e) {}
    turnTo(e) {
        if (this._room.viewMode === "observer") {
            this._room.sceneManager.cameraComponent.MainCamera.setTarget(ue4Position2Xverse(e.point));
            return
        }
        return this._room.actionsHandler.turnTo(e).then(()=>{
            this.emit("viewChanged", {
                extra: (e == null ? void 0 : e.extra) || ""
            })
        }
        )
    }
    async moveTo(e) {
        const {point: t, extra: r=""} = e || {};
        if (!this.position)
            return Promise.reject(new ParamError("avatar position is empty"));
        if (typeof r != "string" || typeof r == "string" && r.length > 64) {
            const a = "extra shoud be string which length less than 64";
            return logger.warn(a),
            Promise.reject(new ParamError(a))
        }
        const o = util.getDistance(this.position, t) / 100 > 100 ? MotionType.Run : MotionType.Walk;
        return this._room.actionsHandler.moveTo({
            point: t,
            motionType: o,
            extra: r
        })
    }
    async stopMoving() {
        return this._room.actionsHandler.stopMoving()
    }
    rotateTo(e) {
        return this._room.actionsHandler.rotateTo(e)
    }
    setRayCast(e) {
        this.xAvatar && (this.xAvatar.isRayCastEnable = e)
    }
    say(e, t) {
        if (this.sayTimer && window.clearTimeout(this.sayTimer),
        !this.xAvatar) {
            logger.error("say failed, without instance: xAvatar");
            return
        }
        this.xAvatar.say(e, {
            scale: this.xAvatar.scale,
            isUser: this.group === AvatarGroup.User
        }),
        !(t === void 0 || t <= 0) && (this.sayTimer = window.setTimeout(()=>{
            this.silent()
        }
        , t))
    }
    silent() {
        var e;
        if (!this.xAvatar) {
            logger.error("silent failed, without instance: xAvatar");
            return
        }
        (e = this.xAvatar) == null || e.silent()
    }
    setMotionType({type: e=MotionType.Walk}) {
        return this.motionType === e ? Promise.resolve() : this._room.actionsHandler.setMotionType(e).then(()=>{
            this._motionType = e
        }
        )
    }
    setNickname(e) {
        return this._room.actionsHandler.setNickName(encodeURIComponent(e))
    }
    _setNickname(e) {
        var r, n;
        if (!e)
            return;
        const t = safeDecodeURIComponent(e);
        ((r = this.xAvatar) == null ? void 0 : r.nickName) !== t && (this.isSelf && (this._room.updateCurrentNetworkOptions({
            nickname: t
        }),
        this._room.options.nickname = t),
        (n = this.xAvatar) == null || n.setNickName(t, {
            scale: this.xAvatar.scale
        }))
    }
    _move(e) {
        var s;
        const {start: t, end: r, walkSpeed: n, moveAnimation: o="Walking", inter: a=[]} = e || {};
        return (s = this.xAvatar) == null ? void 0 : s.move(t, r, n, o, a)
    }
    setPickBoxScale(e=1) {
        return this.xAvatar ? (this.xAvatar.setPickBoxScale(e),
        !0) : (logger.error("setPickBoxScale failed, without instance: xAvatar"),
        !1)
    }
    transfer(e) {
        const {player: t, camera: r, areaName: n, attitude: o, pathName: a} = e;
        return this._room.actionsHandler.transfer({
            renderType: RenderType.RotationVideo,
            player: t,
            camera: r,
            areaName: n,
            attitude: o,
            pathName: a,
            tag: "transfer"
        })
    }
    avatarLoadedHook() {}
    avatarStartMovingHook() {}
    avatarStopMovingHook() {}
    async statusSync(e) {
        var t, r, n;
        try {
            if ((t = e.event) != null && t.rotateEvent) {
                const {angle: o, speed: a} = e.event.rotateEvent
                  , s = this.motionType === MotionType.Run ? "Running" : "Walking";
                this.rotation && (this.rotation.yaw = this.rotation.yaw % 360,
                o.yaw - this.rotation.yaw > 180 && (o.yaw = 180 - o.yaw),
                this.isRotating = !0,
                await this.xAvatar.rotateTo(o, this.rotation, s).then(()=>{
                    this._playAnimation("Idle", !0),
                    this.isRotating = !1
                }
                ))
            }
            if (e.event && (((r = e.event) == null ? void 0 : r.points.length) || 0) > 1 && !this.isSelf) {
                this.isMoving = !0,
                e.playerState.attitude && (this._motionType = e.playerState.attitude);
                const o = this.motionType === MotionType.Run ? "Running" : "Walking"
                  , a = this._room.skin.routeList.find(l=>l.areaName === this._room.currentState.areaName)
                  , s = ((a == null ? void 0 : a.step) || 7.5) * 30 * (25 / 30);
                this.position && await this._move({
                    start: this.position,
                    end: e.event.points[e.event.points.length - 1],
                    walkSpeed: s,
                    moveAnimation: o,
                    inter: (n = e.event) == null ? void 0 : n.points.slice(0, -1)
                }).then(()=>{
                    this.isMoving = !1
                }
                )
            }
        } catch {
            return
        }
    }
}