import CoreBroadcastType from "./enum/CoreBroadcastType.js"
import AvatarGroup from "./enum/AvatarGroup.js"
import Broadcast from "./Broadcast.js"
import {avatarLoader} from "./XAvatarLoader.js"
import SyncEventType from "./enum/SyncEventType.js"
import GetStateTypes from "./enum/GetStateTypes.js"
import EAvatarRelationRank from "./enum/EAvatarRelationRank.js"
import Person from "./enum/Person.js"
import XverseAvatar from "./XverseAvatar.js"
import MotionType from "./enum/MotionType.js"
import TimeoutError from "./error/TimeoutError.js"
import Logger from "./Logger.js"

const logger = new Logger('xverse-avatar-manager')
export default class XverseAvatarManager extends EventEmitter {
    constructor(e) {
        super();
        this.xAvatarManager = null
        this.avatars = new Map
        this.syncAvatarsLength = 0
        this._room = e;
        this._usersStatistics();
        this.broadcast = this.setupBroadcast();
        e.on("skinChanged", ()=>{
            this.avatars.forEach(t=>{
                t.disconnected && this.removeAvatar(t.userId, !0)
            }
            )
        }
        )
    }
    setupBroadcast() {
        return new Broadcast(this._room,async e=>{
            const {broadcastType: t, info: r} = e;
            if (t !== CoreBroadcastType.PlayAnimation)
                return;
            const {userId: n, animation: o, extra: a, loop: s=!1} = r
              , l = this.avatars.get(n);
            l && !l.isSelf && (l.emit("animationStart", {
                animationName: o,
                extra: decodeURIComponent(a)
            }),
            await (l == null ? void 0 : l._playAnimation(o, s)),
            l.emit("animationEnd", {
                animationName: o,
                extra: decodeURIComponent(a)
            }))
        }
        )
    }
    hideAll(e=!0) {
        this.xAvatarManager.hideAll(e)
    }
    showAll(e=!0) {
        this.xAvatarManager.showAll(e)
    }
    async init() {
        this.xAvatarManager = this._room.sceneManager.avatarComponent;
        try {
            const e = await this._room.modelManager.getApplicationConfig()
              , {avatars: t} = e;
            if (t) {
                await avatarLoader.parse(this._room.sceneManager, t);
                return
            }
            return Promise.reject("cannot find avatar config list")
        } catch (e) {
            return logger.error(e),
            Promise.reject("avatar mananger init error!")
        }
    }
    async handleAvatar(e) {
        var r;
        if (this._room.viewMode === "simple" || !this._room.joined || !e.newUserStates)
            return;
        let t = e.newUserStates;
        if (((r = this._room._userAvatar) == null ? void 0 : r.isMoving) && this._room._userAvatar.motionType === MotionType.Run) {
            const n = t.filter(a=>a.userId === this._room.userId)
              , o = t.filter(a=>a.userId !== this._room.userId).slice(0, 2);
            t = n.concat(o)
        }
        if (e.getStateType === GetStateTypes.Event) {
            this.syncAvatarsLength = (t || []).length;
            const n = this._room.avatars.filter(s=>s.group == AvatarGroup.User);
            n.filter(s=>!(t != null && t.find(l=>l.userId == s.userId))).forEach(s=>{
                this.removeAvatar(s.userId)
            }
            );
            const a = t.filter(s=>!n.find(l=>l.userId == s.userId));
            this._handleAvatar(a)
        }
        this._handleAvatar(t)
    }
    async _handleAvatar(e) {
        e == null || e.forEach(t=>{
            var n, o, a, s, l, u, c, h, f;
            const r = this._room.userId === t.userId;
            if (((n = t.event) == null ? void 0 : n.type) === SyncEventType.ET_RemoveVisitor) {
                const d = (a = (o = t.event) == null ? void 0 : o.removeVisitorEvent) == null ? void 0 : a.removeVisitorEvent
                  , _ = JSON.parse(safeDecodeURIComponent(((l = (s = t.event) == null ? void 0 : s.removeVisitorEvent) == null ? void 0 : l.extraInfo) || ""))
                  , {code: g, msg: m} = _;
                d === RemoveVisitorType.RVT_ChangeToObserver ? this._room.audienceViewModeHook() : d === RemoveVisitorType.RVT_MoveOutOfTheRoom && this._room.leave(),
                this._room.emit("visitorStatusChanged", {
                    code: g,
                    msg: m
                })
            }
            if (t.event && [SyncEventType.Appear, SyncEventType.Reset].includes(t.event.type) || !t.event) {
                let d = this.avatars.get(t.userId);
                t.playerState.avatarId && (d == null ? void 0 : d.avatarId) !== t.playerState.avatarId && (d = void 0,this.removeAvatar(t.userId));
                if (d) {
                    if (d.disconnected && d.setConnectionStatus(!1),
                    (u = t.event) != null && u.id && this._room.actionsHandler.confirmEvent(t.event.id),
                    t.playerState.nickName && (d == null || d._setNickname(t.playerState.nickName)),
                    t.playerState.avatarComponents && !d.isSelf && d.xAvatar) {
                        const _ = safeParseComponents(t.playerState.avatarComponents);
                        d._changeComponents({
                            avatarComponents: _,
                            mode: ChangeComponentsMode.Preview
                        })
                    }
                } else {
                    const {position: _, angle: g} = t.playerState.player
                      , m = t.playerState.avatarId
                      , v = t.playerState.prioritySync
                      , y = safelyJsonParse(t.playerState.extra);
                    if (!m)
                        return;
                    const b = safeParseComponents(t.playerState.avatarComponents)
                      , T = safeDecodeURIComponent(t.playerState.nickName)
                      , C = this.calculatePriority(t.userId, y);
                    this.addAvatar({
                        userId: t.userId,
                        isHost: t.playerState.isHost,
                        nickname: T,
                        avatarPosition: _,
                        avatarRotation: g,
                        avatarScale: t.playerState.avatarSize,
                        avatarId: m,
                        avatarComponents: t.playerState.person === Person.First ? [] : b,
                        priority: C,
                        group: AvatarGroup.User,
                        prioritySync: v,
                        extraInfo: y
                    }).then(()=>{
                        var A;
                        (A = t.event) != null && A.id && this._room.actionsHandler.confirmEvent(t.event.id),
                        this.updateAvatarPositionAndRotation(t),
                        r && (this.xAvatarManager.setMainAvatar(t.userId),
                        this._room.emit("userAvatarLoaded"),
                        logger.info("userAvatarLoaded"))
                    }
                    ).catch(A=>{
                        r && (this.xAvatarManager.setMainAvatar(t.userId),
                        this._room.emit("userAvatarFailed", {
                            error: A
                        }),
                        logger.error("userAvatarFailed", A))
                    }
                    )
                }
            }
            if (t.event && SyncEventType.Disappear === t.event.type && ((c = t == null ? void 0 : t.event) != null && c.id && this._room.actionsHandler.confirmEvent(t.event.id),
            this.removeAvatar(t.userId)),
            t.event && [SyncEventType.Move, SyncEventType.ChangeRenderInfo].includes(t.event.type) || !t.event) {
                (h = t == null ? void 0 : t.event) != null && h.id && this._room.actionsHandler.confirmEvent(t.event.id);
                const d = this.avatars.get(t.userId);
                d && d.withModel && !d.isLoading && this.updateAvatarPositionAndRotation(t)
            }
            if (!r && ((f = t.event) == null ? void 0 : f.type) === SyncEventType.Rotate) {
                const d = this.avatars.get(t.userId);
                d.statusSyncQueue.append({
                    type: QueueType.Rotate,
                    action: ()=>d.statusSync(t)
                })
            }
        }
        )
    }
    calculatePriority(e, t) {
        var n;
        return e === this._room.userId ? EAvatarRelationRank.Self : (n = this._room.options.firends) != null && n.includes(e) ? EAvatarRelationRank.Friend : EAvatarRelationRank.Stranger
    }
    updateAvatarPositionAndRotation(e) {
        var t, r;
        if ((t = e == null ? void 0 : e.playerState) != null && t.player) {
            let {position: n, angle: o} = e.playerState.player;
            const a = this.avatars.get(e.userId);
            if (!a)
                return;
            if (n = positionPrecisionProtect(n),
            o = rotationPrecisionProtect(o),
            a.isSelf && !this._room.networkController.rtcp.workers.inPanoMode && (a.setPosition(n),
            a.setRotation(o)),
            e.event && (((r = e.event) == null ? void 0 : r.points.length) || 0) > 1 && !a.isSelf && a.statusSyncQueue.append({
                type: QueueType.Move,
                action: ()=>a.statusSync(e)
            }),
            e.renderInfo && a.isSelf) {
                const {isMoving: s, isRotating: l} = e.renderInfo;
                this._updateAvatarMovingStatus({
                    id: e.userId,
                    isMoving: !!s,
                    isRotating: !!l
                })
            }
        }
    }
    async addAvatar(e) {
        const {userId: t, isHost: r, avatarPosition: n, avatarId: o, avatarRotation: a, nickname: s, avatarComponents: l=[], priority: u, group: c=AvatarGroup.Npc, avatarScale: h=DEFAULT_AVATAR_SCALE, extraInfo: f, prioritySync: d} = e
          , _ = t === this._room.userId;
        let g = this.avatars.get(t);
        if (g)
        {
            return Promise.resolve(g);
        }

        g = new XverseAvatarManager.subAvatar({
            userId: t,
            isHost: r,
            isSelf: _,
            room: this._room,
            avatarComponents: l,
            avatarId: o,
            nickname: s,
            group: c
        });
        this.avatars.set(t, g);
        if (!g.withModel)
            return g.isLoading = !1,
            g.avatarLoadedHook(),
            this._room.emit("avatarChanged", {
                avatars: this._room.avatars
            }),
            g;
        const v = (await this._room.modelManager.getAvatarModelList()).find(b=>b.id === o)
          , y = Date.now();
        if (!v)
            return this._room.emit("avatarChanged", {
                avatars: this._room.avatars
            }),
            this.avatars.delete(t),
            Promise.reject(`no such avatar model with id: ${o}`);
        try {
            let b = await avatarComponentsModify(v, l);
            b = b.filter(A=>A.type != "pendant");
            const T = await avatarComponentsParser(v, b)
              , C = await this.xAvatarManager.loadAvatar({
                id: t,
                avatarType: o,
                priority: u,
                avatarManager: this.xAvatarManager,
                assets: T,
                status: {
                    avatarPosition: n,
                    avatarRotation: a,
                    avatarScale: h
                }
            })._timeout(8e3, new TimeoutError("loadAvatar timeout(8s)"));
            return C.setPickBoxScale(t === this._room.userId ? 0 : 1),
            g.xAvatar = C,
            g.setScale(h),
            g.extraInfo = f,
            g.priority = u,
            g.isLoading = !1,
            g.prioritySync = !!d,
            g._playAnimation("Idle", !0, !0),
            g.avatarLoadedHook(),
            this._room.emit("avatarChanged", {
                avatars: this._room.avatars
            }),
            s && g._setNickname(s),
            t === this._room.userId && (logger.infoAndReportMeasurement({
                metric: "avatarLoadDuration",
                startTime: y,
                group: "costs"
            }),
            logger.infoAndReportMeasurement({
                metric: "avatarLoadAt",
                startTime: this._room._startTime,
                group: "costs"
            })),
            g
        } catch (b) {
            return g.isLoading = !1,
            this._room.emit("avatarChanged", {
                avatars: this._room.avatars
            }),
            logger.error(b),
            Promise.reject(b)
        }
    }
    removeAvatar(e, t=!1) {
        const r = this.avatars.get(e);
        if (!!r) {
            if (r.removeWhenDisconnected || t) {
                r.xAvatar && this.xAvatarManager.deleteAvatar(r.xAvatar),
                this.avatars.delete(e),
                this._room.emit("avatarChanged", {
                    avatars: this._room.avatars
                });
                return
            }
            r.setConnectionStatus(!0)
        }
    }
    clearOtherUsers() {
        this.avatars.forEach(e=>{
            !e.isSelf && e.group === AvatarGroup.User && this.removeAvatar(e.userId)
        }
        )
    }
    async _updateAvatarMovingStatus(e) {
        const {id: t, isMoving: r, isRotating: n} = e
          , o = this.avatars.get(t);
        if (!!o) {
            if (o.isRotating !== n) {
                o.isRotating = n;
                let a = "Idle";
                n && (a = "Walking",
                o.motionType === MotionType.Run && (a = "Running")),
                o._playAnimation(a, !0, !0),
                logger.infoAndReportMeasurement({
                    startTime: Date.now(),
                    value: 0,
                    metric: n ? "userAvatarStartRotating" : "userAvatarStopRotating",
                    extra: {
                        motionType: o.motionType,
                        moveToExtra: this._room.moveToExtra
                    }
                })
            }
            if (o.isMoving !== r) {
                o.isMoving = r;
                let a = "Idle";
                r && (a = "Walking",
                o.motionType === MotionType.Run && (a = "Running")),
                r ? (o.avatarStartMovingHook(),
                o.emit("startMoving", {
                    target: o,
                    extra: this._room.moveToExtra
                })) : (o.avatarStopMovingHook(),
                o.emit("stopMoving", {
                    target: o,
                    extra: this._room.moveToExtra
                })),
                o._playAnimation(a, !0, !0),
                logger.infoAndReportMeasurement({
                    startTime: Date.now(),
                    value: 0,
                    metric: r ? "userAvatarStartMoving" : "userAvatarStopMoving",
                    extra: {
                        motionType: o.motionType,
                        moveToExtra: this._room.moveToExtra
                    }
                })
            }
        }
    }
    _usersStatistics() {
        this.on("userAvatarLoaded", ()=>{
            window.setInterval(()=>{
                const e = this._room.avatars.filter(r=>r.group === AvatarGroup.User).length || 0
                  , t = this._room.avatars.filter(r=>r.group === AvatarGroup.User && r.isRender).length || 0;
                this._room.stats.assign({
                    userNum: e,
                    syncUserNum: this.syncAvatarsLength,
                    renderedUserNum: t
                })
            }
            , 3e3)
        }
        )
    }
};

E(XverseAvatarManager, "subAvatar", XverseAvatar);