import { persistentStorage } from "../../../utils/web-storage.js";
import { CMicroAppHost } from "../../../utils/micro-frontend/host.js";
import { getLogger } from "../../../utils/micro-frontend/index.js";
import { logRecords } from "../../../utils/log-records.js";
import { APP_CONTAINER_ID_PREFIX } from "../const.js";
import {
  getCurrentBreakpoint,
  getViewportSize
} from "../../../utils/window.js";
import {
  addStylesToDOM,
  removeDynamicImportedStyles
} from "../resources/style.js";
import {
  addScriptToDOM,
  removeDynamicImportedScripts,
  removePrefetchLinks
} from "../resources/script.js";
import {
  getAppManifest,
  getFullFileNameofAssetsFromManifest
} from "../resources/manifest.js";
const cssType = /\.css$/;
const isCss = (fileName) => cssType.test(fileName);
const activeApps = {};
const getPathName = (url) => url ? new URL(url).pathname : "";
const getAppNotFoundError = (id, uuid) => `Application ${id} with instance id ${uuid} is not found. Most probably the appId property of app.config.json is not set to ${id}`;
const getMicroAppGuestFromWindow = ({
  id,
  uuid
}) => {
  let microAppGuest = null;
  const guests = window.emui?.[id];
  if (Array.isArray(guests)) {
    microAppGuest = guests.find((guest) => guest.uuid === uuid) ?? null;
  } else {
    microAppGuest = guests;
  }
  return microAppGuest;
};
const initApplication = async ({
  id,
  uuid,
  name,
  hostUrl,
  history,
  theme,
  manifestPath,
  homeRoute
}) => {
  const app = getMicroAppGuestFromWindow({
    id,
    uuid
  });
  if (!app) throw new Error(getAppNotFoundError(id, uuid));
  const { init } = app;
  if (!init || typeof init !== "function")
    throw new Error(
      `Application ${name} with id ${id} doesn't expose init method.`
    );
  const prevState = await persistentStorage.get(
    `state-${id}-${uuid}`
  );
  return init({
    host: CMicroAppHost.getInstance(),
    hostUrl,
    manifestPath,
    homeRoute: homeRoute ?? getPathName(hostUrl),
    prevState,
    history,
    theme,
    hostBreakpoint: getCurrentBreakpoint(),
    hostViewportSize: getViewportSize()
  });
};
const mountApp = async ({ id, uuid, name }) => {
  const app = getMicroAppGuestFromWindow({
    id,
    uuid
  });
  if (!app) throw new Error(getAppNotFoundError(id, uuid));
  const { mount } = app;
  if (!mount || typeof mount !== "function")
    throw new Error(
      `Application ${name} with id ${id} doesn't expose mount method.`
    );
  return mount({
    containerId: `${APP_CONTAINER_ID_PREFIX}${id}`,
    hostBreakpoint: getCurrentBreakpoint(),
    hostViewportSize: getViewportSize()
  });
};
const unmountApp = async ({ id, uuid, name }) => {
  const app = getMicroAppGuestFromWindow({
    id,
    uuid
  });
  if (!app) return null;
  const { unmount } = app;
  if (!unmount) return null;
  if (typeof unmount !== "function")
    throw new Error(
      `unmount failed for application ${name} with id ${id}. unmount is not a valid function`
    );
  const currentState = await unmount({
    containerId: `${APP_CONTAINER_ID_PREFIX}${id}`
  });
  if (currentState) {
    await persistentStorage.set(`state-${id}-${uuid}`, currentState);
  }
  return Promise.resolve();
};
const addAppToActiveAppList = (id, uuid, elementIds) => {
  const app = getMicroAppGuestFromWindow({
    id,
    uuid
  });
  if (!app) throw new Error(getAppNotFoundError(id, uuid));
  const { getRef } = app;
  activeApps[`${id}-${uuid}`] = { elementIds };
  const host = CMicroAppHost.getInstance();
  if (host) host.activeGuests[`${id}-${uuid}`] = getRef && getRef() || {};
  return Promise.resolve();
};
const waitAndInitApplication = (appConfig, requests) => (
  // wait for all assets to get downloaded
  Promise.all(requests).then(addAppToActiveAppList.bind(null, appConfig.id, appConfig.uuid)).then(initApplication.bind(null, appConfig)).catch((err) => {
    const logRecord = logRecords.APP_INIT_FAILED(
      appConfig.id,
      appConfig.uuid,
      err.message
    );
    getLogger().error({ ...logRecord, exception: err });
    throw new Error(logRecord.message);
  })
);
const removeAssetsFromDOM = (id, uuid, documentEle = document) => {
  const host = CMicroAppHost.getInstance();
  if (host) delete host.activeGuests[`${id}-${uuid}`];
  const { elementIds } = activeApps[`${id}-${uuid}`] || {};
  if (elementIds) {
    elementIds.forEach((elementId) => {
      const ele = documentEle.getElementById(elementId);
      if (ele) ele.remove();
    });
    delete activeApps[`${id}-${uuid}`];
  }
};
const addAppToGlobalVariable = ({
  id,
  uuid,
  documentEle
}) => {
  const newAppInstance = {
    uuid,
    init: null,
    mount: null,
    unmount: null,
    getRef: null,
    navigate: null
  };
  const app = getMicroAppGuestFromWindow({ id, uuid });
  if (app) {
    if (!uuid) {
      throw new Error(
        `Application ${id} is already loaded. uuid is required to load multiple instances of the same app`
      );
    }
    if (Array.isArray(window.emui[id])) {
      window.emui[id].push(newAppInstance);
    } else if (typeof app.init === "undefined") {
      window.emui[id] = { ...window.emui[id], ...newAppInstance };
    } else {
      window.emui[id] = [window.emui[id], newAppInstance];
    }
  } else {
    window.emui[id] = newAppInstance;
  }
  if (documentEle.defaultView) {
    documentEle.defaultView.emui = documentEle.defaultView.emui ?? {};
    documentEle.defaultView.emui.uuid = uuid;
  }
};
const removeAppFromGlobalVariable = (id, uuid) => {
  if (Array.isArray(window.emui[id])) {
    const index = window.emui[id].findIndex(
      (app) => app.uuid === uuid
    );
    if (index > -1) window.emui[id].splice(index, 1);
  } else {
    delete window.emui[id];
  }
};
const loadApp = async (appConfig) => {
  const { id, uuid, documentEle, isJsModule } = appConfig;
  addAppToGlobalVariable({ id, uuid, documentEle });
  try {
    getLogger().info(logRecords.APP_LOADING(id, uuid));
    let assets = appConfig.files;
    const manifest = await getAppManifest(appConfig);
    assets = getFullFileNameofAssetsFromManifest(manifest, appConfig.files);
    let counter = 0;
    const requests = assets.map((fileName) => {
      counter += 1;
      return !isCss(fileName) ? addScriptToDOM(appConfig, fileName, counter, isJsModule) : addStylesToDOM(appConfig, fileName, counter);
    });
    await waitAndInitApplication(appConfig, requests);
    getLogger().info(logRecords.APP_LOADING_COMPLETE(id, uuid));
  } catch (err) {
    removeAppFromGlobalVariable(id, uuid);
    throw err;
  }
};
const unloadApp = ({
  id,
  uuid,
  hostUrl,
  documentEle
}) => {
  if (!hostUrl) throw new Error("Unable to unload app. hostUrl is required");
  getLogger().info(logRecords.APP_UNLOADING(id, uuid));
  const app = getMicroAppGuestFromWindow({ id, uuid });
  if (!app) return;
  removeAssetsFromDOM(id, uuid, documentEle);
  removeDynamicImportedScripts(hostUrl, documentEle);
  removePrefetchLinks(hostUrl, documentEle);
  removeDynamicImportedStyles(hostUrl, documentEle);
  removeAppFromGlobalVariable(id, uuid);
  getLogger().info(logRecords.APP_UNLOADING_COMPLETE(id, uuid));
};
export {
  loadApp,
  mountApp,
  unloadApp,
  unmountApp
};
