getForm = async (
  table,
  viewname,
  columns,
  layout0,
  id,
  req,
  isRemote
) => {
  const fields = table.getFields();
  const state = getState();
  const tfields = (columns || [])
    .map((column) => {
      if (column.type === "Field") {
        const f0 = fields.find((fld) => fld.name === column.field_name);

        if (f0) {
          const f = new Field(f0);
          f.fieldview = column.fieldview;
          if (f.type === "Key") {
            if (state.keyFieldviews[column.fieldview])
              f.fieldviewObj = state.keyFieldviews[column.fieldview];
            f.input_type =
              !f.fieldview ||
              !f.fieldviewObj ||
              (f.fieldview === "select" && !f.fieldviewObj)
                ? "select"
                : "fromtype";
          }
          if (f.type === "File") {
            const fvNm = column.fieldview || "upload";
            if (state.fileviews[fvNm]) f.fieldviewObj = state.fileviews[fvNm];
            f.input_type =
              !f.fieldview || !f.fieldviewObj ? "file" : "fromtype";
          }
          if (f.calculated) {
            const qs = objToQueryString(column.configuration);
            f.sourceURL = `/field/show-calculated/${table.name}/${f.name}/${f.fieldview}?${qs}`;
          }
          f.attributes = { ...column.configuration, ...f.attributes };
          if (
            typeof column.block !== "undefined" &&
            typeof f.attributes.block === "undefined"
          )
            f.attributes.block = column.block;
          return f;
        } else if (table.name === "users" && column.field_name === "password") {
          return new Field({
            name: "password",
            fieldview: column.fieldview,
            type: "String",
          });
        } else if (
          table.name === "users" &&
          column.field_name === "passwordRepeat"
        ) {
          return new Field({
            name: "passwordRepeat",
            fieldview: column.fieldview,
            type: "String",
          });
        } else if (table.name === "users" && column.field_name === "remember") {
          return new Field({
            name: "remember",
            fieldview: column.fieldview,
            type: "Bool",
          });
        }
      }
    })
    .filter((tf) => !!tf);
  const path = isWeb(req) ? req.baseUrl + req.path : "";
  const qs = objectToQueryString(req.query);
  let action = `/view/${viewname}${qs ? "?" + qs : ""}`;
  if (path && path.startsWith("/auth/")) action = path;
  const layout = structuredClone(layout0);
  traverseSync(layout, {
    container(segment) {
      if (segment.showIfFormula) {
        segment.showIfFormulaInputs = segment.showIfFormula;
        const fvs = [...freeVariables(segment.showIfFormula)];
        const jfFvs = fvs.filter(
          (fv) => fv.includes(".") && !fv.startsWith("user.")
        );
        if (jfFvs.length)
          segment.showIfFormulaJoinFields = jfFvs
            .map((jf) => {
              const [ref, target] = jf.split(".");
              const refField = table.getField(ref);
              if (!refField || !refField?.reftable_name) return null;
              return {
                ref: ref.replace("?", ""),
                target,
                refTable: refField.reftable_name,
              };
            })
            .filter(Boolean);
      }
    },
  });
  if (!req.layout_hints)
    req.layout_hints = state.getLayout(req.user).hints || {};
  let isMobileLogin = false;
  if (isRemote) {
    const loginForm = getState().getConfig("login_form", "");
    if (loginForm && viewname === loginForm) isMobileLogin = true;
  }
  let submitActionJS = undefined;
  const submitActionCol = columns.find((c) => c.is_submit_action);

  if (submitActionCol) {
    submitActionJS = `event.preventDefault();view_post(this, 'run_action', {rndid:'${submitActionCol.rndid}', ...get_form_record(this) })`;
    if (layout.above) layout.above.push(`<input type="submit" hidden />`);
    //TODO what if there is no above, e.g. all in card or container
  }

  const form = new Form({
    action: action,
    onSubmit:
      isRemote || isOfflineMode()
        ? `javascript:${
            !isMobileLogin
              ? `formSubmit(this, '/view/', '${viewname}')`
              : "loginFormSubmit(this)"
          }`
        : submitActionJS,
    viewname: viewname,
    fields: tfields,
    layout,
    req,
  });
  if (id) form.hidden("id");
  return form;
}
@param {object} table
@param {string} viewname
@param {object[]} [columns]
@param {object} layout0
@param {boolean|null} id
@param {object} req
@param {boolean} isRemote
@returns {Promise<Form>}