import { publish, subscribe, unsubscribe } from "pubsub-js";
import { throttle } from "lodash";
import { getDefaultTheme } from "@elliemae/pui-theme";
import { updateBAEventParameters, Analytics } from "@elliemae/pui-analytics-so";
import { getStore } from "../../data/store.js";
import { waitMessage } from "../../data/wait-message/actions.js";
import { actions as error } from "../../data/error/index.js";
import { logout } from "../../data/logout/actions.js";
import { getAppConfigValue } from "../app-config/config.js";
import { loadAppConfig } from "../app-config/index.js";
import { browserHistory } from "../history.js";
import { consoleLogger } from "../console-logger.js";
import { setLogger } from "../logger.js";
import {
  HOST_WINDOW_RESIZED,
  HOST_WINDOW_BREAKPOINT_CHANGED,
  SESSION_RENEWAL_INTERVAL
} from "../constants.js";
import {
  getWindow,
  getCurrentBreakpoint,
  getViewportSize as getWindowViewportSize
} from "../window.js";
import { setLogRocket } from "../logrocket.js";
import { renewSession } from "../session.js";
import { getAppBridge } from "./app-bridge.js";
import { getSSFHost, setSSFHost } from "./ssf-host.js";
class CMicroAppHost {
  static instance;
  logger;
  /**
   * LogRocket instance
   */
  #logRocket;
  appId;
  props;
  activeGuests;
  onInit;
  onRenewSessionTimer;
  throttleRenewSessionTimer;
  scriptingObjects;
  #ssfHostRef;
  #appBridge = null;
  // eslint-disable-next-line max-statements
  constructor(params) {
    this.appId = getAppConfigValue("appId");
    this.onInit = params?.onInit;
    this.logger = params?.logger || consoleLogger;
    setLogger(this.logger);
    this.#logRocket = params?.logRocket ?? window.emui?.logRocket;
    setLogRocket(this.#logRocket);
    this.onRenewSessionTimer = params?.onRenewSessionTimer;
    this.props = {
      systemVersion: params?.version ?? "latest",
      history: params?.history ?? browserHistory,
      theme: params?.theme ?? getDefaultTheme()
    };
    this.activeGuests = {};
    if (params?.ssfHostRef) setSSFHost(params.ssfHostRef);
    this.#ssfHostRef = getSSFHost();
    const analyticsObj = params?.analytics ?? new Analytics({ logger: this.logger });
    this.scriptingObjects = {
      analytics: analyticsObj,
      ...params?.scriptingObjects ?? {}
    };
    this.getProps = this.getProps.bind(this);
    this.getLogger = this.getLogger.bind(this);
    this.getGuests = this.getGuests.bind(this);
    this.getGuest = this.getGuest.bind(this);
    this.getObject = this.getObject.bind(this);
    this.renewSessionTimer = this.renewSessionTimer.bind(this);
    this.setSystemVersion = this.setSystemVersion.bind(this);
    this.sendBAEvent = this.sendBAEvent.bind(this);
    this.throttleRenewSessionTimer = throttle(
      async () => {
        await renewSession();
        if (this.onRenewSessionTimer) this.onRenewSessionTimer();
      },
      SESSION_RENEWAL_INTERVAL,
      {
        leading: true
      }
    );
    loadAppConfig().then(() => {
      getAppBridge().then((appBridge) => {
        this.#appBridge = appBridge;
        if (this.onInit) this.onInit(this.props);
      }).catch((ex) => {
        throw ex;
      });
    }).catch(() => {
    });
    updateBAEventParameters({ appId: this.appId });
  }
  static getInstance(params) {
    if (!this.instance) {
      this.instance = new CMicroAppHost(params);
    }
    return this.instance;
  }
  static isInitialized() {
    return !!this.instance;
  }
  getProps() {
    return this.props;
  }
  getLogger() {
    return this.logger;
  }
  getItem(key) {
    return getWindow().sessionStorage.getItem(key);
  }
  setItem(key, value) {
    getWindow().sessionStorage.setItem(key, value);
  }
  getGuests() {
    return this.activeGuests;
  }
  getGuest(id) {
    return this.activeGuests[id] ? this.activeGuests[id] : null;
  }
  publish(message, data) {
    return publish(message, data);
  }
  // eslint-disable-next-line @typescript-eslint/ban-types
  subscribe(message, func) {
    return subscribe(message, func);
  }
  unsubscribe(token) {
    unsubscribe(token);
  }
  navigate(url, state) {
    if (url) this.props.history.push(url, state);
  }
  openWaitMessage() {
    const store = getStore();
    if (store) store.dispatch(waitMessage.open());
  }
  closeWaitMessage() {
    const store = getStore();
    if (store) store.dispatch(waitMessage.close());
  }
  openErrorBanner(message) {
    const store = getStore();
    if (store) store.dispatch(error.set({ description: message }));
  }
  getAuthToken() {
    return getWindow().sessionStorage.getItem("Authorization");
  }
  renewAuthToken() {
    return getWindow().sessionStorage.getItem("Authorization");
  }
  logout() {
    const store = getStore();
    if (store) store.dispatch(logout.confirm());
  }
  // eslint-disable-next-line @typescript-eslint/require-await
  async renewSessionTimer() {
    this.throttleRenewSessionTimer();
  }
  getBreakpoint() {
    return getCurrentBreakpoint();
  }
  getViewportSize() {
    return getWindowViewportSize();
  }
  onResizeEvent(eventHandler) {
    return subscribe(
      HOST_WINDOW_RESIZED,
      (msg, data) => eventHandler(data)
    );
  }
  onBreakpointChangeEvent(eventHandler) {
    return subscribe(
      HOST_WINDOW_BREAKPOINT_CHANGED,
      (msg, data) => eventHandler(data)
    );
  }
  setSystemVersion(version = "latest") {
    window.emui.version = version;
    this.props.systemVersion = version;
  }
  sendBAEvent(data) {
    this.scriptingObjects.analytics.sendBAEvent(data).catch(() => {
    });
  }
  // eslint-disable-next-line @typescript-eslint/require-await
  async getObject(name) {
    return this.scriptingObjects[name];
  }
  /**
   * add scripting object for child microapp to use
   * @param so - The scripting object to be added.
   * @param params - Optional parameters for adding the scripting object.
   */
  addScriptingObject(so, params) {
    if (this.#appBridge) {
      this.#appBridge.addScriptingObject(so, params);
    }
    this.#ssfHostRef.addScriptingObject(so, params);
  }
  /**
   * dispatch event to child microapp
   * @param {DispatchEventParams<EventId, Params>} params - event parameters
   * @returns {Promise<any>} A promise that resolves with the combined results of dispatched events.
   */
  async dispatchEvent(params) {
    if (this.#appBridge) {
      const [appBridgeResult, ssfResult] = await Promise.all([
        this.#appBridge.dispatchEvent(params),
        this.#ssfHostRef.dispatchEvent(params)
      ]);
      if (Array.isArray(appBridgeResult) && Array.isArray(ssfResult)) {
        return appBridgeResult.concat(ssfResult);
      }
      return Promise.resolve();
    }
    return this.#ssfHostRef.dispatchEvent(params);
  }
  /**
   * removes scripting object from child microapp use
   * @param objectId unique id of the scripting object
   */
  removeScriptingObject(objectId) {
    if (this.#appBridge) {
      this.#appBridge.removeScriptingObject(objectId);
    }
    this.#ssfHostRef.removeScriptingObject(objectId);
  }
}
export {
  CMicroAppHost
};
