updateSegmentGroups = (groups, groupId, segmentId) => {
  if ((!groupId && groupId !== 0) || (!segmentId && segmentId !== 0)) {
    return groups;
  }
  // clone segment group in case the result will be used to trigger re-render
  const updatedGroups = groups.slice();
  // deselect a segment from the current group
  if ((groups[groupId] || []).includes(segmentId)) {
    if (groups[groupId].length > 1) {
      // remove segment from current group if it is not the only segment left
      updatedGroups[groupId] = groups[groupId].filter(id => id !== segmentId);
    }
  } else {
    // remove from other groups, segment only allows in one group
    groups.forEach((group, idx) => {
      if (
        // if the group is not the source group users interacted with
        idx !== groupId &&
        // and it has more than one remaining segments, or exactly one remaining segment that is not equal to segmentId
        (group.length > 1 || (group.length === 1 && group[0] !== segmentId))
      ) {
        // remove segment Id from the other group
        updatedGroups[idx] = group.filter(id => id !== segmentId);
        // add segment Id to current group, since one segment Id can
        // only exist in one segment group, there won't be duplicates
        updatedGroups[groupId] = groups[groupId].concat([segmentId]);
      }
    });
  }

  return updatedGroups;
}
update segment grouping when user (de)select a segment from either group
@param {array} groups - the input segment group before updating
@param {number} groupId - the source segment group
@param {number} segmentId - the segment Id being (de)selected
@return {array} updated segment groups