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((resolve, reject) => {
            const workers = this.networkController.rtcp.workers;

            workers.registerFunction("signal", data => {
                // 更新坐标数据
                this.signal.handleSignal(data, reject)
            }), 

            workers.registerFunction("stream", data => {
                // 更新视频贴图数据
                this.emit("streamTimestamp", {
                    timestamp: Date.now()
                })
                o || (o = !0, logger.info("Invoke stream event"))
                if (data.stream) {
                    s || (s = !0, logger.info("Invoke updateRawYUVData"))
                    this.isUpdatedRawYUVData = !1;
                    const $ = this._currentState.skin == null ? void 0 : this._currentState.skin.fov;
                    this.sceneManager.materialComponent.updateRawYUVData(data.stream, data.width, data.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(), 
                            resolve(this), 
                            this.afterJoinRoom()
                        ))
                    })
                }
            }), 
            this.panorama.bindListener(() => {
                resolve(this)
                this.afterJoinRoom()
            }), 
            workers.registerFunction("reconnectedFrame", () => {}), 
            logger.info("Invoke decoderWorker.postMessage"), 
            workers.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, skinId, userId, roomId, role, appId, wsServerUrl} = this.options;
      reporter.updateHeader({ userId }),
      reporter.updateBody({ roomId, role, skinId, avatarId, appId, wsServerUrl })
  }
  
  async initRoom() {
    const { timeout: e = DEFAULT_JOINROOM_TIMEOUT } = this.options;
    if (util.isSupported()) {
      return this._initRoom()._timeout(e, new TimeoutError("initRoom timeout"));
    } 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, avatarId, skinId, userId, wsServerUrl, role, token, pageSession, rotationRenderType, isAllSync=!1, appId, camera, player, 
          avatarComponents, nickname, avatarScale, firends=[], syncByEvent=!1, areaName, attitude=MotionType.Walk, pathName, viewMode="full", 
          person, roomId, roomTypeId, hasAvatar=!1, syncToOthers=!1, prioritySync=!1, extra, removeWhenDisconnected=!0
      } = this.options;
      this.setCurrentNetworkOptions({
          avatarId, skinId, roomId, userId, wsServerUrl, role, token, pageSession, rotationRenderType, isAllSync, appId, camera, player,
          avatarComponents, nickname, avatarScale, firends, syncByEvent, areaName, attitude, pathName,
          person, roomTypeId, hasAvatar, syncToOthers, prioritySync, extra, removeWhenDisconnected
      });
        
      this.userId = userId;
      this.canvas = canvas;
      areaName && (this.pathManager.currentArea = areaName);
      this.networkController = new NetworkController(this);
      this.setCurrentState({ areaName, pathName, attitude, speed: 0, viewMode, state: this.networkController._state, skinId });
      try {
          await Promise.all([this.initNetwork(), this.initConfig(), this.initWasm()]),
          logger.info("network config wasm all ready, start to create game");
          const skin = await this.requestCreateRoom({skinId})
            , skinRoute = skin.routeList.find(route => route.areaName === areaName)
            , speed = ((skinRoute == null ? void 0 : skinRoute.step) || 7.5) * 30;
          this.updateCurrentState({ skin, skinId: skin.id, versionId: skin.versionId, speed }),
          await this.initEngine(skin)
      } catch (e) {
          return Promise.reject(e)
      }
      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() {
      return logger.info("Invoke room.leave"),
      this.eventsController == null || this.eventsController.clearEvents(),
      this.networkController == null || this.networkController.quit(),
      this
  }
  
  validateOptions(e) {
      const {canvas, avatarId, skinId, userId, role, roomId, token, appId, avatarComponents} = e || {}
      const h = [];
      canvas 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;
  }
}
