const _this = {};
// 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 = e.IS_DOCKER;
  _this.IS_API_SERVER = e.IS_API_SERVER;
  _this.IS_PROCESS_SERVER = e.IS_PROCESS_SERVER;
};

export const project_loader = async function (SESSION_ID, app_id, prog_id) {
  try {
    var _session = _this.SESSION_OBJ[SESSION_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 = {};

    // call from xuda real-preview app
    if (typeof XUDA_BUILD_INFO !== 'undefined') {
      _session.build_info = XUDA_BUILD_INFO;
    } 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) {
            return console.error(ret_build_info);
          }
          // save to pouch

          try {
            const { _id, _rev } = await db.get(`cache_build_info`);
            await db.remove({ _id, _rev });
          } catch (error) {}
          try {
            await db.put({
              _id: `cache_build_info`,
              build_info: ret_build_info,
              docType: 'cache_build_info',
            });
          } catch (error) {}
        } else {
          // off-line get from pouch
          try {
            ret_build_info = (await db.get(`cache_build_info`)).build_info;
          } catch (error) {
            return console.error('cache_build_info error');
          }
        }
        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;
    // }

    await get_rt_info(SESSION_ID, app_id, last_changed_ts);

    insert_custom_prop(SESSION_ID);

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

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

      module.live_preview_loader(SESSION_ID);
      return;
    }

    if (_this.APP_OBJ[app_id]?.is_deployment) {
      try {
        await init_runtime_websocket(SESSION_ID, app_id);
      } catch (error) {
        throw error;
      }
    }
    const module = await _this.func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);

    if (app_id !== 'unknown') {
      await module.load_objects_cache(SESSION_ID);
    }
    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: $.cookie('uid'),
    }),
  });
  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];

  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 = _.clone(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;

        // 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();

        $('.loader_logo').css('background-image', `url( ${rt_info_obj.app_pic})`).hide().fadeIn();
      },
      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);
    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);

          var doc = {
            _id: `cache_rt_info`,
            data: json,
            docType: 'cache_app',
          };
          db.put(doc);
        })
        .catch((err) => {
          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).append(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).prepend(_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://${location.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).hasClass('runtime_offline')) {
          $(_session.root_element).removeClass('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 $('body').trigger('get_doc_obj_from_build_response_' + data._id, {
            data,
          });
        }
        if (e.service === 'heartbeat') {
          return $('body').trigger('heartbeat_response', {
            data,
          });
        }
        return $('body').trigger('get_ws_data_response_' + e.websocket_queue_num, {
          data,
          e,
        });
      }
      if (e.source === 'deployment_server') {
        console.log('document_changed', e);

        return func.UI.screen.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).addClass('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.');
      }
    });

    window.addEventListener('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}`;
  };

  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 import(get_path(key, method + '.mjs'));
      try {
        await module.default({ SESSION_ID: SESSION_ID });
      } catch (error) {
        console.error(error);
      }
    }
  }
};
