get_viewable_fields = (
  viewname,
  statehash,
  table,
  fields,
  columns,
  isShow,
  req,
  __,
  state = {},
  srcViewName,
  viewResults,
  in_row_click
) => {
  const dropdown_actions = [];
  const checkShowIf = (tFieldGenF) => (column, index) => {
    const tfield = tFieldGenF(column, index);
    if (column.showif) {
      const oldKeyF = tfield.key;
      if (typeof oldKeyF !== "function") return tfield;
      const newKeyF = (r) => {
        if (
          eval_expression(column.showif, r, req.user, "Column show if formula")
        )
          return oldKeyF(r);
        else return "";
      };
      tfield.key = newKeyF;
    }
    return tfield;
  };
  const tfields = flapMapish(
    columns,
    checkShowIf((column, index) => {
      const role = req.user ? req.user.role_id : 100;
      const user_id = req.user ? req.user.id : null;
      const setWidth = column.col_width
        ? { width: `${column.col_width}${column.col_width_units}` }
        : {};
      setWidth.align =
        !column.alignment || column.alignment === "Default"
          ? undefined
          : column.alignment.toLowerCase();
      if (column.type === "FormulaValue") {
        return {
          ...setWidth,
          label: column.header_label ? text(__(column.header_label)) : "",
          key: (r) =>
            text(
              eval_expression(
                column.formula,
                r,
                req.user,
                "Formula value column"
              )
            ),
        };
      } else if (column.type === "Text") {
        return {
          ...setWidth,
          label: column.header_label ? text(__(column.header_label)) : "",
          key: (r) =>
            column.interpolator
              ? column.interpolator(r)
              : text(column.contents),
        };
      } else if (column.type === "DropdownMenu") {
        return {
          ...setWidth,
          label: column.header_label ? text(__(column.header_label)) : "",
          key: (r) =>
            div(
              { class: "dropdown" },
              button(
                {
                  class:
                    column.action_style === "btn-link"
                      ? "btn btn-link"
                      : `btn ${column.action_style || "btn-primary"} ${
                          column.action_size || ""
                        } dropdown-toggle`,
                  "data-boundary": "viewport",
                  type: "button",
                  id: `actiondd${r.id}_${index}`, //TODO need unique
                  "data-bs-toggle": "dropdown",
                  "aria-haspopup": "true",
                  "aria-expanded": "false",
                  onclick: in_row_click ? "event.stopPropagation()" : undefined,
                  style:
                    column.action_style === "btn-custom-color"
                      ? `background-color: ${
                          column.action_bgcol || "#000000"
                        };border-color: ${
                          column.action_bordercol || "#000000"
                        }; color: ${column.action_textcol || "#000000"}`
                      : null,
                },
                column.label || req.__("Action")
              ),
              div(
                {
                  class: [
                    "dropdown-menu",
                    column.menu_direction === "end" && "dropdown-menu-end",
                  ],
                  "aria-labelledby": `actiondd${r.id}_${index}`,
                },
                column.dropdown_columns.map((acol) =>
                  div({ class: "dropdown-item" }, acol.key(r))
                )
              )
            ),
        };
      } else if (column.type === "Action") {
        if (column.minRole && column.minRole != 100) {
          const minRole = +column.minRole;
          const userRole = req?.user?.role_id || 100;
          if (minRole < userRole) return false;
        }
        const action_col = {
          ...setWidth,
          label: column.header_label ? text(__(column.header_label)) : "",
          key: (r) => {
            if (action_requires_write(column.action_name)) {
              if (table.min_role_write < role && !table.is_owner(req.user, r))
                return "";
            }
            const url = action_url(
              viewname,
              table,
              column.action_name,
              r,
              column.rndid || column.action_name,
              column.rndid ? "rndid" : "action_name",
              column.confirm,
              index
            );
            const label = column.action_label_formula
              ? eval_expression(
                  column.action_label,
                  r,
                  req.user,
                  "Action label formula"
                )
              : __(column.action_label) || __(column.action_name);
            const icon = column.action_icon || column.icon || undefined;
            if (url.javascript)
              return a(
                {
                  href: "javascript:void(0)",
                  class: [
                    column.in_dropdown && "dropdown-item",
                    column.action_style !== "btn-link" &&
                      `btn ${column.action_style || "btn-primary"} ${
                        column.action_size || ""
                      }`,
                  ],
                  onclick:
                    url.javascript +
                    (column.spinner ? ";spin_action_link(this)" : "") +
                    (in_row_click ? ";event.stopPropagation()" : ""),
                },
                !!icon &&
                  icon !== "empty" &&
                  i({ class: icon }) + (label === " " ? "" : "&nbsp;"),
                label
              );
            else
              return post_btn(url, label, req.csrfToken(), {
                small: true,
                ajax: true,
                icon,
                reload_on_done: true,
                confirm: column.confirm,
                spinner: column.spinner,
                btnClass: column.in_dropdown
                  ? "dropdown-item"
                  : column.action_style || "btn-primary",
                req,
              });
          },
        };
        if (column.in_dropdown) {
          //legacy
          dropdown_actions.push(action_col);
          return false;
        } else return action_col;
      } else if (column.type === "View") {
        return {
          label: column.header_label ? __(column.header_label) : "",
          key: (r) => viewResults[column.view + column.relation]?.(r),
        };
      } else if (column.type === "ViewLink") {
        if (!column.view) return;
        const r = view_linker(
          column,
          fields,
          __,
          isWeb(req),
          req.user,
          "",
          state,
          req,
          srcViewName,
          undefined,
          in_row_click
        );
        if (column.header_label) r.label = text(__(column.header_label));
        Object.assign(r, setWidth);
        if (column.in_dropdown) {
          dropdown_actions.push(r);
          return false;
        } else return r;
      } else if (column.type === "Link") {
        const r = make_link(column, fields, __, in_row_click);
        if (column.header_label) r.label = text(__(column.header_label));
        Object.assign(r, setWidth);
        if (column.in_dropdown) {
          dropdown_actions.push(r);
          return false;
        } else return r;
      } else if (column.type === "JoinField") {
        //console.log(column);
        let fvrun;
        const fieldview = column.join_fieldview || column.fieldview;
        let refNm, targetNm, through, key, type;
        const keypath = column.join_field.split(".");
        if (column.join_field.includes("->")) {
          const [relation, target] = column.join_field.split("->");
          const [ontable, ref] = relation.split(".");
          targetNm = target;
          refNm = ref;
          key = validSqlId(
            column.targetNm ||
              `${ref}_${ontable.replaceAll(" ", "").toLowerCase()}_${target}`
          );
        } else {
          refNm = keypath[0];
          targetNm = keypath[keypath.length - 1];
          key = keypath.join("_");
        }
        const field = table.getField(column.join_field);

        if (column.field_type) type = getState().types[column.field_type];
        else {
          if (field && field.type === "File") column.field_type = "File";
          else if (field?.type.name && field?.type?.fieldviews[fieldview]) {
            column.field_type = field.type.name;
            type = getState().types[column.field_type];
          }
        }
        if (fieldview && type?.fieldviews?.[fieldview]?.expandColumns) {
          return type.fieldviews[fieldview].expandColumns(
            field,
            {
              ...field.attributes,
              ...column,
            },
            column
          );
        }
        let gofv =
          fieldview && type && type.fieldviews && type.fieldviews[fieldview]
            ? (row) =>
                type.fieldviews[fieldview].run(row[key], req, {
                  row,
                  ...(field?.attributes || {}),
                  ...column,
                  ...(column?.configuration || {}),
                })
            : null;
        if (!gofv && column.field_type === "File") {
          gofv = (row) =>
            row[key]
              ? getState().fileviews[fieldview].run(row[key], "", {
                  row,
                  ...column,
                  ...(column?.configuration || {}),
                })
              : "";
        }
        fvrun = {
          ...setWidth,
          label: headerLabelForName(
            column.header_label
              ? text(__(column.header_label))
              : text(targetNm),
            key,
            req,
            __,
            statehash
          ),
          row_key: key,
          key: gofv ? gofv : (row) => text(row[key]),
          sortlink: sortlinkForName(key, req, viewname, statehash),
        };
        if (column.click_to_edit) {
          const reffield = fields.find((f) => f.name === refNm);

          const oldkey =
            typeof fvrun.key === "function" ? fvrun.key : (r) => r[fvrun.key];
          const newkey = (row) =>
            div(
              {
                "data-inline-edit-fielddata": encodeURIComponent(
                  JSON.stringify({
                    field_name: keypath[0],
                    table_name: table.name,
                    pk: row[table.pk_name],
                    fieldview,
                    configuration: column?.configuration,
                    join_field: keypath[keypath.length - 1],
                  })
                ),
                "data-inline-edit-ajax": "true",
                "data-inline-edit-dest-url": `/api/${table.name}/${
                  row[table.pk_name]
                }`,
              },
              span({ class: "current" }, oldkey(row)),
              i({ class: "editicon fas fa-edit ms-1" })
            );
          fvrun.key = newkey;
        }
        return fvrun;
      } else if (column.type === "Aggregation") {
        let table, fld, through;
        if (column.agg_relation.includes("->")) {
          let restpath;
          [through, restpath] = column.agg_relation.split("->");
          [table, fld] = restpath.split(".");
        } else {
          [table, fld] = column.agg_relation.split(".");
        }
        let targetNm =
          column.targetNm ||
          db.sqlsanitize(
            (
              column.stat.replace(" ", "") +
                "_" +
                table +
                "_" +
                fld +
                "_" +
                column.agg_field.split("@")[0] +
                "_" +
                column.aggwhere || ""
            ).toLowerCase()
          );
        if (targetNm.length > 58) {
          targetNm = targetNm
            .split("")
            .filter((c, i) => i % 2 == 0)
            .join("");
        }
        let showValue = (value) => {
          if (value === true || value === false)
            return bool.fieldviews.show.run(value);
          if (value instanceof Date) return date.fieldviews.show.run(value);
          return value?.toString ? value.toString() : value;
        };
        if (column.agg_fieldview && column.agg_field?.includes("@")) {
          const tname = column.agg_field.split("@")[1];
          const type = getState().types[tname];
          if (type?.fieldviews[column.agg_fieldview])
            showValue = (x) =>
              type.fieldviews[column.agg_fieldview].run(x, req, column);
        } else if (column.agg_fieldview) {
          const aggField = Table.findOne(table)?.getField?.(column.agg_field);
          const outcomeType =
            column.stat === "Percent true" || column.stat === "Percent false"
              ? "Float"
              : column.stat === "Count" || column.stat === "CountUnique"
                ? "Integer"
                : aggField?.type?.name;
          const type = getState().types[outcomeType];
          if (type?.fieldviews[column.agg_fieldview])
            showValue = (x) =>
              type.fieldviews[column.agg_fieldview].run(type.read(x), req, {
                ...column,
                ...(column?.configuration || {}),
              });
        }

        let key = (r) => {
          const value = r[targetNm];
          return showValue(value);
        };
        if (column.stat.toLowerCase() === "array_agg")
          key = (r) =>
            Array.isArray(r[targetNm])
              ? r[targetNm].map((v) => showValue(v)).join(", ")
              : "";
        return {
          ...setWidth,
          label: headerLabelForName(
            column.header_label
              ? text(column.header_label)
              : text(column.stat + " " + table),

            targetNm,
            req,
            __,
            statehash
          ),
          key,
          sortlink: sortlinkForName(targetNm, req, viewname, statehash),
        };
      } else if (column.type === "Field") {
        //console.log(column);
        let f = fields.find((fld) => fld.name === column.field_name);
        let f_with_val = f;
        if (f && f.attributes && f.attributes.localized_by) {
          const locale = req?.getLocale?.();
          const localized_fld_nm = f.attributes.localized_by[locale];
          f_with_val = fields.find((fld) => fld.name === localized_fld_nm) || f;
        }
        const isNum = f && f.type && f.type.name === "Integer";
        if (isNum && !setWidth.align) setWidth.align = "right";
        let fvrun;
        if (
          column.fieldview &&
          f?.type?.fieldviews?.[column.fieldview]?.expandColumns
        ) {
          fvrun = f.type.fieldviews[column.fieldview].expandColumns(
            f,
            {
              ...f.attributes,
              ...column.configuration,
            },
            column
          );
        } else
          fvrun = f && {
            ...setWidth,
            label: headerLabelForName(
              column.header_label
                ? text(__(column.header_label))
                : text(__(f.label)),
              f.name,
              req,
              __,
              statehash
            ),
            row_key: f_with_val.name,
            key:
              column.fieldview && f.type === "File"
                ? (row) =>
                    row[f.name] &&
                    getState().fileviews[column.fieldview].run(
                      row[f.name],
                      row[`${f.name}__filename`],
                      { row, ...column, ...(column?.configuration || {}) }
                    )
                : column.fieldview &&
                    f.type.fieldviews &&
                    f.type.fieldviews[column.fieldview]
                  ? (row) =>
                      f.type.fieldviews[column.fieldview].run(
                        row[f_with_val.name],
                        req,
                        { row, ...f.attributes, ...column.configuration }
                      )
                  : isShow
                    ? f.type.showAs
                      ? (row) => f.type.showAs(row[f_with_val.name])
                      : (row) => text(row[f_with_val.name])
                    : f.listKey,
            sortlink:
              !f.calculated || f.stored
                ? sortlinkForName(f.name, req, viewname, statehash)
                : undefined,
          };
        if (column.click_to_edit) {
          const updateKey = (fvr, column_key) => {
            const oldkey =
              typeof fvr.key === "function" ? fvr.key : (r) => r[fvr.key];
            const doSetKey =
              (column.fieldview === "subfield" ||
                column.fieldview === "keys_expand_columns") &&
              column_key;
            const schema =
              doSetKey && f.attributes?.hasSchema
                ? (f.attributes.schema || []).find((s) => s.key === column_key)
                : undefined;
            const newkey = (row) => {
              if (role <= table.min_role_write || table.is_owner(req.user, row))
                return div(
                  {
                    "data-inline-edit-fielddata": encodeURIComponent(
                      JSON.stringify({
                        field_name: f.name,
                        table_name: table.name,
                        pk: row[table.pk_name],
                        fieldview: column.fieldview,
                        configuration: column?.configuration,
                      })
                    ),
                    "data-inline-edit-ajax": "true",
                    "data-inline-edit-dest-url": `/api/${table.name}/${
                      row[table.pk_name]
                    }`,
                  },
                  span({ class: "current" }, oldkey(row)),
                  i({ class: "editicon fas fa-edit ms-1" })
                );
              else return oldkey(row);
            };
            fvr.key = newkey;
          };
          if (Array.isArray(fvrun)) {
            fvrun.forEach((fvr) => {
              updateKey(fvr, fvr.row_key[1]);
            });
          } else updateKey(fvrun, column.configuration?.key || column.key);
        }
        return fvrun;
      }
    })
  ).filter((v) => !!v);
  if (dropdown_actions.length > 0) {
    //legacy
    tfields.push({
      label: req.__("Action"),
      key: (r) =>
        div(
          { class: "dropdown" },
          button(
            {
              class: "btn btn-sm btn-xs btn-outline-secondary dropdown-toggle",
              "data-boundary": "viewport",
              type: "button",
              id: `actiondd${r.id}`,
              "data-bs-toggle": "dropdown",
              "aria-haspopup": "true",
              "aria-expanded": "false",
            },
            req.__("Action")
          ),
          div(
            {
              class: "dropdown-menu dropdown-menu-end",
              "aria-labelledby": `actiondd${r.id}`,
            },
            dropdown_actions.map((acol) => acol.key(r))
          )
        ),
    });
  }
  return tfields;
}
@function
@param {string} viewname
@param {Table|object} table
@param {Field[]} fields
@param {object[]} columns
@param {boolean} isShow
@param {object} req
@param {*} __
@returns {object[]}