export const run_javascript = async function (SESSION_ID, jobNoP, dsSession, script, evaluate, prog_id, params, evt, $container) {
  const _session = SESSION_OBJ[SESSION_ID];

  const should_delete_job_after_script = function (source) {
    return !String(source || '').includes('func.api.callback');
  };

  const get_script_base_url = function () {
    return `https://${_session.domain}/studio-drive/${APP_OBJ[_session.app_id].app_replicate || _session.app_id}/scripts/${prog_id}/`;
  };

  const get_browser_api_utils = async function () {
    return await func.common.get_module(SESSION_ID, 'xuda-api-library.mjs', {
      func,
      glb,
      SESSION_OBJ,
      SESSION_ID,
      APP_OBJ,
      dsSession,
      jobNoP,
    });
  };

  const normalize_rows_array = function (rows, source) {
    if (!Array.isArray(rows)) {
      return rows;
    }

    Object.defineProperties(rows, {
      rows: { value: rows, enumerable: false, configurable: true },
      code: { value: source?.code, enumerable: false, configurable: true },
      data: { value: source?.data, enumerable: false, configurable: true },
      total_rows: { value: source?.data?.total_rows ?? source?.total_rows ?? rows.length, enumerable: false, configurable: true },
      response: { value: source, enumerable: false, configurable: true },
    });
    return rows;
  };

  const normalize_db_rows_value = function (value) {
    if (Array.isArray(value)) {
      return normalize_rows_array(value, value.response || value);
    }

    if (!value || typeof value !== 'object') {
      return value;
    }

    if (Array.isArray(value?.data?.rows)) {
      return normalize_rows_array(value.data.rows, value);
    }

    if (Array.isArray(value?.rows)) {
      return normalize_rows_array(value.rows, value);
    }

    return value;
  };

  const normalize_javascript_params = function (value) {
    const normalized_value = normalize_db_rows_value(value);
    if (normalized_value !== value) {
      return normalized_value;
    }

    if (!value || typeof value !== 'object' || Array.isArray(value)) {
      return value || {};
    }

    const params_obj = { ...value };
    for (const [key, val] of Object.entries(params_obj)) {
      params_obj[key] = normalize_db_rows_value(val);
    }

    const keys = Object.keys(params_obj);
    if (keys.length === 1 && keys[0] === 'rows' && Array.isArray(params_obj.rows)) {
      return params_obj.rows;
    }

    return params_obj;
  };

  const script_params = normalize_javascript_params(params);

  const get_static_imports = function (source) {
    return Array.from(String(source || '').matchAll(/(^|\n)([ \t]*import\s+(?:(?:[\s\S]*?)\s+from\s+)?['"][^'"]+['"]\s*(?:(?:assert|with)\s*\{[\s\S]*?\})?\s*;?)[ \t]*(?=\n|$)/g));
  };

  const has_module_syntax = function (source) {
    return /^\s*(import|export)\s/m.test(source || '');
  };

  const rewrite_import_specifier = function (import_source) {
    const base_url = get_script_base_url();
    const to_runtime_url = function (specifier) {
      if (/^[a-z][a-z0-9+.-]*:/i.test(specifier)) return specifier;
      if (specifier.startsWith('.') || specifier.startsWith('/')) return new URL(specifier, base_url).href;
      return specifier;
    };

    return import_source
      .replace(/(\bfrom\s*)(['"])([^'"]+)\2/, function (_, prefix, quote, specifier) {
        return `${prefix}${quote}${to_runtime_url(specifier)}${quote}`;
      })
      .replace(/^(\s*import\s*)(['"])([^'"]+)\2/, function (_, prefix, quote, specifier) {
        return `${prefix}${quote}${to_runtime_url(specifier)}${quote}`;
      });
  };

  const build_source_module = function (source) {
    const imports = [];
    const source_without_imports = source.replace(/(^|\n)([ \t]*import\s+(?:(?:[\s\S]*?)\s+from\s+)?['"][^'"]+['"]\s*(?:(?:assert|with)\s*\{[\s\S]*?\})?\s*;?)[ \t]*(?=\n|$)/g, function (match, prefix, import_source) {
      imports.push(rewrite_import_specifier(import_source));
      return prefix;
    });

    if (/^\s*export\s/m.test(source_without_imports)) {
      return `${imports.join('\n')}
${source_without_imports}`;
    }

    return `${imports.join('\n')}
export default async function(params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt) {
${source_without_imports}
}`;
  };

  const create_module_url = function (source) {
    if (typeof window !== 'undefined' && typeof Blob !== 'undefined' && typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function') {
      const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
      return {
        url,
        revoke: function () {
          URL.revokeObjectURL(url);
        },
      };
    }

    return {
      url: `data:text/javascript;charset=utf-8,${encodeURIComponent(source)}`,
      revoke: function () {},
    };
  };

  const run_source_javascript_module_in_browser = async function (source, xu) {
    const module_source = build_source_module(source);
    const module_url = create_module_url(`${module_source}\n//# sourceURL=xuda-source-program-${prog_id || 'inline'}.mjs`);

    try {
      const source_module = await import(module_url.url);
      const code = func.utils.replace_studio_drive_url(SESSION_ID, source_module.default);
      if (typeof code === 'function') {
        return await code(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt);
      }

      return code;
    } finally {
      module_url.revoke();
    }
  };

  const run_source_javascript_program_in_browser = async function (import_error) {
    if (!script) {
      throw import_error;
    }

    if (import_error) {
      await func.utils.report_issue(SESSION_ID, {
        code: 'RUN_MSG_JS_010',
        source: 'run_javascript',
        message: 'Compiled JavaScript module unavailable, running source script',
        type: 'W',
        err: import_error,
        details: {
          prog_id,
        },
      });
    }

    const source = func.utils.replace_studio_drive_url(SESSION_ID, script);
    const xu = await get_browser_api_utils();
    let ret;

    if (has_module_syntax(source) || get_static_imports(source).length) {
      ret = await run_source_javascript_module_in_browser(source, xu);
    } else {
      const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
      const runner = new AsyncFunction('params', 'xu', 'SESSION_ID', 'SESSION_OBJ', 'jobNoP', '$container', 'func', 'glb', 'APP_OBJ', 'evt', source);
      ret = await runner(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ, evt);
    }

    if (should_delete_job_after_script(source)) {
      func.events.delete_job(SESSION_ID, jobNoP);
    }

    return ret;
  };

  const run_native_javascript_program_in_browser = async function () {
    // run in browser

    ///// insert the css
    const path_to_dist = `${get_script_base_url()}dist/`;

    let index_mjs;
    try {
      index_mjs = await import(`${path_to_dist}index.mjs`);
    } catch (import_error) {
      return await run_source_javascript_program_in_browser(import_error);
    }

    if (index_mjs.css) {
      const css_url = `${path_to_dist}index.css`;
      func.utils.load_css_on_demand(css_url);
    }

    const code = func.utils.replace_studio_drive_url(SESSION_ID, index_mjs.default);

    const xu = await get_browser_api_utils();

    const ret = await code(script_params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container);

    if (should_delete_job_after_script(code)) {
      func.events.delete_job(SESSION_ID, jobNoP);
    }
    return ret;
  };

  const run_native_javascript_program_in_server = async function () {
    return new Promise(async (resolve, reject) => {
      const studio_drive_script_folder = path.join(_conf.studio_drive_path, app_id, 'scripts');

      const studio_drive_script_program_folder = path.join(studio_drive_script_folder, prog_id);

      const dist = path.join(studio_drive_script_program_folder, 'dist');
      const index_mjs = path.join(dist, 'index.mjs');

      process.on('uncaughtException', (err) => {
        console.error('Asynchronous error caught.', err);
      });
      const resolve_local = function (e) {
        // resolve({ code: 1, data: e });

        if (should_delete_job_after_script(script)) {
          func.events.delete_job(SESSION_ID, jobNoP);
        }

        resolve(e);
      };

      const reject_local = function (e) {
        // reject({ code: -1, data: e });
        func.events.delete_job(SESSION_ID, jobNoP);
        reject(e);
        // reject(e.message);
      };

      try {
        const script = new VMScript(
          ` 
           const index_mjs = await import(${index_mjs});
          
          try{index_mjs.default(req,params,setup_doc,resolve,reject);}catch(e){ console.error(e); }`,
          { filename: dist, dirname: dist },
        );
        let vm = new NodeVM({
          require: {
            external: true,
          },
          sandbox: {
            resolve: resolve_local,
            reject: reject_local,
          },
          timeout: 60000,
        });
        return await vm.run(script, {
          filename: dist,
          dirname: dist,
        });
      } catch (err) {
        console.error('Failed to execute script.', err);
      }
    });
  };

  if (evaluate) {
    var script_ret = (await func.expression.get(SESSION_ID, script, dsSession, 'javascript', null, null, null, null, null, null, null, () => {}, jobNoP)).result;
  } else {
    if (prog_id) {
      if (typeof IS_API_SERVER !== 'undefined' || typeof IS_DOCKER !== 'undefined' || typeof IS_PROCESS_SERVER !== 'undefined') {
        return await run_native_javascript_program_in_server();
      } else {
        return await run_native_javascript_program_in_browser();
      }
    } else {
      await func.expression.secure_eval(SESSION_ID, 'javascript', func.utils.replace_studio_drive_url(SESSION_ID, script), jobNoP, dsSession, () => {}, evt);
    }
  }

  if (should_delete_job_after_script(script)) {
    func.events.delete_job(SESSION_ID, jobNoP);

    return;
  }
};

export const call_javascript = async function (SESSION_ID, jobNoP, refIdP, dsSession, evaluate, $container) {
  const error_log = async function (msg) {
    let _session = SESSION_OBJ[SESSION_ID];
    func.events.delete_job(SESSION_ID, jobNoP);
    if (typeof IS_API_SERVER !== 'undefined') {
      return __.rpi.write_log(_session.app_id, 'error', 'api', msg);
    }

    if (typeof IS_DOCKER !== 'undefined') {
      return __.rpi.write_log(_session.app_id, 'error', 'worker', msg);
    }

    // client
    await func.common.db(SESSION_ID, 'write_log', {
      msg,
      log_type: 'error',
      source: 'runtime',
    });
  };

  let view_ret = await func.utils.VIEWS_OBJ.get(SESSION_ID, refIdP.prog);

  if (!view_ret) {
    return error_log('program not found');
  }

  if (!view_ret?.scriptData?.value) {
    return error_log('script is empty');
  }
  return await run_javascript(SESSION_ID, jobNoP, dsSession, view_ret.scriptData.value, evaluate, refIdP.prog, refIdP?.parameters, null, $container);
};
