const _this = {};
const normalize_runtime_flag = function (value) {
  return value === true || value === 'true' || value === 1 || value === '1' ? true : undefined;
};
// boaz
export const init_module = (e) => {
  _this.func = e.func;
  _this.glb = e.glb;
  _this.SESSION_OBJ = e.SESSION_OBJ;
  _this.APP_OBJ = e.APP_OBJ;
  _this.IS_DOCKER = normalize_runtime_flag(e.IS_DOCKER);
  _this.IS_API_SERVER = normalize_runtime_flag(e.IS_API_SERVER);
  _this.IS_PROCESS_SERVER = normalize_runtime_flag(e.IS_PROCESS_SERVER);
};

const clone_json = function (data) {
  if (typeof data === 'undefined') {
    return data;
  }
  return JSON.parse(JSON.stringify(data));
};

const get_doc_ts = function (doc) {
  return Math.max(Number(doc?.ts) || 0, Number(doc?.order_ts) || 0, Number(doc?.docDate) || 0, Number(doc?.studio_meta?.created) || 0, Number(doc?.studio_meta?.migratedTs) || 0);
};

const resolve_studio_app_id = function (SESSION_ID, app_id) {
  const _session = _this.SESSION_OBJ[SESSION_ID];
  const resolved_app_id =
    (app_id && app_id !== 'unknown' && app_id) ||
    (_session?.app_id && _session.app_id !== 'unknown' && _session.app_id) ||
    _session?.url_params?.app_id ||
    _session?.url_params?.id ||
    _session?.build_info?.src_app_id ||
    _session?.build_info?.app_id ||
    (typeof XUDA_BUILD_SRC !== 'undefined' && XUDA_BUILD_SRC) ||
    (typeof XUDA_BUILD_INFO !== 'undefined' && (XUDA_BUILD_INFO?.src_app_id || XUDA_BUILD_INFO?.app_id)) ||
    _this.func?.runtime?.ui?.get_attr?.(_session?.root_element, 'app_id') ||
    null;

  if (resolved_app_id && resolved_app_id !== 'unknown' && _session && (!_session.app_id || _session.app_id === 'unknown')) {
    _session.app_id = resolved_app_id;
  }

  return resolved_app_id && resolved_app_id !== 'unknown' ? resolved_app_id : null;
};

const resolve_draft_runtime_flag = function (SESSION_ID, app_id, ...candidates) {
  const _session = _this.SESSION_OBJ?.[SESSION_ID];
  const candidate_app_ids = [
    resolve_studio_app_id(SESSION_ID, app_id),
    ...candidates.map((candidate) => candidate?._id || candidate?.app_id).filter(Boolean),
    _session?.app_id,
  ].filter(Boolean);

  for (const candidate_app_id of candidate_app_ids) {
    const app_obj = _this.APP_OBJ?.[candidate_app_id];
    if (typeof app_obj?.draft === 'boolean') {
      return app_obj.draft;
    }
    if (typeof app_obj?.is_draft_runtime === 'boolean') {
      return app_obj.is_draft_runtime;
    }
  }

  for (const candidate of candidates) {
    if (typeof candidate?.draft === 'boolean') {
      return candidate.draft;
    }
    if (typeof candidate?.is_draft_runtime === 'boolean') {
      return candidate.is_draft_runtime;
    }
  }

  if (typeof _session?.build_info?.draft === 'boolean') {
    return _session.build_info.draft;
  }

  return false;
};

const should_try_local_studio_snapshot = function (SESSION_ID, app_id) {
  const _session = _this.SESSION_OBJ[SESSION_ID];
  const is_server_runtime = !!(_this.IS_DOCKER || _this.IS_PROCESS_SERVER || _this.IS_API_SERVER);
  const resolved_app_id = resolve_studio_app_id(SESSION_ID, app_id);
  const is_supported_mode = !_session?.engine_mode || ['miniapp', 'live_preview'].includes(_session.engine_mode);
  return !is_server_runtime && is_supported_mode && !!resolved_app_id;
};

const connect_studio_pouchdb = function (app_id, rt = false, custom) {
  if (_this.func?.utils?.connect_studio_pouchdb) {
    return _this.func.utils.connect_studio_pouchdb(app_id, rt, custom);
  }

  if (custom) {
    return new PouchDB(custom, { auto_compaction: true });
  }

  var db_name = 'xuda_studio_db';
  if (app_id) {
    db_name += '_' + app_id;
  }

  if (rt) {
    db_name = `xuda_rt_${app_id}`;
  }

  return new PouchDB(db_name, { auto_compaction: true });
};

const connect_studio_resources_pouchdb = function () {
  return connect_studio_pouchdb(null, false, 'xuda_studio_resources');
};

const upsert_cache_doc = async function (db, doc) {
  try {
    const existing_doc = await db.get(doc._id);
    doc._rev = existing_doc._rev;
  } catch (error) {}

  return await db.put(doc);
};

const write_cached_build_info = async function (SESSION_ID, build_info_ret) {
  const db = await func.utils.connect_pouchdb(SESSION_ID);
  await upsert_cache_doc(db, {
    _id: `cache_build_info`,
    build_info: build_info_ret,
    docType: 'cache_build_info',
  });
};

const write_cached_rt_info = async function (SESSION_ID, rt_info_ret) {
  const db = await func.utils.connect_pouchdb(SESSION_ID);
  await upsert_cache_doc(db, {
    _id: `cache_rt_info`,
    data: rt_info_ret,
    docType: 'cache_app',
  });
};

const build_draft_error_message = function (app_id, remote_error) {
  let message = `Unable to load remote app ${app_id}. No local Studio draft was found in browser storage.`;
  if (remote_error) {
    message += ` Remote error: ${remote_error}`;
  }
  return message;
};

const load_active_studio_docs = async function (studio_db) {
  try {
    const ret = await studio_db.find({
      selector: {
        docType: 'studio',
        stat: 3,
      },
      limit: 999999999,
    });
    return ret.docs || [];
  } catch (error) {
    const ret = await studio_db.allDocs({
      include_docs: true,
    });

    return (ret.rows || []).map((row) => row.doc).filter((doc) => doc?.docType === 'studio' && doc?.stat === 3);
  }
};

const infer_draft_plugin_manifest_entry = function (plugin_name, plugin_doc, dist_path, source_path, extra = {}) {
  const has_file = function (file_path) {
    return !!plugin_doc?.files?.[`${plugin_name}/${file_path}`];
  };

  if (has_file(dist_path)) {
    return {
      exist: true,
      dist: true,
      is_empty: false,
      ...extra,
    };
  }

  if (has_file(source_path)) {
    return {
      exist: true,
      dist: false,
      is_empty: false,
      ...extra,
    };
  }

  return null;
};

const normalize_draft_plugins = async function (app_plugins_purchased = {}) {
  const ret = {};
  let resources_db = null;

  try {
    resources_db = connect_studio_resources_pouchdb();
  } catch (error) {}

  for (const [plugin_name, plugin_meta] of Object.entries(app_plugins_purchased || {})) {
    const plugin = clone_json(plugin_meta || {});
    if (!plugin.installed) {
      ret[plugin_name] = plugin;
      continue;
    }

    if (!plugin.manifest) {
      plugin.manifest = {};
    }

    let plugin_doc = null;
    if (resources_db) {
      try {
        plugin_doc = await resources_db.get(plugin_name);
      } catch (error) {}
    }

    if (!plugin.manifest['runtime.mjs']) {
      plugin.manifest['runtime.mjs'] =
        infer_draft_plugin_manifest_entry(plugin_name, plugin_doc, 'dist/runtime.mjs', 'runtime.mjs', {
          css: !!plugin_doc?.files?.[`${plugin_name}/dist/runtime.css`],
        }) || {
          exist: true,
          dist: true,
          is_empty: false,
          css: false,
        };
    }

    if (!plugin.manifest['index.mjs']) {
      plugin.manifest['index.mjs'] =
        infer_draft_plugin_manifest_entry(plugin_name, plugin_doc, 'dist/index.mjs', 'index.mjs') || {
          exist: true,
          dist: true,
          is_empty: false,
        };
    }

    ret[plugin_name] = plugin;
  }

  return ret;
};

const build_draft_docs_map = function (app_id, studio_docs = []) {
  const docs_obj = {};
  let globals_doc = null;

  for (const doc of studio_docs) {
    const cloned_doc = clone_json(doc);
    docs_obj[cloned_doc._id] = cloned_doc;
    if (cloned_doc?.properties?.menuType === 'globals') {
      globals_doc = cloned_doc;
    }
  }

  if (globals_doc && !docs_obj[`global_${app_id}`]) {
    docs_obj[`global_${app_id}`] = {
      ...clone_json(globals_doc),
      _id: `global_${app_id}`,
    };
  }

  return docs_obj;
};

const get_local_draft_snapshot = async function (SESSION_ID, app_id) {
  app_id = resolve_studio_app_id(SESSION_ID, app_id);
  if (!should_try_local_studio_snapshot(SESSION_ID, app_id)) {
    return null;
  }

  const studio_db = connect_studio_pouchdb(app_id);
  let app_doc;

  try {
    app_doc = await studio_db.get(app_id);
  } catch (error) {
    return null;
  }

  const studio_docs = await load_active_studio_docs(studio_db);
  const docs_obj = build_draft_docs_map(app_id, studio_docs);
  const draft_docs = Object.values(docs_obj);
  const tables = [];
  const programs = [];

  for (const doc of draft_docs) {
    const menu_type = doc?.properties?.menuType;
    if (menu_type === 'table') {
      tables.push(doc._id);
      continue;
    }
    if (menu_type === 'globals') {
      continue;
    }
    programs.push(doc._id);
  }

  let last_changed_ts = get_doc_ts(app_doc);
  for (const doc of draft_docs) {
    last_changed_ts = Math.max(last_changed_ts, get_doc_ts(doc));
  }

  const build = last_changed_ts || 0;
  const app_doc_data = clone_json(app_doc) || {};
  const is_draft_runtime = _this.SESSION_OBJ?.[SESSION_ID]?.engine_mode === 'live_preview' ? !!app_doc_data.draft : true;
  delete app_doc_data._rev;

  const rt_info_obj = {
    ...app_doc_data,
    _id: app_id,
    app_id,
    draft: is_draft_runtime,
    is_deployment: false,
    is_draft_runtime,
    app_type: app_doc_data.app_type || 'master',
    app_build_id: build.toString(),
    app_id_reference: app_doc_data.app_id_reference || app_id,
    app_replicate: app_doc_data.app_replicate || app_id,
    app_db_name: app_doc_data.app_db_name || app_id,
    app_plugins_purchased: await normalize_draft_plugins(app_doc_data.app_plugins_purchased || {}),
    account_info: app_doc_data.account_info || {},
    login_info: app_doc_data.login_info || {},
    deploy_data: clone_json(app_doc_data.deploy_data || {}),
    client_ip: '',
    rpi_http_methods: ['get_doc_obj_from_build', 'dbs_read', 'dbs_create', 'dbs_update', 'dbs_delete'],
    accessible_deployed_progs_arr: programs,
    accessible_deployed_tables_arr: tables,
  };

  const build_info = {
    build,
    src_app_id: app_id,
    master_build: build,
    server_ts: last_changed_ts,
    last_changed_ts,
    progs_changed: programs,
    tables_changed: tables,
    draft: is_draft_runtime,
  };

  return {
    build_info,
    build_info_ret: {
      code: 1,
      data: build_info,
    },
    rt_info: rt_info_obj,
    rt_info_ret: {
      code: 1,
      data: rt_info_obj,
    },
    docs_obj,
  };
};

const persist_draft_snapshot = async function (SESSION_ID, draft_snapshot) {
  await write_cached_build_info(SESSION_ID, draft_snapshot.build_info_ret);
  await write_cached_rt_info(SESSION_ID, draft_snapshot.rt_info_ret);
};

export const project_loader = async function (SESSION_ID, app_id, prog_id) {
  try {
    var _session = _this.SESSION_OBJ[SESSION_ID];
    app_id = resolve_studio_app_id(SESSION_ID, app_id) || app_id;
    if (app_id && app_id !== 'unknown') {
      _session.app_id = app_id;
    }

    if (_this.func.UI.utils.get_url_attribute(SESSION_ID, 'clear_cache')) {
      await func.index.delete_pouch(SESSION_ID);
    }
    let last_changed_ts = 0;
    var ret_build_info = {};
    let draft_snapshot = null;

    if (_session.engine_mode === 'live_preview') {
      draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
      if (!draft_snapshot) {
        return _this.func.UI.utils.progressScreen.show(SESSION_ID, build_draft_error_message(app_id), null, true);
      }
      ret_build_info = draft_snapshot.build_info_ret;
      _session.build_info = draft_snapshot.build_info;
      _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, draft_snapshot.rt_info, draft_snapshot.build_info);
      await persist_draft_snapshot(SESSION_ID, draft_snapshot);
      last_changed_ts = draft_snapshot.build_info.last_changed_ts || 0;
    } else if (typeof XUDA_BUILD_INFO !== 'undefined') {
      // call from xuda real-preview app
      _session.build_info = XUDA_BUILD_INFO;
      _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, _session.build_info);
      if (_session.is_draft_runtime) {
        draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
        if (!draft_snapshot) {
          return _this.func.UI.utils.progressScreen.show(SESSION_ID, build_draft_error_message(app_id), null, true);
        }
        ret_build_info = draft_snapshot.build_info_ret;
        _session.build_info = draft_snapshot.build_info;
        _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, draft_snapshot.rt_info, draft_snapshot.build_info);
        await persist_draft_snapshot(SESSION_ID, draft_snapshot);
      }
      last_changed_ts = _session.build_info?.last_changed_ts || 0;
    } else {
      // test
      const db = await func.utils.connect_pouchdb(SESSION_ID);
      if (['live_preview', 'miniapp'].includes(_session.engine_mode)) {
        // if on-line
        if (IS_ONLINE) {
          ret_build_info = await get_app_build_info(SESSION_ID, app_id);
          if (ret_build_info.code < 0) {
            draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
            if (!draft_snapshot) {
              return _this.func.UI.utils.progressScreen.show(SESSION_ID, build_draft_error_message(app_id, ret_build_info.data), null, true);
            }
            ret_build_info = draft_snapshot.build_info_ret;
            _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, draft_snapshot.rt_info, draft_snapshot.build_info);
            await persist_draft_snapshot(SESSION_ID, draft_snapshot);
          } else {
            _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, ret_build_info?.data);
          }
          _session.build_info = ret_build_info.data;
          await write_cached_build_info(SESSION_ID, ret_build_info);
        } else {
          // off-line get from pouch
          try {
            ret_build_info = (await db.get(`cache_build_info`)).build_info;
            _session.build_info = ret_build_info.data;
            _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, ret_build_info?.data);
          } catch (error) {
            draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
            if (!draft_snapshot) {
              return console.error('cache_build_info error');
            }
            ret_build_info = draft_snapshot.build_info_ret;
            _session.build_info = draft_snapshot.build_info;
            _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, draft_snapshot.rt_info, draft_snapshot.build_info);
            await persist_draft_snapshot(SESSION_ID, draft_snapshot);
          }
        }
        last_changed_ts = ret_build_info.data.last_changed_ts;
      } else {
        // deployments
        try {
          // const db = await func.utils.connect_pouchdb(SESSION_ID);
          // only get indication for fresh installation
          await db.get(`cache_rt_info`);
        } catch (err) {
          // fresh load
          const startup_module = await func.common.get_module(SESSION_ID, 'xuda-deploy-startup-loader.mjs');
          await startup_module.loader(SESSION_ID);
        }
        last_changed_ts = _session.opt.last_changed_ts;
      }
    }

    // if (_session.engine_mode !== 'live_preview') {
    //   try {
    //     const db = await func.utils.connect_pouchdb(SESSION_ID);
    //     // only get indication for fresh installation
    //     await db.get(`cache_rt_info`);
    //   } catch (err) {
    //     const startup_module = await func.common.get_module(SESSION_ID, 'xuda-deploy-startup-loader.mjs');
    //     await startup_module.loader(SESSION_ID);
    //   }
    //   last_changed_ts = _session.opt.last_changed_ts;
    // }

    const rt_info_ret = await get_rt_info(SESSION_ID, app_id, last_changed_ts);
    const active_draft_snapshot = rt_info_ret?.draft_snapshot || draft_snapshot;

    insert_custom_prop(SESSION_ID);

    if (_session?.app_admin_prop?.app_admin_direction) {
      _session.root_element.setAttribute('dir', _session.app_admin_prop.app_admin_direction);
    }
    // load live preview module

    const progs_loader_module = await _this.func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);

    if (_session.is_draft_runtime && active_draft_snapshot?.docs_obj) {
      await progs_loader_module.prime_objects_cache(SESSION_ID, 'DOCS_OBJ', active_draft_snapshot.docs_obj);
    } else if (_session.app_id !== 'unknown' && _session.engine_mode !== 'live_preview') {
      await progs_loader_module.load_objects_cache(SESSION_ID);
    }

    if (_session.engine_mode === 'live_preview') {
      const live_preview_module = await _this.func.common.get_module(SESSION_ID, 'xuda-live-preview-module.esm.js');

      live_preview_module.live_preview_loader(SESSION_ID);
      return;
    }

    if (_this.APP_OBJ[_session.app_id]?.is_deployment) {
      try {
        await init_runtime_websocket(SESSION_ID, _session.app_id);
      } catch (error) {
        throw error;
      }
    }
    await _this.func.UI.main.embed_loader(SESSION_ID);
  } catch (error) {
    throw error;
  }
};

const get_app_build_info = async function (SESSION_ID, app_id) {
  return new Promise(function (resolve, reject) {
    var _session = _this.SESSION_OBJ[SESSION_ID];
    let app_id_reference = _this.APP_OBJ[app_id].app_id_reference;

    fetch(_this.func.common.get_url(SESSION_ID, 'rpi', 'get_app_build'), {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        'xu-gtp-token': _session.gtp_token,
        'xu-app-token': _session.app_token,
      },
      body: JSON.stringify({
        app_id: app_id,
        app_id_reference,
        engine_mode: _session.engine_mode,
      }),
    })
      .then((response) => {
        if (!response.ok) {
          return response.text().then((text) => {
            throw new Error(text);
          });
        }

        return response.json();
      })
      .then(async (json) => {
        _this.SESSION_OBJ[SESSION_ID].build_info = json.data;

        resolve(json);
      })
      .catch((err) => {
        try {
          resolve(JSON.parse(err.message));
        } catch (error) {
          resolve({ code: -1, data: err.message });
        }
      });
  });
};

const get_user_group_account_info = async function (SESSION_ID, uid) {
  var _session = SESSION_OBJ[SESSION_ID];

  const response = await fetch(`https://${_session.domain}/cpi/get_account_info`, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      uid,
      uid_query:
        document.cookie
          .split('; ')
          .find((c) => c.startsWith('uid='))
          ?.split('=')[1] || '',
    }),
  });
  const json = await response.json();

  return json.data;
};

const get_rt_info = async function (SESSION_ID, app_id, last_changed_ts) {
  var _session = _this.SESSION_OBJ[SESSION_ID];
  app_id = resolve_studio_app_id(SESSION_ID, app_id) || app_id;

  return new Promise(async function (resolve, reject) {
    const response = {
      success: async function (ret, ajaxP) {
        if (ret.code < 0) {
          return response.error(ret.data);
        }
        const rt_info_obj = ret.data;
        var app_id = rt_info_obj._id;
        _this.APP_OBJ[app_id] = rt_info_obj;
        _session.app_id = app_id;

        if (rt_info_obj?.deploy_data?.global_variables) {
          _session.url_params = {
            ..._session.url_params,
            ...rt_info_obj.deploy_data.global_variables,
          };
        }

        let account_info = { ...(rt_info_obj?.account_info || {}) };
        if (_session.engine_mode === 'user_group') {
          const user_group_data = await get_user_group_account_info(SESSION_ID, rt_info_obj?.account_info?.uid);
          account_info = user_group_data.account_info;
          account_info.uid = user_group_data._id;
        }
        _session.USR_OBJ = {
          _id: account_info?.uid,
          usr_name: account_info?.username,
          usr_first_name: account_info?.first_name || account_info?.email,
          usr_last_name: account_info?.last_name || '',
          usr_email: account_info?.email,
          usr_profile_picture: account_info?.profile_picture,
        };

        _session.login_info = rt_info_obj?.login_info;
        _session.client_ip = rt_info_obj.client_ip;
        _session.rpi_http_methods = rt_info_obj.rpi_http_methods;
        _session.app_admin_prop = rt_info_obj.app_admin_prop;
        _session.is_deployment = rt_info_obj.is_deployment;
        _session.is_draft_runtime = resolve_draft_runtime_flag(SESSION_ID, app_id, rt_info_obj);

        // const set_prog_cache = async function () {
        //   if (_session.prog_id) {
        //     if (!rt_info_obj.prog_docs[_session.prog_id]) {
        //       return console.error(
        //         `error - program ${_session.prog_id} not found.`
        //       );
        //     }

        //     DOCS_OBJ[app_id][_session.prog_id] = rt_info_obj.prog_docs[
        //       _session.prog_id
        //     ] || {
        //       _id: _session.prog_id,
        //     };

        //     const module = await _this.func.common.get_module(
        //       SESSION_ID,
        //       `xuda-progs-loader-module.mjs`
        //     );

        //     module.save_objects_cache(
        //       SESSION_ID,
        //       _session.prog_id,
        //       "DOCS_OBJ",
        //       rt_info_obj.prog_docs[_session.prog_id]
        //     );
        //   }

        //   // for (let prog_id of [
        //   //   ...rt_info_obj?.accessible_deployed_progs_arr,
        //   //   ...rt_info_obj?.accessible_deployed_tables_arr,
        //   // ] || []) {
        //   //   module.DOCS_OBJ_get(SESSION_ID, prog_id);
        //   // }
        // };

        // if (_session.engine_mode !== "live_preview") {
        //   set_prog_cache();
        // }

        resolve({
          rt_info_obj,
          draft_snapshot: ret?.draft_snapshot || null,
        });

        const loaderLogo = document.querySelector('.loader_logo');
        if (loaderLogo) {
          loaderLogo.style.backgroundImage = `url( ${rt_info_obj.app_pic})`;
          loaderLogo.style.display = 'none';
          loaderLogo.style.display = '';
        }
      },
      error: async function (err) {
        if (err) {
          return _this.func.UI.utils.progressScreen.show(SESSION_ID, err, null, true);
        }
        // location.reload();
        console.warn('** reload request');
      },
    };
    const db = await func.utils.connect_pouchdb(SESSION_ID);

    if (_session.engine_mode === 'live_preview') {
      const draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
      if (!draft_snapshot) {
        return response.error(build_draft_error_message(app_id));
      }

      await persist_draft_snapshot(SESSION_ID, draft_snapshot);
      response.success({
        ...draft_snapshot.rt_info_ret,
        draft_snapshot,
      });
      return;
    }

    if (resolve_draft_runtime_flag(SESSION_ID, app_id, _session?.build_info)) {
      const draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
      if (!draft_snapshot) {
        return response.error(build_draft_error_message(app_id));
      }

      await persist_draft_snapshot(SESSION_ID, draft_snapshot);
      response.success({
        ...draft_snapshot.rt_info_ret,
        draft_snapshot,
      });
      return;
    }

    try {
      let ret = await db.get(`cache_rt_info`);
      const rt_info_obj = ret.data;
      if (rt_info_obj.data.last_changed_ts !== last_changed_ts || (!['live_preview', 'miniapp'].includes(_session.engine_mode) && rt_info_obj.data.app_build_id !== _session.opt.app_build_id)) {
        _this.func.UI.utils.progressScreen.show(SESSION_ID, 'New application setup detected, refreshing data and reloading in 5 sec');
        setTimeout(async () => {
          await func.index.delete_pouch(SESSION_ID);
          location.reload();
        }, 5000);
        return;
      }

      response.success(rt_info_obj);
    } catch (err) {
      fetch(_this.func.common.get_url(SESSION_ID, 'rpi', 'get_rt_info'), {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
          'xu-gtp-token': _session.gtp_token,
          'xu-app-token': _session.app_token,
        },
        body: JSON.stringify({
          app_id,
          prog_id: _session.engine_mode === 'live_preview' ? '' : _session.prog_id,
          engine_mode: _session.engine_mode,
          build: _session.build_info.build,
          session_id: SESSION_ID,
          is_cordova: _this.glb.is_cordova,
          client_info: _session.SYS_GLOBAL_OBJ_CLIENT_INFO,
          client_id: _session.SYS_GLOBAL_OBJ_CLIENT_INFO.fingerprint,
        }),
      })
        .then((response) => {
          if (!response.ok) {
            return response.text().then((text) => {
              throw new Error(text);
            });
          }
          return response.json();
        })
        .then((json) => {
          if (json.code < 0) {
            throw new Error(json.data || 'unknown error');
          }
          response.success(json);
        })
        .then(async (json) => {
          if (json?.code > 0) {
            await write_cached_rt_info(SESSION_ID, json);
          }
        })
        .catch(async (err) => {
          if (should_try_local_studio_snapshot(SESSION_ID, app_id)) {
            const draft_snapshot = await get_local_draft_snapshot(SESSION_ID, app_id);
            if (draft_snapshot) {
              await persist_draft_snapshot(SESSION_ID, draft_snapshot);
              response.success({
                ...draft_snapshot.rt_info_ret,
                draft_snapshot,
              });
              return;
            }
            response.error(build_draft_error_message(app_id, err.message));
            return;
          }
          response.error(err.message);
        });
    }
  });
};

const insert_custom_prop = function (SESSION_ID) {
  try {
    var app_id = _this.SESSION_OBJ[SESSION_ID].app_id;
    if (_this.APP_OBJ[app_id]?.app_custom_prop?.app_custom_header) {
      var head = document.getElementsByTagName('head')[0];
      const app_custom_header = _this.APP_OBJ[app_id].app_custom_prop.app_custom_header;

      head.insertAdjacentHTML('beforeend', func.utils.replace_studio_drive_url(SESSION_ID, app_custom_header));
    }
    if (_this.APP_OBJ[app_id]?.app_custom_prop?.app_custom_body) {
      _this.SESSION_OBJ[SESSION_ID].root_element.insertAdjacentHTML('afterbegin', _this.APP_OBJ[app_id].app_custom_prop.app_custom_body);
    }
  } catch (err) {
    console.error(err);
  }
};

const init_runtime_websocket = function (SESSION_ID, app_id) {
  return new Promise(function (resolve, reject) {
    const set_connected = async function (stat) {
      var datasource_changes = {
        [0]: {
          ['data_system']: { SYS_GLOBAL_BOL_CONNECTED: stat },
        },
      };
      await func.datasource.update(SESSION_ID, datasource_changes);
    };

    //////////////// IO //////////////////
    var _session = _this.SESSION_OBJ[SESSION_ID];
    var _data_system = _session?.DS_GLB?.[0]?.data_system;
    const url = `https://${_this.func.runtime.platform.get_hostname()}`;
    var error;
    RUNTIME_SERVER_WEBSOCKET = io(url, {
      secure: true,
      reconnection: true, // _this.glb.debug_js ? false : true,
      // reconnection: true,
      // reconnectionDelayMax: 10000,
      rejectUnauthorized: false,
      path: '/ws/socket.io',

      // transports: ['websocket']
      // query: { session_obj: SESSION_OBJ[SESSION_ID] },
    });

    RUNTIME_SERVER_WEBSOCKET.on('connect', () => {
      console.info('RUNTIME_SERVER_WEBSOCKET connected');

      if (_data_system) {
        set_connected(1);
      }

      if (_session.opt.enable_offline) {
        if (_session.root_element.classList.contains('runtime_offline')) {
          _session.root_element.classList.remove('runtime_offline');
          func.utils.alerts.toast(SESSION_ID, 'Switched to on-line mode', 'You are now online. All data stored while you were offline will be synchronized to the server.', 'success');
        }
      } else {
        // back from temporarily disconnected from the server
        _this.func.UI.utils.progressScreen.hide(SESSION_ID);
      }
      if (error) {
        if (!RUNTIME_SERVER_WEBSOCKET_CONNECTED) {
          // location.reload();
          console.warn('** reload request');
        }
      }
    });

    RUNTIME_SERVER_WEBSOCKET.on('message', (e) => {
      if (_this.APP_OBJ[app_id].is_deployment) {
        _this.func.UI.utils.indicator.server.busy();
        setTimeout(
          function () {
            _this.func.UI.utils.indicator.server.normal();
          },
          e?.data?.length * 100 || 100,
        );
      }
      var data = e.data;

      if (e.source === 'http_call') {
        if (e.service === 'get_doc_obj_from_build') {
          return func.runtime.platform.emit('get_doc_obj_from_build_response_' + data._id, {
            data,
          });
        }
        if (e.service === 'heartbeat') {
          return func.runtime.platform.emit('heartbeat_response', {
            data,
          });
        }
        return func.runtime.platform.emit('get_ws_data_response_' + e.websocket_queue_num, {
          data,
          e,
        });
      }
      if (e.source === 'deployment_server') {
        console.log('document_changed', e);

        return func.runtime.ui.refresh_document_changes_for_realtime_update(SESSION_ID, e.data);
      }

      if (data !== 'connected') return;

      RUNTIME_SERVER_WEBSOCKET_CONNECTED = true;

      resolve();
    });
    var callback_done = false;
    RUNTIME_SERVER_WEBSOCKET.on('connect_error', (error) => {
      if (!callback_done) {
        resolve();
        callback_done = true;
        error = true;
      }
    });

    RUNTIME_SERVER_WEBSOCKET.on('disconnect', async () => {
      RUNTIME_SERVER_WEBSOCKET_CONNECTED = false;
      if (_data_system) {
        set_connected(0);
      }
      if (_session.opt.enable_offline) {
        _session.root_element.classList.add('runtime_offline');
        func.utils.alerts.toast(SESSION_ID, 'Switched to off-line mode', 'You have lost connection to the server and are now working offline. Once the connection is restored, all data will be synchronized.', 'warning');
      } else {
        // await func.index.delete_pouch();
        // window.location.href = `https://${_session.domain}/error?error_code=408`;
        _this.func.UI.utils.progressScreen.show(SESSION_ID, 'Your browser has temporarily disconnected from the server. Please wait while we attempt to reconnect.');
      }
    });

    _this.func.runtime.platform.add_window_listener('beforeunload', function (event) {
      var obj = {
        service: 'close_websocket',
        data: { session_id: SESSION_ID },
      };
      RUNTIME_SERVER_WEBSOCKET.emit('message', obj);
    });
  });
};

export const run_plugins_runtime_init = async function (SESSION_ID, app_id, method) {
  var _session = _this.SESSION_OBJ[SESSION_ID];

  const get_path = function (plugin_name, resource) {
    if (_session.worker_type === 'Dev') {
      return `../../plugins/${_session.domain}/${plugin_name}/${resource}`;
    }
    return `https://${_session.domain}/plugins/${plugin_name}/runtime/${resource}?gtp_token=${_session.gtp_token}&app_id=${_session.app_id}`;
  };
  const load_plugin_runtime_module = async function (plugin_name, resource) {
    const local_plugin_resource_url = await _this.func?.utils?.get_local_studio_plugin_resource_url?.(SESSION_ID, plugin_name, resource);
    if (local_plugin_resource_url) {
      return await import(/* @vite-ignore */ local_plugin_resource_url);
    }
    return await import(get_path(plugin_name, resource));
  };

  if (!_this.APP_OBJ[app_id].app_plugins_purchased) return;
  for await (const [key, val] of Object.entries(_this.APP_OBJ[app_id].app_plugins_purchased)) {
    if (!val.installed) continue;
    if (val.manifest?.[method].mjs?.exist && !val.manifest[method].mjs.is_empty) {
      const module = await load_plugin_runtime_module(key, method + '.mjs');
      try {
        await module.default({ SESSION_ID: SESSION_ID });
      } catch (error) {
        console.error(error);
      }
    }
  }
};
