render = async ({
  table,
  fields,
  viewname,
  columns,
  layout,
  row,
  req,
  state,
  res,
  auto_save,
  destination_type,
  isRemote,
  getRowQuery,
  optionsQuery,
  split_paste,
  mobileReferrer,
  confirm_leave,
  delete_unchanged_auto_create,
  isPreview,
  auto_created_row,
  hiddenLoginDest,
}) => {
  const form = await getForm(
    table,
    viewname,
    columns,
    layout,
    state.id,
    req,
    isRemote
  );
  if (split_paste) form.splitPaste = true;

  if (row) {
    form.values = row;
    const file_fields = form.fields.filter((f) => f.type === "File");
    if (isWeb(req)) {
      for (const field of file_fields) {
        if (field.fieldviewObj?.valueIsFilename && row[field.name]) {
          const file = await File.findOne({ id: row[field.name] });
          if (file.id) form.values[field.name] = file.filename;
        }
        if (field.fieldviewObj?.editContent && row[field.name]) {
          const file = await File.findOne(row[field.name]);
          if (file && file.min_role_read >= (req.user?.role_id || 100))
            form.values[`_content_${field.name}`] = await file.get_contents();
        }
      }
    }
    form.hidden(table.pk_name);
    const user_id = req.user ? req.user.id : null;
    const owner_field = await table.owner_fieldname();
    if (table.ownership_formula && user_id) {
      const freeVars = freeVariables(table.ownership_formula);
      //need to fetch with joinfields
      if (freeVars.size > 0) {
        const joinFields = {};
        add_free_variables_to_joinfields(freeVars, joinFields, fields);
        const row_joined = await table.getJoinedRow({
          where: { [table.pk_name]: row[table.pk_name] },
          forPublic: !req.user,
          forUser: req.user,
          joinFields,
        });
        form.isOwner = await table.is_owner(req.user, row_joined);
      } else form.isOwner = await table.is_owner(req.user, row);
    } else
      form.isOwner = owner_field && user_id && row[owner_field] === user_id;
  } else {
    form.isOwner = true;
  }

  if (destination_type === "Back to referer") {
    form.hidden("_referer");
    form.values._referer = mobileReferrer
      ? mobileReferrer
      : req.headers?.referer;
  }
  if (hiddenLoginDest && req.query.dest) {
    form.hidden("dest");
    if (!req.query.dest.includes(":/") && !req.query.dest.includes("//"))
      form.values.dest = req.query.dest;
  }

  Object.entries(state).forEach(([k, v]) => {
    const field = form.fields.find((f) => f.name === k);
    if (field && ((field.type && field.type.read) || field.is_fkey)) {
      form.values[k] = field.type.read ? field.type.read(v) : v;
    } else {
      const tbl_field = fields.find((f) => f.name === k);
      if (tbl_field && !field) {
        form.fields.push(new Field({ name: k, input_type: "hidden" }));
        form.values[k] = tbl_field.type.read ? tbl_field.type.read(v) : v;
      }
    }
  });

  // add row values not in columns as hidden if needed for join fields
  if (row) {
    const need_join_fields = new Set(
      columns
        .filter((c) => c.type === "JoinField")
        .map((c) => c.join_field.split(".")[0])
    );
    const colFields = new Set(
      columns.filter((c) => c.type === "Field").map((c) => c.field_name)
    );
    const formFields = new Set(form.fields.map((f) => f.name));
    fields.forEach((f) => {
      if (
        !colFields.has(f.name) &&
        !formFields.has(f.name) &&
        typeof row[f.name] !== "undefined" &&
        need_join_fields.has(f.name)
      )
        form.fields.push(new Field({ name: f.name, input_type: "hidden" }));
    });
  }
  // no autosave if new and save button exists
  // !row && hasSave
  let hasSave = false;
  traverseSync(layout, {
    action({ action_name }) {
      if (action_name === "Save" || action_name === "SubmitWithAjax") {
        hasSave = true;
      }
    },
  });
  const actually_auto_save = auto_save && !(!row && hasSave);
  if (actually_auto_save)
    form.onChange = `saveAndContinueDelayed(this, ${
      !isWeb(req) ? `'${form.action}'` : undefined
    }, event);`;
  let reloadAfterCloseInModalScript =
    actually_auto_save && req.xhr
      ? script(
          domReady(`
    $("#scmodal").on("hidden.bs.modal", function (e) {
     const on_close_reload_view = $("#scmodal").attr(
        "data-on-close-reload-view"
      );
      if(on_close_reload_view)
        reload_embedded_view(on_close_reload_view)
      else
        setTimeout(()=>location.reload(),0);
    });`)
        )
      : "";

  let confirmLeaveScript = "";
  if (confirm_leave) {
    //add to onchange
    if (!form.onChange) form.onChange = "";
    form.onChange += "this.setAttribute('data-unsaved-changes','true');";
    if (!form.onSubmit) form.onSubmit = "";
    form.onSubmit += "this.removeAttribute('data-unsaved-changes')";

    //beforeunload script
    confirmLeaveScript = script(
      `((curScript)=>{window.addEventListener("beforeunload", (e) => check_unsaved_form(e, curScript));})(document.currentScript)`
    );
  }
  let deleteUnchangedScript = "";
  if (auto_created_row && delete_unchanged_auto_create && !isPreview) {
    if (hasSave) {
      if (!form.onSubmit) form.onSubmit = "";
      form.onSubmit += "this.setAttribute('data-form-changed','true');";
    } else {
      if (!form.onChange) form.onChange = "";
      form.onChange += "this.setAttribute('data-form-changed','true');";
    }
    deleteUnchangedScript = script(
      `((curScript)=>{window.addEventListener("beforeunload", () => check_delete_unsaved("${table.name}", curScript));})(document.currentScript)`
    );
  }

  const formId = isTest()
    ? "test-form-id"
    : `form${Math.floor(Math.random() * 16777215).toString(16)}`;
  const identicalFieldsScript = script(
    domReady(
      `const editForm = document.getElementById('${formId}'); if (editForm) editForm.addEventListener("change", handle_identical_fields, true);`
    )
  );

  if (actually_auto_save) {
    for (const field of form.fields) {
      field.in_auto_save = true;
    }
  }
  await form.fill_fkey_options(false, optionsQuery, req.user);
  await transformForm({
    form,
    table,
    req,
    row,
    res,
    getRowQuery,
    viewname,
    optionsQuery,
  });
  form.id = formId;
  return (
    renderForm(form, !isRemote && req.csrfToken ? req.csrfToken() : false) +
    reloadAfterCloseInModalScript +
    confirmLeaveScript +
    deleteUnchangedScript +
    identicalFieldsScript
  );
}
@param {object} opts
@param {Table} opts.table
@param {Fields[]} opts.fields
@param {string} opts.viewname
@param {object[]} opts.columns
@param {Layout} opts.layout
@param {object} opts.row
@param {object} opts.req
@param {object} opts.state
@param {object} opts.res
@returns {Promise<Form>}