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"
import QueueType from "./enum/QueueType.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 configList = await this._room.modelManager.getApplicationConfig()
              , {avatars} = configList;
            if (avatars) {
                await avatarLoader.parse(this._room.sceneManager, avatars);
                return
            }
            return Promise.reject("cannot find avatar config list")
        } catch (e) {
            return logger.error(e),
            Promise.reject("avatar mananger init error!")
        }
    }

    async handleAvatar(signal) {
        if (this._room.viewMode === "simple" || !this._room.joined || !signal.newUserStates)
            return;

        let newUserStates = signal.newUserStates;
        let userAvatar = this._room._userAvatar;
        if ((userAvatar == null ? void 0 : userAvatar.isMoving) && userAvatar.motionType === MotionType.Run) 
        {
            const mainStates = newUserStates.filter(state => state.userId === this._room.userId)
              , o = newUserStates.filter(state => state.userId !== this._room.userId).slice(0, 2);
              newUserStates = mainStates.concat(o)
        }

        if (signal.getStateType === GetStateTypes.Event) 
        {
            this.syncAvatarsLength = (newUserStates || []).length;
            const n = this._room.avatars.filter(avatar => avatar.group == AvatarGroup.User);
            n.filter(avatar => 
                !(newUserStates != null && newUserStates.find(state => state.userId == avatar.userId))
            ).forEach(avatar=>{
                this.removeAvatar(avatar.userId)
            });
            const a = newUserStates.filter(state => !n.find(avatar => avatar.userId == state.userId));
            this._handleAvatar(a)
        }
        this._handleAvatar(newUserStates)
    }

    async _handleAvatar(newUserStates) {

        newUserStates && newUserStates.forEach(state=>{

            const isMainAvatar = this._room.userId === state.userId;

            if (state.event && state.event.type === SyncEventType.ET_RemoveVisitor) 
            {
                const removeVisitorEvent = state.event.removeVisitorEvent
                const event =  removeVisitorEvent && removeVisitorEvent.removeVisitorEvent
                  , extraInfo = JSON.parse(safeDecodeURIComponent((removeVisitorEvent && removeVisitorEvent.extraInfo) || ""))
                  , { code, msg } = extraInfo;

                  event === RemoveVisitorType.RVT_ChangeToObserver 
                    ? this._room.audienceViewModeHook() 
                    : event === RemoveVisitorType.RVT_MoveOutOfTheRoom && this._room.leave()      // 这里会触发Actions.Exit

                this._room.emit("visitorStatusChanged", { code, msg })
            }

            if (state.event && [SyncEventType.Appear, SyncEventType.Reset].includes(state.event.type) || !state.event) {

                let avatar = this.avatars.get(state.userId);
                if(state.playerState.avatarId && avatar && avatar.avatarId !== state.playerState.avatarId) {
                    avatar = void 0
                    this.removeAvatar(state.userId)
                }

                if (avatar) {
                    avatar.disconnected && avatar.setConnectionStatus(!1)
                    state.event && state.event.id && this._room.actionsHandler.confirmEvent(state.event.id)    // 这里会触发Actions.ConfirmEvent
                    state.playerState.nickName && (avatar && avatar._setNickname(state.playerState.nickName))

                    if (state.playerState.avatarComponents && !avatar.isSelf && avatar.xAvatar) {
                        const avatarComponents = safeParseComponents(state.playerState.avatarComponents);
                        // 这里会触发Actions.SetPlayerState
                        avatar._changeComponents({
                            avatarComponents,
                            mode: ChangeComponentsMode.Preview
                        })
                    }
                } else {
                    const {position, angle} = state.playerState.player
                      , avatarId = state.playerState.avatarId
                      , prioritySync = state.playerState.prioritySync
                      , extraInfo = safelyJsonParse(state.playerState.extra);
                    if (!avatarId) return;

                    const avatarComponents = safeParseComponents(state.playerState.avatarComponents)
                      , nickname = safeDecodeURIComponent(state.playerState.nickName)
                      , priority = this.calculatePriority(state.userId, extraInfo);
                      
                    this.addAvatar({
                        userId: state.userId,
                        isHost: state.playerState.isHost,
                        nickname,
                        avatarPosition: position,
                        avatarRotation: angle,
                        avatarScale: state.playerState.avatarSize,
                        avatarId,
                        avatarComponents: state.playerState.person === Person.First ? [] : avatarComponents,
                        priority,
                        group: AvatarGroup.User,
                        prioritySync,
                        extraInfo
                    }).then(()=>{
                        state.event && state.event.id && this._room.actionsHandler.confirmEvent(state.event.id),    // 这里会触发Actions.ConfirmEvent
                        this.updateAvatarPositionAndRotation(state),
                        isMainAvatar && (
                            this.xAvatarManager.setMainAvatar(state.userId),
                            this._room.emit("userAvatarLoaded"),
                            logger.info("userAvatarLoaded")
                        )
                    }).catch(e=>{
                        isMainAvatar && (
                            this.xAvatarManager.setMainAvatar(state.userId),
                            this._room.emit("userAvatarFailed", {error: e}),
                            logger.error("userAvatarFailed", e)
                        )
                    })
                }
            }

            if(state.event && SyncEventType.Disappear === state.event.type) { 
                state.event.id && this._room.actionsHandler.confirmEvent(state.event.id)    // 这里会触发Actions.ConfirmEvent
                this.removeAvatar(state.userId)
            }

            if (state.event && [SyncEventType.Move, SyncEventType.ChangeRenderInfo].includes(state.event.type) || !state.event) {
                state.event && state.event.id && this._room.actionsHandler.confirmEvent(state.event.id);    // 这里会触发Actions.ConfirmEvent
                const avatar = this.avatars.get(state.userId);
                avatar && avatar.withModel && !avatar.isLoading && this.updateAvatarPositionAndRotation(state)
            }

            if (!isMainAvatar && state.event && state.event.type === SyncEventType.Rotate) {
                const avatar = this.avatars.get(state.userId);
                avatar.statusSyncQueue.append({
                    type: QueueType.Rotate,
                    action: () => avatar.statusSync(state)
                })
            }
        })
    }

    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(userState) 
    {
        if (userState.playerState && userState.playerState.player) 
        {
            let {position, angle} = userState.playerState.player;
            const avatar = this.avatars.get(userState.userId);
            if (!avatar) return;

            position = positionPrecisionProtect(position)
            angle = rotationPrecisionProtect(angle)
            avatar.isSelf && !this._room.networkController.rtcp.workers.inPanoMode && (avatar.setPosition(position), avatar.setRotation(angle))
            userState.event && (userState.event.points.length || 0) > 1 && !avatar.isSelf && avatar.statusSyncQueue.append({
                type: QueueType.Move,
                action: () => avatar.statusSync(userState)
            })

            if (userState.renderInfo && avatar.isSelf) {
                const {isMoving, isRotating} = userState.renderInfo;
                this._updateAvatarMovingStatus({
                    id: userState.userId,
                    isMoving: !!isMoving,
                    isRotating: !!isRotating
                })
            }
        }
    }

    async addAvatar({
        userId, isHost, avatarPosition, avatarId, avatarRotation, nickname, avatarComponents=[], priority, group=AvatarGroup.Npc, 
        avatarScale=DEFAULT_AVATAR_SCALE, extraInfo, prioritySync
    }) {
        const isSelf = userId === this._room.userId;
        let avatar = this.avatars.get(userId);
        if (avatar) return Promise.resolve(avatar);

        avatar = new XverseAvatarManager.subAvatar({
            userId,
            isHost,
            isSelf,
            room: this._room,
            avatarComponents,
            avatarId,
            nickname,
            group
        });
        this.avatars.set(userId, avatar);

        if (!avatar.withModel) {
            avatar.isLoading = !1
            avatar.avatarLoadedHook()
            this._room.emit("avatarChanged", { avatars: this._room.avatars })
            return avatar;
        }

        const avatarData = (await this._room.modelManager.getAvatarModelList()).find(data => data.id === avatarId)
          , startTime = Date.now();
        if (!avatarData) {
            this._room.emit("avatarChanged", { avatars: this._room.avatars })
            this.avatars.delete(userId)
            return Promise.reject(`no such avatar model with id: ${avatarId}`);
        }

        try {
            let b = await avatarComponentsModify(avatarData, avatarComponents);
            b = b.filter(A=>A.type != "pendant");
            
            const assets = await avatarComponentsParser(avatarData, b)
              , xAvatar = await this.xAvatarManager.loadAvatar({
                id: userId,
                avatarType: avatarId,
                priority,
                avatarManager: this.xAvatarManager,
                assets,
                status: {
                    avatarPosition,
                    avatarRotation,
                    avatarScale
                }
            })._timeout(8e3, new TimeoutError("loadAvatar timeout(8s)"));
            xAvatar.setPickBoxScale(userId === this._room.userId ? 0 : 1);

            avatar.xAvatar = xAvatar
            avatar.setScale(avatarScale)
            avatar.extraInfo = extraInfo
            avatar.priority = priority
            avatar.isLoading = !1
            avatar.prioritySync = !!prioritySync
            avatar._playAnimation("Idle", !0, !0)
            avatar.avatarLoadedHook()
            this._room.emit("avatarChanged", { avatars: this._room.avatars })
            nickname && avatar._setNickname(nickname)

            userId === this._room.userId && (
                logger.infoAndReportMeasurement({
                    metric: "avatarLoadDuration",
                    startTime,
                    group: "costs"
                }),
                logger.infoAndReportMeasurement({
                    metric: "avatarLoadAt",
                    startTime: this._room._startTime,
                    group: "costs"
                })
            )
            
            return avatar
        } catch (e) {
            return avatar.isLoading = !1,
            this._room.emit("avatarChanged", { avatars: this._room.avatars }),
            logger.error(e),
            Promise.reject(e)
        }
    }

    removeAvatar(userId, t=!1) {
        const avatar = this.avatars.get(userId);
        if (!!avatar) {
            if (avatar.removeWhenDisconnected || t) {
                avatar.xAvatar && this.xAvatarManager.deleteAvatar(avatar.xAvatar),
                this.avatars.delete(userId),
                this._room.emit("avatarChanged", {
                    avatars: this._room.avatars
                });
                return
            }
            avatar.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);