import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
import { getDefaultTheme } from "@elliemae/pui-theme";
import { SSFGuest } from "@elliemae/ssf-guest";
import { updateBAEventParameters } from "@elliemae/pui-analytics-so";
import { getWindow } from "../window.js";
import { loadAppConfig } from "../app-config/index.js";
import { getAppConfigValue } from "../app-config/config.js";
import { browserHistory } from "../history.js";
import { consoleLogger } from "../console-logger.js";
import { setLogger } from "../logger.js";
import { removeStorageEvents } from "../web-storage.js";
import { setLogRocket } from "../logrocket.js";
import { getAppBridge } from "./app-bridge.js";
import { abortPromise } from "../helpers.js";
import { getSSFHost, setSSFHost } from "./ssf-host.js";
const isCrossDomain = () => {
  try {
    window.parent.document;
    return false;
  } catch (ex) {
    return true;
  }
};
var CommunicationMethod = /* @__PURE__ */ ((CommunicationMethod2) => {
  CommunicationMethod2["SSF"] = "ssf";
  CommunicationMethod2["APPSDK"] = "app-sdk";
  return CommunicationMethod2;
})(CommunicationMethod || {});
class CMicroAppGuest {
  /**
   * unique microapp id
   */
  #uuid;
  static instance;
  logger;
  /**
   * LogRocket instance
   */
  #logRocket;
  appId;
  props;
  containerId;
  /**
   * communication method used between microapps
   */
  #communicationMethod;
  /**
   * reference to SSFHost instance
   */
  #ssfHostRef;
  /**
   * true if the app prefers to use SSF V2 Guest from App SDK to communicate with host(optional, default: true)
   */
  #useV2Guest;
  /**
   * AppBridge instance
   */
  #appBridge = null;
  onInit;
  onMount;
  onUnmount;
  onGetRef;
  // eslint-disable-next-line max-statements
  constructor(params) {
    this.containerId = "app-container";
    window.emui.uuid = window.emui.uuid || uuidv4();
    this.appId = window.emui?.appId || getAppConfigValue("appId");
    this.#uuid = window.emui.uuid;
    this.#communicationMethod = "app-sdk" /* APPSDK */;
    this.props = {
      host: null,
      hostUrl: null,
      prevState: null,
      history: params?.history || browserHistory,
      theme: getDefaultTheme(),
      manifestPath: "./latest"
    };
    this.logger = params?.logger || consoleLogger;
    setLogger(this.logger);
    this.#logRocket = params?.logRocket ?? window.emui?.logRocket;
    setLogRocket(this.#logRocket);
    if (params?.ssfHostRef) setSSFHost(params.ssfHostRef);
    this.#ssfHostRef = getSSFHost();
    this.#useV2Guest = params?.useV2Guest !== false;
    this.onInit = params?.onInit;
    this.onMount = params?.onMount;
    this.onUnmount = params?.onUnmount;
    this.onGetRef = params?.onGetRef;
    this.getHost = this.getHost.bind(this);
    this.getLogger = this.getLogger.bind(this);
    this.getProps = this.getProps.bind(this);
    this.#addAppToGlobalVariable();
  }
  static getInstance(params) {
    if (!this.instance) {
      this.instance = new CMicroAppGuest(params);
    }
    return this.instance;
  }
  static isInitialized() {
    return !!this.instance;
  }
  static isHosted() {
    return !!this.instance?.props?.host;
  }
  getHost() {
    return this.props.host;
  }
  getLogger() {
    return this.logger;
  }
  getObject(objectId) {
    let host = null;
    if (this.#communicationMethod === "ssf" /* SSF */) {
      host = this.getHost();
      return host.getObject(objectId);
    }
    host = this.getHost();
    if (!host) return Promise.resolve(null);
    return host.getObject(objectId);
  }
  /**
   * subscribe to the scripting object event
   * @param {SubscribeParam<EventId, AppEvents[EventId]>} params - the parameters for the subscription
   * @param {string} params.soName - the name of the scripting object
   * @param {string} params.eventName - the name of the event
   * @param {Function} params.callback - the callback function to be called when the event is triggered
   * @returns {void}
   */
  subscribe(params) {
    let host = null;
    if (this.#communicationMethod === "ssf" /* SSF */) {
      host = this.getHost();
      return host.subscribe(params);
    }
    host = this.getHost();
    return host.subscribe(params);
  }
  /**
   * unsubscribe from the scripting object event
   * @param params - parameters for the unsubscription
   */
  unsubscribe(params) {
    let host = null;
    if (this.#communicationMethod === "ssf" /* SSF */) {
      host = this.getHost();
      host.unsubscribe(params);
    }
    host = this.getHost();
    host.unsubscribe(params);
  }
  getProps() {
    return this.props;
  }
  /**
   * add the microapp to the global variable in the parent window and current window
   */
  #addAppToGlobalVariable() {
    const browserWindow = getWindow();
    if (!browserWindow) return;
    browserWindow.emui = browserWindow.emui || {};
    const app = {
      uuid: this.#uuid,
      init: this.init.bind(this),
      mount: this.mount.bind(this),
      unmount: this.unmount.bind(this),
      getRef: this.getRef.bind(this),
      navigate: this.navigate.bind(this)
    };
    if (browserWindow.emui[this.appId]) {
      if (Array.isArray(browserWindow.emui[this.appId])) {
        const existingApps = browserWindow.emui[this.appId];
        const appIndex = existingApps.findIndex((a) => a.uuid === this.#uuid);
        if (appIndex > -1) existingApps[appIndex] = app;
        else existingApps.push(app);
      } else if (browserWindow.emui[this.appId].uuid === this.#uuid) {
        browserWindow.emui[this.appId] = app;
      } else if (typeof browserWindow.emui[this.appId].init === "undefined") {
        browserWindow.emui[this.appId] = {
          ...browserWindow.emui[this.appId],
          ...app
        };
      } else {
        browserWindow.emui[this.appId] = [
          browserWindow.emui[this.appId],
          app
        ];
      }
    } else {
      browserWindow.emui[this.appId] = app;
    }
    window.emui.app = app;
    window.addEventListener("beforeunload", this.#onWindowUnload.bind(this));
  }
  /**
   * remove the microapp from the global variable from the parent window and current window
   */
  #onWindowUnload() {
    const browserWindow = getWindow();
    if (Array.isArray(browserWindow.emui[this.appId])) {
      const existingApps = browserWindow.emui[this.appId];
      const index = existingApps.findIndex((app) => app.uuid === this.#uuid);
      if (index > -1) existingApps.splice(index, 1);
      if (existingApps.length === 0) {
        delete browserWindow.emui[this.appId];
      }
    } else {
      delete browserWindow.emui[this.appId];
    }
    delete window.emui.app;
    this.unmount({ containerId: this.containerId }).catch(() => {
    });
  }
  getSessionStorageItem(key) {
    let value = sessionStorage.getItem(key);
    const host = this.getHost();
    if (!value && host?.getItem) {
      value = host.getItem(key);
    }
    return value;
  }
  async findHost() {
    let host = null;
    if (!isCrossDomain()) {
      host = window.parent?.emui?.MicroAppHost ?? null;
    }
    if (!host && this.#useV2Guest) {
      host = new SSFGuest({ appLogger: this.logger });
      if (await abortPromise({
        promise: host.connect(),
        delay: 1e3,
        retValue: false
      })) {
        host = null;
      } else this.#communicationMethod = "ssf" /* SSF */;
    }
    return host;
  }
  get uuid() {
    return this.#uuid;
  }
  async init(options) {
    const { history, ...rest } = options ?? {};
    this.props = _.merge(this.props, rest);
    if (!this.props.host) {
      const host = await this.findHost();
      if (host) {
        this.props.host = host;
      } else this.logger.warn("unable to connect to host microapp");
    }
    __webpack_public_path__ = __webpack_public_path__.replace(/\/?$/, "/");
    return loadAppConfig().then(() => {
      const instanceId = this.getSessionStorageItem("instanceId") || "";
      const userId = this.getSessionStorageItem("userId") || "";
      updateBAEventParameters({ appId: this.appId, instanceId, userId });
      const useParentHistory = getAppConfigValue("useParentHistory", true);
      if (useParentHistory && history) {
        this.props.history = history;
      }
      if (!useParentHistory && options?.initialRoute) {
        this.props.history.push(options.initialRoute);
      }
      return getAppBridge().then((appBridge) => {
        this.#appBridge = appBridge;
        if (this.onInit) this.onInit(this.props);
      });
    });
  }
  mount(options = { containerId: "app-container" }) {
    this.containerId = options?.containerId;
    return Promise.resolve().then(() => {
      if (this.onMount) this.onMount(options);
    });
  }
  unmount(options) {
    removeStorageEvents();
    return Promise.resolve().then(
      () => this.onUnmount ? this.onUnmount(options) : null
    );
  }
  getRef() {
    return this.onGetRef ? this.onGetRef() : null;
  }
  navigate(url, state) {
    if (url) this.props.history.push(url, state);
  }
  /**
   * 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);
  }
  /**
   * Dispatches an event to the child microapp.
   * @param {DispatchEventParam<EventId, Params, Options>} 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 {
  CMicroAppGuest
};
