import XverseAvatarManager from "./XverseAvatarManager.js"
import Codes from "./enum/Codes.js"
import PathManager from "./PathManager.js"
import Camera from "./Camera.js"
import Stats from "./Stats.js"
import ActionsHandler from "./ActionsHandler.js"
import Signal from "./Signal.js"
import ModelManager from "./ModelManager.js"
import {reporter} from "./Reporter.js"
import util from "./util.js"
import XverseEffectManager from "./XverseEffectManager.js"
import TimeoutError from "./error/TimeoutError.js"
import ParamError from "./error/ParamError.js"
import MotionType from "./enum/MotionType.js"
import NetworkController from "./NetworkController.js"
import InitNetworkTimeoutError from "./error/InitNetworkTimeoutError.js"
import InitConfigTimeoutError from "./error/InitConfigTimeoutError.js"
import InitDecoderTimeoutError from "./error/InitDecoderTimeoutError.js"
import InitEngineError from "./error/InitEngineError.js"
import {eventsManager} from "./EventsManager.js"
import EngineProxy from "./EngineProxy.js"
import EventsController from "./EventsController.js"
import EImageQuality from "./enum/EImageQuality.js"
import Panorama from "./Panorama.js"
import Debug from "./Debug.js"
import Logger from "./Logger.js"
import NewUserStateType from "./enum/NewUserStateType.js"

const logger = new Logger('xverse-room')
export default class Xverse_Room extends EventEmitter {
    constructor(e) {
        super();
        E(this, "disableAutoTurn", !1);
        E(this, "options");
        E(this, "_currentNetworkOptions");
        E(this, "lastSkinId");
        E(this, "debug");
        E(this, "isFirstDataUsed", !1);
        E(this, "userId", null);
        E(this, "pathManager", new PathManager);
        E(this, "networkController");
        E(this, "_startTime", Date.now());
        E(this, "canvas");
        E(this, "modelManager");
        E(this, "eventsController");
        E(this, "panorama");
        E(this, "engineProxy");
        E(this, "_id");
        E(this, "skinList", []);
        E(this, "isHost", !1);
        E(this, "avatarManager", new XverseAvatarManager(this));
        E(this, "effectManager", new XverseEffectManager(this));
        E(this, "sceneManager");
        E(this, "scene");
        E(this, "breathPointManager");
        E(this, "_currentState");
        E(this, "joined", !1);
        E(this, "disableRotate", !1);
        E(this, "isPano", !1);
        E(this, "movingByClick", !0);
        E(this, "camera", new Camera(this));
        E(this, "stats", new Stats(this));
        E(this, "isUpdatedRawYUVData", !1);
        E(this, "actionsHandler", new ActionsHandler(this));
        E(this, "_currentClickingState", null);
        E(this, "signal", new Signal(this));
        E(this, "firstFrameTimestamp");
        E(this, "receiveRtcData", async()=>{
            logger.info("Invoke receiveRtcData");
            let i = !1,
            o = !1,
            s = !1,
            c = !1;
            return this.viewMode === "serverless" ? (logger.warn("set view mode to serverless"),
            this.setViewMode("observer").then(()=>this, ()=>this)) : new Promise((d, _) => {
                const b = this.networkController.rtcp.workers;
                b.registerFunction("signal", k => {
                    this.signal.handleSignal(k, _)
                }), 
                b.registerFunction("stream", k => {
                    var j;
                    if (this.emit("streamTimestamp", {
                            timestamp: Date.now()
                        }), o || (o = !0, logger.info("Invoke stream event")), k.stream) {
                        s || (s = !0, logger.info("Invoke updateRawYUVData")), this.isUpdatedRawYUVData = !1;
                        const $ = (j = this._currentState.skin) == null ? void 0 : j.fov;
                        this.sceneManager.materialComponent.updateRawYUVData(k.stream, k.width, k.height, $), this.isUpdatedRawYUVData = !0
                    }
                    if(!i){
                        logger.info("Invoke isAfterRenderRegistered")
                        i = !0, this.scene.registerAfterRender(() => {
                            this.engineProxy.frameRenderNumber >= 2 && (c || (c = !0, logger.info("Invoke registerAfterRender")), this.isFirstDataUsed || (logger.info("Invoke isStreamAvailable"), this.isFirstDataUsed = !0, this.firstFrameTimestamp = Date.now(), d(this), this.afterJoinRoom()))
                        })
                    }
                }), 
                this.panorama.bindListener(() => {
                    d(this), this.afterJoinRoom()
                }), 
                b.registerFunction("reconnectedFrame", () => {}), 
                logger.info("Invoke decoderWorker.postMessage"), 
                b.decoderWorker.postMessage({
                    t: 5
                })
            }
            )
        }
        );
        E(this, "moveToExtra", "");
        this.options = e,
        this.options.wsServerUrl || (this.options.wsServerUrl = SERVER_URLS.DEV),
        this.modelManager = ModelManager.getInstance(e.appId, e.releaseId),
        this.updateReporter();
        const n = e
          , {canvas: t} = n
          , r = Oe(n, ["canvas"]);
        logger.infoAndReportMeasurement({
            metric: "startJoinRoomAt",
            startTime: Date.now(),
            group: "joinRoom",
            extra: r,
            value: 0
        })
    }

    get currentNetworkOptions() {
        return this._currentNetworkOptions
    }
    get viewMode() {
        var e;
        return ((e = this._currentState) == null ? void 0 : e.viewMode) || "full"
    }
    get id() {
        return this._id
    }
    get skinId() {
        return this._currentState.skinId
    }
    get skin() {
        return this._currentState.skin
    }
    get sessionId() {
        return this.currentNetworkOptions.sessionId
    }
    get pictureQualityLevel() {
        return this.currentState.pictureQualityLevel
    }
    get avatars() {
        return Array.from(this.avatarManager.avatars.values())
    }
    get currentState() {
        var e;
        return le(oe({}, this._currentState), {
            state: (e = this.networkController) == null ? void 0 : e._state
        })
    }
    get _userAvatar() {
        return this.avatars.find(e=>e.userId === this.userId)
    }
    get tvs() {
        return this.engineProxy._tvs
    }
    get tv() {
        return this.tvs[0]
    }
    get currentClickingState() {
        return this._currentClickingState
    }
    afterJoinRoomHook() {}
    beforeJoinRoomResolveHook() {}
    afterReconnectedHook() {}
    handleSignalHook(e) {}
    skinChangedHook() {}
    async beforeStartGameHook(e) {}
    loadAssetsHook() {}
    afterUserAvatarLoadedHook() {}
    audienceViewModeHook() {}
    setViewModeToObserver() {}
    handleVehicleHook(e) {}
    updateReporter() {
        const {avatarId: avatarId, skinId: skinId, userId: userId, roomId: roomId, role: role, appId: appId, wsServerUrl: wsServerUrl} = this.options;
        reporter.updateHeader({
            userId: userId
        }),
        reporter.updateBody({
            roomId: roomId,
            role: role,
            skinId: skinId,
            avatarId: avatarId,
            appId: appId,
            wsServerUrl: wsServerUrl
        })
    }
    async initRoom() {
        const {timeout: e=DEFAULT_JOINROOM_TIMEOUT} = this.options;
        if(util.isSupported()){
            return this._initRoom();
        }
        else{
            return Promise.reject(new UnsupportedError)
        }
    }
    async _initRoom() {
        const e = this.validateOptions(this.options);
        if(e)
        {
            return logger.error("initRoom param error", e),
            Promise.reject(e);
        }
        const {canvas: canvas, avatarId: avatarId, skinId: skinId, userId: userId, wsServerUrl: wsServerUrl, role: role, token: token, pageSession: pageSession, rotationRenderType: rotationRenderType, isAllSync: isAllSync=!1, appId: f, camera: d, player: _, avatarComponents: g, nickname: nickname, avatarScale: v, firends: y=[], syncByEvent: b=!1, areaName: T, attitude: C=MotionType.Walk, pathName: A, viewMode: S="full", person: P, roomId: roomId, roomTypeId: M, hasAvatar: x=!1, syncToOthers: I=!1, prioritySync: w=!1, removeWhenDisconnected: O=!0, extra: D} = this.options;
        this.setCurrentNetworkOptions({
            avatarId: avatarId,
            skinId: skinId,
            roomId: roomId,
            userId: userId,
            wsServerUrl: wsServerUrl,
            role: role,
            token: token,
            pageSession: pageSession,
            rotationRenderType: rotationRenderType,
            isAllSync: isAllSync,
            appId: f,
            camera: d,
            player: _,
            avatarComponents: g,
            nickname: nickname,
            avatarScale: v,
            firends: y,
            syncByEvent: b,
            areaName: T,
            attitude: C,
            pathName: A,
            person: P,
            roomTypeId: M,
            hasAvatar: x,
            syncToOthers: I,
            prioritySync: w,
            extra: D,
            removeWhenDisconnected: O
        });
        this.userId = userId;
        this.canvas = canvas;
        T && (this.pathManager.currentArea = T);
        this.networkController = new NetworkController(this);
        this.setCurrentState({
            areaName: T,
            pathName: A,
            attitude: C,
            speed: 0,
            viewMode: S,
            state: this.networkController._state,
            skinId: skinId
        });
        try {
            await Promise.all([this.initNetwork(), this.initConfig(), this.initWasm()]),
            logger.info("network config wasm all ready, start to create game");
            const F = await this.requestCreateRoom({
                skinId: skinId
            })
              , V = F.routeList.find(L=>L.areaName === T)
              , N = ((V == null ? void 0 : V.step) || 7.5) * 30;
            this.updateCurrentState({
                skin: F,
                skinId: F.id,
                versionId: F.versionId,
                speed: N
            }),
            await this.initEngine(F)
        } catch (F) {
            return Promise.reject(F)
        }
        this.beforeJoinRoomResolve();
        return this.receiveRtcData()
    }
    beforeJoinRoomResolve() {
        this.setupStats(),
        this.eventsController = new EventsController(this),
        this.eventsController.bindEvents(),
        this.panorama = new Panorama(this),
        this.beforeJoinRoomResolveHook()
    }
    afterJoinRoom() {
        this.joined = !0,
        this.viewMode === "observer" && this.setViewModeToObserver(),
        logger.infoAndReportMeasurement({
            tag: this.viewMode,
            value: this.firstFrameTimestamp || Date.now() - this._startTime,
            startTime: Date.now(),
            metric: "joinRoom",
            reportOptions: {
                immediate: !0
            }
        }),
        this.camera.initialFov = this.sceneManager.cameraComponent.getCameraFov(),
        this.stats.on("stats", ({stats: e})=>{
            reporter.report("stats", oe({}, e))
        }
        ),
        this.debug = new Debug(this),
        this.afterJoinRoomHook();
        setInterval(() => {
            this.actionsHandler.getNewUserState(NewUserStateType.NUST_Undefined).then(i => {
                this.avatarManager.handleAvatar(i)
            }).catch(() => {})
        }, 2e3)
    }
    afterReconnected() {
        this.avatarManager.clearOtherUsers(),
        this.afterReconnectedHook()
    }
    leave() {
        var e, t;
        return logger.info("Invoke room.leave"),
        (e = this.eventsController) == null || e.clearEvents(),
        (t = this.networkController) == null || t.quit(),
        this
    }
    validateOptions(e) {
        const {canvas: t, avatarId: avatarId, skinId: skinId, userId: userId, role: role, roomId: roomId, token: token, appId: appId, avatarComponents: avatarComponents} = e || {}
        const h = [];
        t instanceof HTMLCanvasElement || h.push(new ParamError("`canvas` must be instanceof of HTMLCanvasElement"));
        (!userId || typeof userId != "string") && h.push(new ParamError("`userId` must be string"));
        (!token || typeof token != "string") && h.push(new ParamError("`token` must be string"));
        (!appId || typeof appId != "string") && h.push(new ParamError("`appId` must be string"));
        role == "audience" || (!avatarId || !skinId) && h.push(new ParamError("`avatarId` and `skinId` is required when create room"));
        return h[0]
    }
    async initNetwork() {
        if (this.viewMode === "serverless")
            return Promise.resolve();
        const e = Date.now();
        try {
            await this.networkController.connect()._timeout(8e3, new InitNetworkTimeoutError),
            logger.infoAndReportMeasurement({
                metric: "networkInitAt",
                startTime: this._startTime,
                group: "joinRoom"
            }),
            logger.infoAndReportMeasurement({
                metric: "networkInitCost",
                startTime: e,
                group: "joinRoom"
            })
        } catch (t) {
            throw logger.infoAndReportMeasurement({
                metric: "networkInitAt",
                startTime: e,
                group: "joinRoom",
                error: t
            }),
            t
        }
    }
    async initConfig() {
        const e = Date.now();
        try {
            await this.modelManager.getApplicationConfig()._timeout(8e3, new InitConfigTimeoutError),
            logger.infoAndReportMeasurement({
                metric: "configInitAt",
                startTime: this._startTime,
                group: "joinRoom"
            }),
            logger.infoAndReportMeasurement({
                metric: "configInitCost",
                startTime: e,
                group: "joinRoom"
            })
        } catch (t) {
            throw logger.infoAndReportMeasurement({
                metric: "configInitAt",
                startTime: e,
                group: "joinRoom",
                error: t
            }),
            t
        }
    }
    async initEngine(e) {
        const t = Date.now();
        try {
            this.engineProxy = new EngineProxy(this),
            await this.engineProxy.initEngine(e),
            logger.infoAndReportMeasurement({
                metric: "webglInitAt",
                startTime: this._startTime,
                group: "joinRoom"
            }),
            logger.infoAndReportMeasurement({
                metric: "webglInitCost",
                startTime: t,
                group: "joinRoom"
            });
            return
        } catch (r) {
            let n = r;
            return r.code !== Codes.InitEngineTimeout && (n = new InitEngineError),
            logger.error(r),
            logger.infoAndReportMeasurement({
                metric: "webglInitAt",
                startTime: t,
                group: "joinRoom",
                error: n
            }),
            Promise.reject(n)
        }
    }
    async initWasm() {
        if (this.viewMode === "serverless")
            return Promise.resolve();
        const i = Date.now();
        try {
            await this.networkController.rtcp.workers.init({
                width: 1920,
                height: 1080,
                userID: this.userId,
                pageSession: this.options.pageSession,
                serverSession: ""
            })._timeout(8e3, new InitDecoderTimeoutError),
            this.networkController.rtcp.workers.registerFunction("error", o=>{
                logger.error("decode error", o);
                const {code: s, message: c} = o;
                this.emit("error", {
                    code: s,
                    msg: c
                })
            }
            ),
            logger.infoAndReportMeasurement({
                metric: "wasmInitAt",
                group: "joinRoom",
                startTime: this._startTime
            }),
            logger.infoAndReportMeasurement({
                metric: "wasmInitCost",
                group: "joinRoom",
                startTime: i
            }),
            eventsManager.on("traceId", o=>{
                this.networkController.rtcp.workers.onTraceId(o)
            }
            )
        } catch (o) {
            throw logger.infoAndReportMeasurement({
                metric: "wasmInitAt",
                group: "joinRoom",
                startTime: i,
                error: o
            }),
            o
        }
    }
    async requestCreateRoom({skinId: e}) {
        let skin;
        if (e) {
            skin = await this.getSkin(e);
            this.updateCurrentState({
                skin: skin
            });
            const r = await this.modelManager.findRoute(e, this.options.pathName);
            this.updateCurrentNetworkOptions({
                areaName: r.areaName,
                attitude: r.attitude,
                versionId: skin.versionId
            });
            const {camera: n, player: player} = util.getRandomItem(r.birthPointList) || this.options;
            this.options.camera || this.updateCurrentNetworkOptions({
                camera: n
            }),
            this.options.player || this.updateCurrentNetworkOptions({
                player: player
            })
        }
        if (this.viewMode === "serverless")
            return skin;
        try {
            await this.beforeStartGameHook(this.options);
            const {room_id: room_id, data: n, session_id: session_id} = await this.networkController.startGame();
            this._id = room_id;
            const a = JSON.parse(n);
            this.isHost = a.IsHost,
            e = a.SkinID || e;
            const skin = await this.getSkin(e);
            this.updateCurrentNetworkOptions({
                roomId: room_id,
                sessionId: session_id
            });
            reporter.updateBody({
                roomId: room_id,
                skinId: e,
                serverSession: session_id
            });
            return skin
        } catch (r) {
            logger.error("requestCreateRoom error:", r);
            return Promise.reject(r)
        }
    }
    pause() {
        return this.engineProxy.pause()
    }
    resume() {
        return this.engineProxy.resume()
    }
    reconnect() {
        this.networkController.reconnect()
    }
    async setViewMode(e) {}
    handleRepetLogin() {
        logger.warn("receive " + Codes.RepeatLogin + " for repeat login"),
        this.emit("repeatLogin"),
        reporter.disable(),
        this.networkController.quit()
    }
    setPictureQualityLevel(e) {
        const t = {
            high: EImageQuality.high,
            low: EImageQuality.low,
            average: EImageQuality.mid
        };
        return this.updateCurrentState({
            pictureQualityLevel: e
        }),
        this.sceneManager.setImageQuality(t[e])
    }
    async getSkin(skinId) {
        let t = (this.skinList = await this.modelManager.getSkinsList()).find(skin=>skin.id === skinId || skin.id === skinId)
        if (t)
            return t;
        {
            const n = `skin is invalid: skinId: ${skinId}`;
            return Promise.reject(new ParamError(n))
        }
    }
    setupStats() {
        this.stats.assign({
            roomId: this.id,
            userId: this.userId
        }),
        setInterval(this.engineProxy.updateStats, 1e3)
    }
    proxyEvents(e, t) {
        this.emit(e, t)
    }
    setCurrentNetworkOptions(e) {
        this._currentNetworkOptions = e
    }
    updateCurrentNetworkOptions(e) {
        Object.assign(this._currentNetworkOptions, e),
        Object.assign(this.options, e)
    }
    setCurrentState(e) {
        this._currentState = e
    }
    updateCurrentState(e) {
        e.skinId && (this.lastSkinId = this.currentState.skinId,
        this.updateCurrentNetworkOptions({
            skinId: e.skinId
        })),
        e.versionId && this.updateCurrentNetworkOptions({
            versionId: e.versionId
        }),
        Object.assign(this._currentState, e)
    }

    afterSetUrlHook() {}
    afterTvStopedHook() {}
    afterTvPlayedHook() {}
    pageShowHandler() {
        this.engineProxy.setEnv(this.skin), this.allowRender = !0
    }
    pageHideHandler() {
        this.allowRender = !1
    }
}