export class XudaModule {
  constructor(e) {
    this.func = e.func;
    this.glb = e.glb;
    this.SESSION_OBJ = e.SESSION_OBJ;
    this.SESSION_ID = e.SESSION_ID;
    this.APP_OBJ = e.APP_OBJ;
    this.dsSession = e.dsSession;
    this.job_id = e.job_id;
    this._session = this.SESSION_OBJ[this.SESSION_ID];

    return this;
  }

  async get_field_value(field_id) {
    if (!field_id) {
      console.error('xu.getFieldValue: field_id cannot be empty');
      return false;
    }
    let ret_get_value = await this.func.datasource.get_value(this.SESSION_ID, field_id, this.dsSession);

    if (!ret_get_value.found) {
      console.error(`xu.getFieldValue: ${field_id} field not found`);
      return false;
    }

    return ret_get_value.ret.value;
  }

  async set_field_value(field_id, value, avoid_refresh) {
    if (!field_id) {
      console.error('xu.setFieldValue: field_id cannot be empty');
      return false;
    }

    if (typeof value === 'undefined') {
      console.error(`xu.setFieldValue: ${field_id} - value cannot be undefined`);
      return false;
    }

    let ret_get_value = await this.func.datasource.get_value(this.SESSION_ID, field_id, this.dsSession);

    if (!ret_get_value.found) {
      console.error(`xu.setFieldValue: ${field_id} field not found`);
      return false;
    }

    let _ds = this._session.DS_GLB[ret_get_value.dsSessionP];

    const datasource_changes = {
      [_ds.dsSession]: {
        [ret_get_value.currentRecordId]: { [field_id]: value },
      },
    };
    return await this.func.datasource.update(this.SESSION_ID, datasource_changes, null, avoid_refresh);
  }

  async invoke_event(event_id) {
    if (!event_id) {
      console.error('xu.invokeEvent: event_id cannot be empty');
      return false;
    }

    let ds;

    for await (const [key, val] of Object.entries(Object.assign([], this._session.DS_GLB).reverse())) {
      let ds_val = val;
      let ds_key = ds_val.dsSession;
      const _view_obj = await this.func.utils.VIEWS_OBJ.get(this.SESSION_ID, ds_val.prog_id);
      if (_.isEmpty(_view_obj.progEvents)) continue;
      if (ds) break;

      for await (const [key, val] of Object.entries(_view_obj.progEvents)) {
        if (val?.data?.type === 'user_defined' && val.data.event_name === event_id) {
          ds = ds_key;
          break;
        }
      }
    }

    if (typeof ds === 'undefined') {
      this.reject('xu.invokeEvent error', `${event_id} event_id not found`, this.job_id);
      return false;
    }

    return this.func.events.validate(this.SESSION_ID, 'user_defined', ds, event_id);
  }

  async read_drive(filename, cb) {
    try {
      const response = await fetch(`https://${this._session.domain}/workspace-drive/` + filename + '&' + this._session.gtp_token);
      if (cb) {
        return cb(await response.json());
      }

      return await response.json();
    } catch (e) {
      console.error(e);
      this.reject('xu.readDrive error', e, this.job_id);
    }
  }

  async write_drive(stream, filename, make_public, cb) {
    var data = {
      app_id: this._session.app_id,
      gtp_token: this._session.gtp_token,
      app_token: this._session.app_token,

      req_from_api: 'true',
      public: make_public ? 'true' : '',
    };
    const form = new FormData();
    for (const [key, val] of Object.entries(data)) {
      form.append(key, val);
    }

    form.append('file', stream, filename);
    try {
      const response = await fetch(this.func.common.get_url(this.SESSION_ID, 'rpi', 'runtime_upload_file'), {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(form),
      });
      if (cb) {
        return cb(await response.json());
      }

      return await response.json();
    } catch (e) {
      console.error(e);
      this.reject('xu.writeDrive error', e, this.job_id);
    }
  }

  resolve(cb, job_id) {
    if (job_id) {
      this.func.events.delete_job(this.SESSION_ID, job_id);
    }
    if (cb) {
      cb();
    }
  }

  reject(msg, details, job_id) {
    if (job_id) {
      this.func.events.delete_job(this.SESSION_ID, job_id);
    }
    this.log('E', msg, details);
  }

  log(type, msg, details) {
    this.func.utils.debug_report(this.SESSION_ID, msg, details, type);
  }

  alert(type, display, msg, details) {
    this.func.utils.alerts.execute(
      this.SESSION_ID,
      type, // error, warn, info, log
      display, // console, modal, toast, browser
      details,
      msg,
    );
  }

  async call_project_api(prog_id, params, cb) {
    const _prog_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, prog_id);

    if (_prog_obj?.properties?.menuType !== 'api') {
      return this.reject('xu.callApiProgram error', `${prog_id} is not an API program`, this.job_id);
    }
    // also call internal API if call made from the server itself
    if (this._session.engine_mode === 'live_preview' || typeof IS_PROCESS_SERVER !== 'undefined') {
      const ret = await this.func.datasource.prepare(this.SESSION_ID, prog_id, null, 0, null, null, null, null, null, null, 'api', null, null, null, params);
      try {
        const _ds = this._session.DS_GLB[ret.dsSessionP];

        if (typeof _ds.api_rendered_output === 'undefined' || typeof _ds.tree_obj.apiOutput === 'undefined' || !_ds.tree_obj.apiOutput) {
          throw new Error('undefined api_rendered_output/apiOutput');
        }

        if (_ds.tree_obj.apiOutput === 'json') {
          try {
            return JSON5.parse(_ds.api_rendered_output);
          } catch (err) {
            console.error(err);
            return {};
          }
        }
        return _ds.api_rendered_output;
      } catch (e) {
        this.reject('xu.call_project_api error', e.message || e, this.job_id);
      }
    }

    // miniapp
    if (['miniapp'].includes(this._session.engine_mode)) {
      try {
        const response = await fetch(`https://${this._session.domain}/api`, {
          method: 'POST',
          headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ ...params, ...{ prog_id } }),
        });
        if (cb) {
          return cb(await response.json());
        }

        return await response.json();
      } catch (e) {
        console.error(e);
        this.reject('xu.get_table_data error', e, this.job_id);
      }
    }

    // deployments
    try {
      const response = await fetch(`https://${this._session.domain}/execute_api_program`, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ ...params, ...{ prog_id } }),
      });
      if (cb) {
        return cb(await response.json());
      }

      return await response.json();
    } catch (e) {
      console.error(e);
      this.reject('xu.get_table_data error', e, this.job_id);
    }
  }

  async call_system_api(api_method, payload = {}, cb) {
    try {
      let body = {
        ...{
          app_id: this._session.app_id,
          app_id_query: this._session.app_id,
          app_token: this._session.app_token,
        },
        ...payload,
      };

      const response = await fetch(`https://${this._session.domain}/cpi/${api_method}`, {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      });

      // if (!response.ok) {
      //   return { code: response.status, data: response.statusText };
      // }

      const json = await response.json();
      if (cb) {
        return cb(json);
      }

      return json;
    } catch (e) {
      console.error(e);
      this.reject('xu.call_system_api error', e, this.job_id);
    }
  }

  async call_external_api(method, url, payload = {}, cb) {
    try {
      let json = {
        ...{
          app_id: this._session.app_id,
          app_id_query: this._session.app_id,
          app_token: this._session.app_token,
        },
        ...payload,
      };

      const response = await fetch(`https://${url}`, {
        method,
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(json),
      });
      if (cb) {
        return cb(await response.json());
      }

      return await response.json();
    } catch (e) {
      console.error(e);
      this.reject('xu.call_external_api error', e, this.job_id);
    }
  }

  async call_javascript(prog_id, params = {}, evaluate = false) {
    try {
      const module = await this.func.common.get_module(this.SESSION_ID, 'xuda-event-javascript-module.mjs');

      const result = await module.call_javascript(this.SESSION_ID, this.job_id, { prog: prog_id, parameters: params }, this.dsSession, evaluate);

      return result;
    } catch (e) {
      console.error(e);
      this.reject('xu.call_javascript error', e, this.job_id);
    }
  }

  async dbs_create(table_id, data, cb) {
    const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id);

    if (_table_obj?.properties?.menuType !== 'table') {
      return this.reject('xu.dbsCreate error', `${table_id} is not a table`, this.job_id);
    }

    try {
      const ret = await this.func.common.db(this.SESSION_ID, 'dbs_create', { table_id, table_data: data });
      if (cb) {
        return cb(ret);
      }
      return ret;
    } catch (e) {
      console.error(e);
      this.reject('xu.dbs_create error', e, this.job_id);
    }
  }
  async dbs_read(table_id, selector = {}, fields = [], sort, limit = 999, skip, cb) {
    const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id);

    if (_table_obj?.properties?.menuType !== 'table') {
      return this.reject('xu.dbsRead error', `${table_id} is not a table`, this.job_id);
    }
    let data = { fields, table_id, dataSourceFilterModelType: 'query', filterModelMongo: selector, limit, skip, sort };

    try {
      const ret = await this.func.common.db(this.SESSION_ID, 'dbs_read', data);
      if (cb) {
        return cb(ret);
      }
      return ret;
    } catch (e) {
      console.error(e);
      this.reject('xu.dbs_read error', e, this.job_id);
    }
  }
  async dbs_update(table_id, row_id, data, cb) {
    const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id);

    if (_table_obj?.properties?.menuType !== 'table') {
      return this.reject('xu.dbsUpdate error', `${table_id} is not a table`, this.job_id);
    }

    if (!row_id) {
      return this.reject('xu.dbsDelete error', `row_id is a mandatory field`, this.job_id);
    }

    try {
      const ret = await this.func.common.db(this.SESSION_ID, 'dbs_update', { table_id, row_id, table_data: data });
      if (cb) {
        return cb(ret);
      }
      return ret;
    } catch (e) {
      console.error(e);
      this.reject('xu.dbs_update error', e, this.job_id);
    }
  }
  async dbs_delete(table_id, row_id, cb) {
    const _table_obj = await this.func.utils.DOCS_OBJ.get(this.SESSION_ID, table_id);

    if (_table_obj?.properties?.menuType !== 'table') {
      return this.reject('xu.dbsDelete error', `${table_id} is not a table`, this.job_id);
    }
    if (!row_id) {
      return this.reject('xu.dbsDelete error', `row_id is a mandatory field`, this.job_id);
    }

    try {
      const ret = await this.func.common.db(this.SESSION_ID, 'dbs_delete', { table_id, ids: [row_id] });
      if (cb) {
        return cb(ret);
      }
      return ret;
    } catch (e) {
      console.error(e);
      this.reject('xu.dbs_delete error', e, this.job_id);
    }
  }
}
