///////////////////////////////
//  gzform
//
// provides form services and utilities
// validation services
// dirty and change tracking
// and also general error display in forms
//probably should be broken up more
// All translation keys for validation *MUST* be fetched prior to this being used as it assumes all keys are fetched first
// Add any new keys used to the block in translation.js=>commonKeysEditForm
import Vue from "vue";

let triggeringChange = false;

function isEmpty(o) {
  if (typeof o == "number" && o == 0) {
    return false;
  }
  return !o;
}

////////////////////////////////////
//  isInt value??
//
//FROM HERE: https://stackoverflow.com/a/14794066/8939
//fast test if is an integer:
function isInt(value) {
  let x;
  if (isNaN(value)) {
    return false;
  }
  x = parseFloat(value);
  return (x | 0) === x;
}

////////////////////////////////////
//  isNumber
//
//FROM HERE: https://stackoverflow.com/a/1830632/8939
function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

////////////////////////////////////
//  Get control from ref
//
function getControl(vm, ref) {
  return vm.$refs[ref];
}

////////////////////////////////////
//  Get value from control
//
function getControlValue(ctrl) {
  return ctrl.value;
}

////////////////////////////////////
//  Get field name from control
//
function getControlLabel(ctrl) {
  if (ctrl.label == undefined) {
    return "UNKNOWN CONTROL";
  } else {
    return ctrl.label;
  }
}

/////////////////////////////////////////
// Get errors for a particular field
// from server error collection
//
function getErrorsForField(vm, ref) {
  //Note: to debug this on forms just put {{ formState.serverError }}
  //on the form to see what is actually stored there and should be showing
  let ret = [];
  if (ref == "generalerror") {
    ret = vm.formState.serverError.details.filter(
      z => z.target == false || z.target == "generalerror"
    );
  } else {
    ret = vm.formState.serverError.details.filter(function(o) {
      if (!o.target) {
        return false;
      }
      //server error fields are capitalized
      //client field names are generally lower case except for custom fields
      //so we need to normalize them all to lower case to match
      //they will always differ by more than case so this is fine

      //NOTE: Indexed child collection error target field names are in this scheme "Items[2].FieldName"
      //where Items is the name of the parent models property that contains the child collection

      return o.target.toLowerCase() == ref.toLowerCase();
    });
  }
  return ret;
}

///////////////////////////////
// ERROR BOX ERRORS
// gathers any messages for error box on form which is the generic catch all for non field specific errors from server
// and application itself locally
function getErrorBoxErrors(vm, errs) {
  let hasErrors = false;
  let ret = "";
  if (errs.length > 0) {
    hasErrors = true;
    //loop array and append each error to a return string
    for (let i = 0; i < errs.length; i++) {
      ret += errs[i] + "\r\n";
    }
  }

  //any application errors?
  if (vm.formState.appError) {
    hasErrors = true;
    // console.log("gzform:geterrorboxerrors ", {
    //   appError: vm.formState.appError,
    //   ret: ret
    // });
    ret += vm.formState.appError;
  }

  if (!hasErrors) {
    return null;
  } else {
    return ret;
  }
}

export default {
  ///////////////////////////////
  // REQUIRED
  //
  required(vm, ref) {
    if (vm.formState.loading) {
      return true;
    }

    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (!isEmpty(value)) {
      return true;
    }

    //   "ErrorRequiredFieldEmpty": "{0} is a required field.  Please enter a value for {0}",
    let err = vm.$sock.t("ErrorRequiredFieldEmpty");
    const fieldName = getControlLabel(ctrl);
    err = err.replace("{0}", fieldName);
    //replace only replaces first instance so need to do it twice
    err = err.replace("{0}", fieldName);

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },

  ///////////////////////////////
  // MAXLENGTH
  //
  maxLength(vm, ref, max) {
    if (vm.formState.loading) {
      return true;
    }
    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (isEmpty(value)) {
      return true;
    }

    if (value.length > max) {
      //get the translated rule text
      // "ErrorFieldLengthExceeded": "{0} can not exceed {1} characters.",
      let err = vm.$sock.t("ErrorFieldLengthExceeded");
      const fieldName = getControlLabel(ctrl);
      err = err.replace("{0}", fieldName);
      err = err.replace("{1}", max);

      this.setFormState({
        vm: vm,
        valid: false
      });
      return err;
    } else {
      return true;
    }
  },
  ///////////////////////////////
  // MAX 255
  //
  max255(vm, ref) {
    if (vm.formState.loading) {
      return true;
    }
    return this.maxLength(vm, ref, 255);
  },
  ///////////////////////////////
  // DatePrecedence
  // (start date must precede end date, however if both are empty then that's ok)
  //
  datePrecedence(vm, refStart, refEnd) {
    if (vm.formState.loading) {
      return true;
    }
    const ctrlStart = getControl(vm, refStart);
    if (typeof ctrlStart == "undefined") {
      return true;
    }

    const ctrlEnd = getControl(vm, refEnd);
    if (typeof ctrlEnd == "undefined") {
      return true;
    }

    let valueStart = getControlValue(ctrlStart);
    if (isEmpty(valueStart)) {
      return true;
    }

    let valueEnd = getControlValue(ctrlEnd);
    if (isEmpty(valueEnd)) {
      return true;
    }

    valueStart = window.$gz.DateTime.fromISO(valueStart);
    valueEnd = window.$gz.DateTime.fromISO(valueEnd);

    // if either is not valid.
    //moment.github.io/luxon/docs/manual/validity.html
    if (!valueStart.isValid || !valueEnd.isValid) {
      return true;
    }

    if (valueStart > valueEnd) {
      //   "ErrorStartDateAfterEndDate": "Start date must be earlier than stop / end date",
      const err = vm.$sock.t("ErrorStartDateAfterEndDate");

      this.setFormState({
        vm: vm,
        valid: false
      });
      return err;
    } else {
      return true;
    }
  },
  ///////////////////////////////
  // Confirm password
  // (two fields must match)
  //
  confirmMatch(vm, refFirst, refSecond) {
    if (vm.formState.loading) {
      return true;
    }
    const ctrlFirst = getControl(vm, refFirst);
    if (typeof ctrlFirst == "undefined") {
      return true;
    }

    const ctrlSecond = getControl(vm, refSecond);
    if (typeof ctrlSecond == "undefined") {
      return true;
    }
    const valueFirst = getControlValue(ctrlFirst);
    const valueSecond = getControlValue(ctrlSecond);

    if (valueFirst != valueSecond) {
      const err = vm.$sock.t("ErrorNoMatch");

      this.setFormState({
        vm: vm,
        valid: false
      });
      return err;
    } else {
      return true;
    }
  },
  ///////////////////////////////
  // INTEGER IS VALID
  //
  integerValid(vm, ref) {
    if (vm.formState.loading) {
      return true;
    }
    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }
    const value = getControlValue(ctrl);
    if (isInt(value)) {
      return true;
    }
    const err = vm.$sock.t("ErrorFieldValueNotInteger");
    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // DECIMAL
  // Basically anything that can be a number is valid
  //
  decimalValid(vm, ref) {
    if (vm.formState.loading) {
      return true;
    }
    //TODO: Handle commas and spaces in numbers
    //as per window.$gz.translation rules for numbers

    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (isEmpty(value)) {
      return true;
    }

    if (isNumber(value)) {
      return true;
    }
    const err = vm.$sock.t("ErrorFieldValueNotDecimal");
    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // MAX VALUE
  // Maximum numeric value
  // maxValue is lt or eq
  // empty is considered valid for this rule
  //
  maxValueValid(vm, ref, maxValue) {
    if (vm.formState.loading) {
      return true;
    }

    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (isEmpty(value)) {
      return true;
    }

    if (!isNumber(value)) {
      return true;
    }

    //Ok, were here with a non empty number of some kind
    if (value <= maxValue) {
      return true;
    }

    const err = `${vm.$sock
      .t("ErrorFieldValueNumberGreaterThanMax")
      .replace("{0}", maxValue)} ${maxValue}`;

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // MIN VALUE
  // Minimum numeric value
  // minValue is gt or eq
  // empty is considered valid for this rule
  //
  minValueValid(vm, ref, minValue) {
    if (vm.formState.loading) {
      return true;
    }

    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (isEmpty(value)) {
      return true;
    }

    if (!isNumber(value)) {
      return true;
    }

    //Ok, were here with a non empty number of some kind
    //actual check
    if (value >= minValue) {
      return true;
    }

    const err = `${vm.$sock
      .t("ErrorFieldValueNumberLessThanMin")
      .replace("{0}", minValue)} ${minValue}`;

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // EMAIL IS VALID-ish
  //https://tylermcginnis.com/validate-email-address-javascript/
  emailValid(vm, ref) {
    if (vm.formState.loading) {
      return true;
    }
    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (isEmpty(value)) {
      return true;
    }

    if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) == true) {
      return true;
    }

    const err = vm.$sock.t("ErrorAPI2203"); //"Invalid value"

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // USER REQUIRED FIELDS
  // (Fields defined by Sockeye users as required on form that are not stock required already)
  userRequiredFields(vm, ref, formCustomTemplateFieldName) {
    if (vm.formState.loading) {
      return true;
    }

    const template =
      window.$gz.store.state.formCustomTemplate[vm.formCustomTemplateKey];
    if (template === undefined) {
      return true;
    }
    //See if control formCustomTemplateFieldName is in server required fields collection
    //this is a collection of both custom field definitions and standard form fields that are required
    //since all names are unique can just filter out the one we need by name which will inherently ignore custom fields by default
    const templateItem = template.find(
      z => z.fld == formCustomTemplateFieldName
    );

    //templateItem.required
    if (templateItem === undefined || templateItem.required !== true) {
      return true;
    }

    const ctrl = getControl(vm, ref);
    if (typeof ctrl == "undefined") {
      return true;
    }

    const value = getControlValue(ctrl);
    if (!isEmpty(value)) {
      return true;
    }

    let err = vm.$sock.t("ErrorRequiredFieldEmpty");
    const fieldName = getControlLabel(ctrl);
    err = err.replace("{0}", fieldName);
    //replace only replaces first instance so need to do it twice
    err = err.replace("{0}", fieldName);

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // CUSTOMFIELDS
  //  For now the only rule is that they can be required or not
  //
  customFieldsCheck(vm, templateItem, subvm, fieldName) {
    if (vm.formState.loading) {
      return true;
    }

    if (templateItem.required !== true) {
      return true;
    }

    const value = subvm.GetValueForField(templateItem.dataKey);
    if (!isEmpty(value)) {
      return true;
    }

    //It's empty and it's required so return error
    let err = vm.$sock.t("ErrorRequiredFieldEmpty");
    err = err.replace("{0}", fieldName);
    //replace only replaces first instance so need to do it twice
    err = err.replace("{0}", fieldName);

    this.setFormState({
      vm: vm,
      valid: false
    });
    return err;
  },
  ///////////////////////////////
  // SERVER ERRORS
  // Process and return server errors if any for form and field specified
  // note that this is called in turn by every control on the form so it's only job
  // is to return errors if they exist for *that* field
  // Not to be confused with validation errors through the "rules" property
  // that is separate and any errors returned here are *added* to the validation
  // errors in the UI by Vuetify
  //
  serverErrors(vm, ref) {
    const ret = [];

    //check for errors if we have any errors
    if (!window.$gz.util.objectIsEmpty(vm.formState.serverError)) {
      //First let's get the top level error code
      const apiErrorCode = parseInt(vm.formState.serverError.code);
      //Not all server errors mean the form is invalid, exceptions here
      let formValid = false;

      /*      
      These errors are not the user's fault and no changes to the form are required 
      so they may be temporary and user should be able to retry save
        API_CLOSED = 2000,
        API_OPS_ONLY = 2001,
        API_SERVER_ERROR = 2002,
        VALIDATION_REFERENTIAL_INTEGRITY = 2208
      */
      switch (apiErrorCode) {
        case 2000:
        case 2001:
        case 2002:
        case 2208:
          formValid = true; //we came here because the user saved because the form was valid so it's safe to set that the same again
          break;
        default:
          formValid = false;
      }

      //GENERAL ERROR
      if (ref == "generalerror") {
        //Add any general errors to ret (specific detail errors for the "generalerror" will be processed later below)
        let err = vm.$sock.t("ErrorAPI" + apiErrorCode.toString());

        if (vm.formState.serverError.message) {
          err = err + "\r\n" + vm.formState.serverError.message;
        }

        this.setFormState({
          vm: vm,
          valid: formValid
        });
        ret.push(err);
      }

      //DETAIL ERRORS
      //{"error":{"code":"2200","details":[{"message":"Exception: Error converting value \"\" to type 'Sockeye.Biz.AUTHORIZATION_ROLES'. Path 'roles', line 1, position 141.","target":"roles","error":"2203"}],"message":"Object did not pass validation"}}
      //Specific field validation errors are in an array in "details" key
      if (!window.$gz.util.objectIsEmpty(vm.formState.serverError.details)) {
        //See if this key is in the details array
        const errorsForField = getErrorsForField(vm, ref);
        if (errorsForField.length > 0) {
          //iterate the errorsForField object and add each to return array of errors
          //de-lodash
          //window.$gz. _.each(errorsForField, function(ve) {
          errorsForField.forEach(function(ve) {
            let fldErr = "";
            const fldErrorCode = parseInt(ve.error);
            fldErr =
              vm.$sock.t("ErrorAPI" + fldErrorCode.toString()) +
              " [" +
              ve.error +
              "]";
            if (ve.message) {
              //NOTE: call sync version here as can't call async code from here
              //so translations must already be pre-fetched to work here
              fldErr +=
                ' - "' +
                window.$gz.translation.translateStringWithMultipleKeys(
                  ve.message
                ) +
                '"';
              ret.push(fldErr);
            } else {
              ret.push(fldErr);
            }
          });

          this.setFormState({
            vm: vm,
            valid: false
          });

          return ret;
        }
      }
    }

    //default if no error message to display
    return ret;
  },
  ///////////////////////////////
  // childRowHasError
  // returns true if error exists for row
  // else returns false
  // (actual errors not returned just for row indicator,
  // user opens child edit form to see exact error)
  //
  childRowHasError(vm, path) {
    //Note: this just shows server errors, not local form validation errors
    //it's assumed user will fix in form or when they submit see the error come back
    //Note: this method is easily converted to return actual errors if it ever makes sense to do that but for now I'm ok with row TTM

    //No server errors?
    if (window.$gz.util.objectIsEmpty(vm.formState.serverError)) {
      //nothing to process
      return null;
    }

    //no detail errors?
    if (window.$gz.util.objectIsEmpty(vm.formState.serverError.details)) {
      //nothing to process
      return null;
    }

    path = path.toLowerCase();
    //Might be an error, check if collectionName is in error collection
    //this is what we're dealing with
    // { "code": "2200", "details": [ { "message": "LT:PurchaseOrderReceiptItemQuantityReceivedErrorInvalid", "target": "Items[0].QuantityReceived", "error": "2203" } ], "message": "ErrorAPI2200" }
    //or multilayered like this: target: "Items[3].scheduledUsers[1].EstimatedQuantity"
    //so set "path" accordingly easy peasy, such error, so wow

    //filter in items that start with the row collection name and index provided
    return vm.formState.serverError.details.some(function(o) {
      if (!o.target) {
        return false;
      }
      return o.target.toLowerCase().includes(path);
    });
  },
  ///////////////////////////////
  // childRowErrorClass
  // returns class to set on row if error exists for row
  // else returns nothing
  //( called by some forms but notably not the workorder or PO which have this built in)
  //
  childRowErrorClass(vm, collectionName, rowIndex) {
    if (this.childRowHasError(vm, collectionName, rowIndex)) {
      return "font-weight-black font-italic error--text";
    }
    return null;
  },
  ///////////////////////////////
  // ShowMe
  // (returns false if the field has been set to hidden by the user in the formcustomtemplate)
  // NOTE: that in a form this should only be used with non stock-required fields, if they are already required they cannot be hidden
  //
  showMe(vm, formCustomTemplateFieldName) {
    //special check for wiki field
    //if read only then can't add any content and if no content then no reason to show it at all
    if (formCustomTemplateFieldName == "wiki") {
      if (
        vm.formState.readOnly &&
        (vm.obj.wiki == null || vm.obj.wiki.length == 0)
      ) {
        return false;
      }
    }

    const template =
      window.$gz.store.state.formCustomTemplate[vm.formCustomTemplateKey];

    if (template === undefined) {
      return true;
    }
    //See if control templateFieldName is in server required fields collection
    //this is a collection of both custom field definitions and standard form fields that are required
    //since all names are unique can just filter out the one we need by name which will inherently ignore custom fields by default

    const templateItem = template.find(
      z => z.fld.toLowerCase() == formCustomTemplateFieldName.toLowerCase()
    );

    if (templateItem === undefined || templateItem.hide !== true) {
      return true;
    }

    //Only here if we have a record in the custom template for this particular field and it's set to hide:true
    return false;
  },

  ///////////////////////////////
  // ClearformState.serverErrors
  // Clear all server errors and app errors and ensure error box doesn't show
  //
  deleteAllErrorBoxErrors(vm) {
    //clear all keys from server error
    window.$gz.util.removeAllPropertiesFromObject(vm.formState.serverError);
    //clear app errors
    vm.formState.appError = null;
    //clear out actual message box display
    vm.formState.errorBoxMessage = null;

    this.setFormState({
      vm: vm,
      valid: true
    });
  },
  ///////////////////////////////
  // setErrorBoxErrors
  // Gather server errors and set the appropriate keys
  //
  setErrorBoxErrors(vm) {
    const errs = this.serverErrors(vm, "generalerror");
    const ret = getErrorBoxErrors(vm, errs);
    vm.formState.errorBoxMessage = ret;
  },
  ///////////////////////////////
  // On fieldValueChanged handler
  //  formReference is an optional string name of the form ref property if alternative named form
  // - Clear server errrors for this field
  // - Flag form dirty
  // - check and flag form validity
  //
  //
  fieldValueChanged(vm, ref, formReference) {
    const that = this;
    let formControl = null;
    if (formReference == undefined) {
      formControl = vm.$refs.form;
    } else {
      //NOTE: Due to automatic code formatting some refs may come here with newlines in them resulting in no matches
      formReference = formReference.replace(/\s/g, "");
      formControl = vm.$refs[formReference];
    }

    //dev error on form?
    if (formControl == null) {
      if (vm.$sock.dev) {
        //not necessarily an error, can happen during form init
        console.trace(
          `gzform::fieldValueChanged formControl is not found ref:${ref}, formReferences:${formReference} `
        );
      } else {
        return;
      }
    }

    //this is currently required to ensure that this method runs after all the broken rule checks have settled
    Vue.nextTick(function() {
      //-------------
      if (triggeringChange || vm.formState.loading) {
        return;
      }

      //# REMOVE SERVER ERRORS FOR THIS FIELD REF
      const targetRef = ref.toLowerCase();

      //NOTE: This block of code is meant to remove all detailed server errors where the Target matches the current referenced control
      //Then it checks to see if there is anything left in details as this might have been all there was and if so removes that whole thing
      //leaving only errors for other fields or nothing if this ref field was all the errors left

      //Remove any server errors that are for our target ref field
      //and also set a flag if there *are* any server errors for our target field
      let targetFieldHasServerError = false;

      if (vm.formState.serverError.details) {
        let i = vm.formState.serverError.details.length;
        //iterate backwards so we can mutate the array in place
        while (i--) {
          var o = vm.formState.serverError.details[i];
          if (o.target && o.target.toLowerCase() == targetRef) {
            //remove it, it's for our ref field
            vm.formState.serverError.details.splice(i, 1);
            targetFieldHasServerError = true;
          }
        }
      }

      //# CLEAN UP SERVER ERRORS IF NONE LEFT
      //If there are no more errors in details then remove the whole thing as it's no longer required
      if (
        vm.formState.serverError.details &&
        vm.formState.serverError.details.length < 1
      ) {
        if (vm.formState.serverError.code == "2200") {
          //clear all keys from server error
          window.$gz.util.removeAllPropertiesFromObject(
            vm.formState.serverError
          );
        }
      }

      //# CLEAR OUT STALE VALIDATION ERRORS FOR CONTROL
      //Clear out old validation display in form by forcing the control's data to change
      //I tried calling form validate and reset but it did nothing
      //probably because it has safeguards to prevent excess validation, this works though so far
      //I added the triggering change guard but it actually doesn't seem to be required here, more investigation is required

      //TODO: find a cleaner way to remove old validation on the control, why can't it just be set on the control referenced??

      if (targetFieldHasServerError) {
        triggeringChange = true;
        const val = vm.obj[ref];
        vm.obj[ref] = null;
        vm.obj[ref] = val;
        triggeringChange = false;
      }

      //# UPDATE FORM STATUS
      let formValid = formControl.validate();
      that.setFormState({
        vm: vm,
        dirty: true,
        valid: formValid
      });
      //---------------
    }); //next tick end
  },

  setFormState(newState) {
    //this returns a promise so any function that needs to wait for this can utilize that

    return Vue.nextTick(function() {
      if (newState.valid != null) {
        newState.vm.formState.valid = newState.valid;
      }

      if (newState.dirty != null) {
        newState.vm.formState.dirty = newState.dirty;
      }

      if (newState.loading != null) {
        newState.vm.formState.loading = newState.loading;
      }

      if (newState.readOnly != null) {
        newState.vm.formState.readOnly = newState.readOnly;
      }

      if (newState.ready != null) {
        newState.vm.formState.ready = newState.ready;
      }
    });
  },
  ////////////////////////////////////
  //  Get form settings
  //  for form specified or empty object if there is none
  // EAch form is responsible for what it stores and how it initializes, this just provides that
  // the form does the actual work of what settings it requires
  // Form settings are temp and saved, saved ones go into vuex and localstorage and persist a refresh
  // and temporary ones are stored in session storage and don't persist a refresh
  //
  getFormSettings(formKey) {
    return {
      temp: JSON.parse(sessionStorage.getItem(formKey)),
      saved: window.$gz.store.state.formSettings[formKey]
    };
  },
  ////////////////////////////////////
  //  Set form settings
  //  for form key specified
  // requires object with one or both keys {temp:{...tempformsettings...},saved:{...persistedformsettings...}}
  //
  setFormSettings(formKey, formSettings) {
    if (formSettings.saved) {
      window.$gz.store.commit("setFormSettings", {
        formKey: formKey,
        formSettings: formSettings.saved
      });
    }

    if (formSettings.temp) {
      sessionStorage.setItem(formKey, JSON.stringify(formSettings.temp));
    }
  }, ////////////////////////////////////
  //  Get last report used from form settings
  //
  getLastReport(formKey) {
    const fs = window.$gz.store.state.formSettings[formKey];
    if (fs == null || fs.lastReport == null) {
      return null;
    }
    return fs.lastReport;
  }, ////////////////////////////////////
  //  Set last report used in form settings
  //
  setLastReportMenuItem(formKey, reportSelected, vm) {
    let fs = window.$gz.store.state.formSettings[formKey];
    if (fs == null) {
      fs = {};
    }
    fs.lastReport = reportSelected;

    window.$gz.store.commit("setFormSettings", {
      formKey: formKey,
      formSettings: fs
    });

    window.$gz.eventBus.$emit("menu-upsert-last-report", {
      title: reportSelected.name,
      notrans: true,
      icon: "$sockiFileAlt",
      key: formKey + ":report:" + reportSelected.id,
      vm: vm
    });
  }, ////////////////////////////////////
  //  Add no selection item
  // Used by forms that need the option of an unselected
  // item in a pick list
  //
  addNoSelectionItem(listArray, nullNotZero) {
    if (listArray == undefined || listArray == null) {
      listArray = [];
    }
    listArray.unshift({ name: "-", id: nullNotZero ? null : 0 });
  },
  ////////////////////////////////////
  //  Get no selection item
  // Used by forms that need just the
  // unselected item itself not added
  // to a list
  //
  getNoSelectionItem(nullNotZero) {
    return { name: "-", id: nullNotZero ? null : 0 };
  },
  ////////////////////////////////////
  // Get validity of referenced control
  //
  controlIsValid(vm, ref) {
    if (vm.$refs[ref]) {
      return vm.$refs[ref].valid;
    }
    return false;
  },
  ////////////////////////////////////////
  // All controls are valid?
  //
  controlsAreAllValid(vm, refs) {
    //if any are not valid return false
    for (let i = 0; i < refs.length; i++) {
      const item = refs[i];
      if (vm.$refs[item]) {
        if (!vm.$refs[item].valid) {
          return false;
        }
      }
    }
    return true;
  },
  ////////////////////////////////////////
  // Standard object not found handler
  //
  handleObjectNotFound(vm) {
    window.$gz.eventBus.$emit("notify-error", vm.$sock.t("ErrorAPI2010"));
    //after small delay to show error
    //(the navigate removes the toast notification immediately)
    setTimeout(function() {
      vm.$router.go(-1);
    }, 2000);
  },
  ////////////////////////////////////////
  // Standard data table row error class
  //
  tableRowErrorClass() {
    return "font-weight-black font-italic error--text ";
  },
  ////////////////////////////////////////
  // Standard data table deleted class
  //
  tableRowDeletedClass() {
    return "text-decoration-line-through ";
  }
};
