<template>
  <div>
    <gz-error :errorBoxMessage="formState.errorBoxMessage"></gz-error>
    <v-card>
      {{ dataTablePagingOptions }}
      <v-card-title>
        <v-select
          v-model="listViewId"
          :items="selectLists.listViews"
          item-text="name"
          item-value="id"
          :label="$ay.t('Filter')"
          @input="listViewChanged"
          data-cy="selectlistview"
        >
        </v-select>
        <v-spacer></v-spacer>
        <div>
          <v-btn @click="refresh">
            <v-icon data-cy="refresh">$ayiSync</v-icon>
          </v-btn>

          <v-btn class="ml-12" @click="editListView">
            <v-icon data-cy="filter">$ayiColumns</v-icon>
          </v-btn>
        </div>
      </v-card-title>
      <div
        class="headline ml-3 accent--text"
        v-if="!loading && records.length < 1"
      >
        {{ $ay.t("NoData") }}
      </div>
      <!-- WIDE TABLE VIEW -->
      <template v-if="$vuetify.breakpoint.smAndUp">
        <v-data-table
          :headers="headers"
          :items="records"
          v-model="selected"
          :options.sync="dataTablePagingOptions"
          :server-items-length="totalRecords"
          :loading="loading"
          multi-sort
          :show-select="showSelect"
          :single-select="singleSelect"
          :footer-props="{
            showCurrentPage: true,
            showFirstLastPage: true,
            itemsPerPageOptions: rowsPerPageItems,
            itemsPerPageText: $ay.t('RowsPerPage'),
            pageText: $ay.t('PageOfPageText')
          }"
          :loading-text="$ay.t('Loading')"
          :no-data-text="$ay.t('NoData')"
          class="elevation-1"
          data-cy="datatable"
        >
          <template v-slot:body="{ items }">
            <tbody>
              <tr v-for="item in items" :key="item.id">
                <template v-if="showSelect">
                  <td>
                    <v-checkbox
                      v-model="selected"
                      :value="item"
                      hide-details
                    ></v-checkbox>
                  </td>
                </template>
                <td
                  v-for="c in item.columns"
                  :key="c.key"
                  v-bind:style="cellStyle(c)"
                >
                  <template v-if="c.t == 1">
                    <!-- DATETIME -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 2">
                    <!-- DATE -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 3">
                    <!-- TIME -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 4">
                    <!-- TEXT (also maybe openable)-->
                    <template v-if="c.i && c.i != 0 && !c.nopen">
                      <!-- openable object with an ID -->
                      <div
                        class="subtitle-1"
                        @click="gridCellButtonClick(c.key, c.i, c.ot)"
                      >
                        <a href="javascript:"> {{ c.v }}</a>
                      </div>
                    </template>
                    <template v-else>
                      {{ c.v }}
                    </template>
                  </template>
                  <template v-else-if="c.t == 5">
                    <!-- INTEGER -->
                    <template v-if="c.i && c.i != 0 && !c.nopen">
                      <!-- openable object with an ID -->
                      <div
                        class="subtitle-1"
                        @click="gridCellButtonClick(c.key, c.i, c.ot)"
                      >
                        <a href="javascript:"> {{ c.v }}</a>
                      </div>
                    </template>
                    <template v-else>
                      {{ c.v }}
                    </template>
                  </template>
                  <template v-else-if="c.t == 6">
                    <!-- BOOL -->

                    <v-icon v-if="c.v === false" small>$ayiSquare</v-icon>
                    <v-icon v-else-if="c.v === true" small
                      >$ayiCheckSquare</v-icon
                    >
                    <v-icon v-else small>$ayiMinus-square</v-icon>
                  </template>
                  <template v-else-if="c.t == 7">
                    <!-- DECIMAL -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 8">
                    <!-- CURRENCY -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 9">
                    <!-- TAGS -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 10">
                    <!-- ENUM (translated to text on getdata) ALSO MAYBE OPENABLE -->
                    <template v-if="c.i && c.ot">
                      <!-- openable object with an ID -->
                      <div
                        class="subtitle-1"
                        @click="gridCellButtonClick(c.key, c.i, c.ot)"
                      >
                        <a href="javascript:"> {{ c.v }}</a>
                      </div>
                    </template>
                    <template v-else>
                      {{ c.v }}
                    </template>
                  </template>
                  <template v-else-if="c.t == 11">
                    <!-- EMAIL -->
                    <a :href="'mailto:' + c.v">{{ c.v }}</a>
                  </template>
                  <template v-else-if="c.t == 12">
                    <!-- URL / HTTP -->
                    <!-- Expects full url with protocol etc in c.v so might need to add to record builder -->
                    <a :href="c.v" target="_blank">{{ c.v }}</a>
                  </template>
                  <template v-else-if="c.t == 14">
                    <!-- File / memory Size -->
                    {{ c.v }}
                  </template>
                  <template v-else-if="c.t == 16">
                    <!-- PHONE NUMBER -->
                    <a :href="'tel:' + c.v">{{ c.v }}</a>
                  </template>
                  <template v-else>
                    <!-- UNKNOWN -->
                    {{ c.v }}
                  </template>
                </td>
              </tr>
            </tbody>
          </template>
        </v-data-table>
      </template>
      <!-- MOBILE TABLE VIEW -->
      <template v-else>
        <v-data-iterator
          :headers="headers"
          :items="records"
          v-model="selected"
          :options.sync="dataTablePagingOptions"
          :server-items-length="totalRecords"
          :loading="loading"
          :disable-sort="true"
          :show-select="showSelect"
          :single-select="singleSelect"
          :footer-props="{
            showCurrentPage: true,
            showFirstLastPage: true,
            itemsPerPageOptions: rowsPerPageItems,
            itemsPerPageText: $ay.t('RowsPerPage'),
            pageText: $ay.t('PageOfPageText')
          }"
          :loading-text="$ay.t('Loading')"
          class="elevation-1"
          data-cy="datatable"
        >
          <template v-slot:header="props">
            <!-- Mimic the full width data table select all toggle :no-data-text="$ay.t('NoData')"-->
            <div
              id="divSelectAll"
              @click="props.toggleSelectAll(!props.everyItem)"
              class="pl-2 pt-2"
            >
              <v-icon v-if="!props.someItems" large>$ayiSquare</v-icon>
              <v-icon v-if="props.someItems && !props.everyItem" large
                >$ayiMinus-square</v-icon
              >
              <v-icon v-if="props.everyItem" large>$ayiCheckSquare</v-icon>
            </div>
          </template>

          <template v-slot:default="{ items }">
            <v-row>
              <v-col
                v-for="item in items"
                :key="item.id"
                cols="12"
                sm="6"
                md="4"
                lg="3"
              >
                <v-card elevation="4" tile>
                  <template v-if="showSelect">
                    <v-card-title class="subheading font-weight-bold">
                      <v-checkbox
                        v-model="selected"
                        :value="item"
                        hide-details
                      ></v-checkbox>
                    </v-card-title>
                    <v-divider></v-divider>
                  </template>
                  <v-list dense>
                    <v-list-item
                      two-line
                      v-for="c in item.columns"
                      :key="c.key"
                    >
                      <v-list-item-content>
                        <v-list-item-title>
                          {{ getHeaderText(c.key) }}</v-list-item-title
                        >
                        <v-list-item-subtitle v-bind:style="cellStyle(c)">
                          <template v-if="c.t == 1">
                            <!-- DATETIME -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 2">
                            <!-- DATE -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 3">
                            <!-- TIME -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 4">
                            <!-- TEXT (also maybe openable)-->
                            <template v-if="c.i && c.i != 0 && !c.nopen">
                              <!-- openable object with an ID -->
                              <div
                                class="subtitle-1"
                                @click="gridCellButtonClick(c.key, c.i, c.ot)"
                              >
                                <a href="javascript:"> {{ c.v }}</a>
                              </div>
                            </template>
                            <template v-else>
                              {{ c.v }}
                            </template>
                          </template>
                          <template v-else-if="c.t == 5">
                            <!-- INTEGER -->
                            <template v-if="c.i && c.i != 0 && !c.nopen">
                              <!-- openable object with an ID -->
                              <div
                                class="subtitle-1"
                                @click="gridCellButtonClick(c.key, c.i, c.ot)"
                              >
                                <a href="javascript:"> {{ c.v }}</a>
                              </div>
                            </template>
                            <template v-else>
                              {{ c.v }}
                            </template>
                          </template>
                          <template v-else-if="c.t == 6">
                            <!-- BOOL -->
                            <div>
                              <v-icon v-if="c.v === false" small
                                >$ayiSquare</v-icon
                              >
                              <v-icon v-else-if="c.v === true" small
                                >$ayiCheckSquare</v-icon
                              >
                              <v-icon v-else small>$ayiMinus-square</v-icon>
                            </div>
                          </template>
                          <template v-else-if="c.t == 7">
                            <!-- DECIMAL -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 8">
                            <!-- CURRENCY -->
                            <div>{{ c.v }}</div>
                          </template>
                          <template v-else-if="c.t == 9">
                            <!-- TAGS -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 10">
                            <!-- ENUM (translated to text on getdata) ALSO MAYBE OPENABLE -->
                            <template v-if="c.i && c.ot">
                              <!-- openable object with an ID -->
                              <div
                                class="subtitle-1"
                                @click="gridCellButtonClick(c.key, c.i, c.ot)"
                              >
                                <a href="javascript:"> {{ c.v }}</a>
                              </div>
                            </template>
                            <template v-else>
                              {{ c.v }}
                            </template>
                          </template>
                          <template v-else-if="c.t == 11">
                            <!-- EMAIL -->
                            <a :href="'mailto:' + c.v">{{ c.v }}</a>
                          </template>
                          <template v-else-if="c.t == 12">
                            <!-- URL / HTTP -->
                            <!-- Expects full url with protocol etc in c.v so might need to add to record builder -->
                            <a :href="c.v" target="_blank">{{ c.v }}</a>
                          </template>
                          <template v-else-if="c.t == 14">
                            <!-- File / memory Size -->
                            {{ c.v }}
                          </template>
                          <template v-else-if="c.t == 16">
                            <!-- PHONE NUMBER -->
                            <a :href="'tel:' + c.v">{{ c.v }}</a>
                          </template>
                          <template v-else>
                            <!-- UNKNOWN -->
                            {{ c.v }}
                          </template>
                        </v-list-item-subtitle>
                      </v-list-item-content>
                    </v-list-item>
                  </v-list>
                </v-card>
              </v-col>
            </v-row>
          </template>
        </v-data-iterator>
      </template>
    </v-card>
  </div>
</template>

<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*  Xeslint-disable    */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
import relativeDatefilterCalculator from "../api/relative-date-filter-calculator.js";
const MAX_TEXT_COLUMN_LENGTH = 1024;
export default {
  data() {
    return {
      loading: true,
      dataTablePagingOptions: {},
      lastDataTablePagingOptions: {},
      listViewId: 0,
      listView: undefined,
      selectLists: {
        listViews: []
      },
      headers: [],
      serverColumns: [],
      totalRecords: 0,
      records: [],
      rowsPerPageItems: [5, 10, 25, 50, 100],
      selected: [],
      formState: {
        ready: false,
        dirty: false,
        valid: true,
        readOnly: false,
        loading: true,
        errorBoxMessage: undefined,
        appError: undefined,
        serverError: {}
      }
    };
  },
  props: {
    apiBaseUrl: {
      type: String,
      default: "data-list"
    },
    formKey: String,
    dataListKey: String,
    metaView: {
      type: String,
      default: undefined
    },
    clientCriteria: {
      type: String,
      default: undefined
    },
    showSelect: {
      type: Boolean,
      default: false
    },
    singleSelect: {
      type: Boolean,
      default: false
    },
    reload: {
      type: Boolean,
      default: false
    },
    ridColumnOpenable: {
      type: Boolean,
      default: true
    }
  },
  watch: {
    dataTablePagingOptions: {
      handler() {
        //{ "page": 1, "itemsPerPage": 10, "sortBy": [], "sortDesc": [], "groupBy": [], "groupDesc": [], "mustSort": false, "multiSort": false }
        //this code works around some weird bug that causes visible items to be selected in grid (only, not in actual selected array, just a visual thing)
        // when breakpoint is switched between wide and narrow either way.  No idea why it happens but this fixes that issue and also ensures that there are no

        //spurious fetches happening just because the view has changed
        //See what has changed and record it for processing
        let sortHasChanged = !(
          window.$gz.util.isEqualArraysOfPrimitives(
            this.dataTablePagingOptions.sortBy,
            this.lastDataTablePagingOptions.sortBy
          ) &&
          window.$gz.util.isEqualArraysOfPrimitives(
            this.dataTablePagingOptions.sortDesc,
            this.lastDataTablePagingOptions.sortDesc
          )
        );

        let pagingHaschanged = !(
          this.lastDataTablePagingOptions.page ==
            this.dataTablePagingOptions.page &&
          this.lastDataTablePagingOptions.itemsPerPage ==
            this.dataTablePagingOptions.itemsPerPage
        );

        if (!pagingHaschanged && !sortHasChanged) {
          //no effective change, return
          console.log("NO CHANGE");
          return;
        }
        if (pagingHaschanged) {
          console.log("PAGING Has changed");
        }
        if (sortHasChanged) {
          console.log("SORT Has changed");
        }
        //has changed something important so refetch and put a pin in last paging settings for next time
        this.getDataFromApi();
        this.lastDataTablePagingOptions = this.dataTablePagingOptions;
      },
      deep: true
    },
    selected: function(newValue, oldValue) {
      if (!newValue) {
        return;
      }
      if (newValue.length != oldValue.length) {
        this.handleSelectChange();
      }
    },
    reload: function(newValue, oldValue) {
      if (newValue != oldValue) {
        this.getDataFromApi(true);
      }
    }
  },
  methods: {
    //Used by narrow view to get the "header" text for a column based on the column key
    getHeaderText(key) {
      //key format is row-column e.g."500-2"
      let columnIndex = key.split("-")[1];
      let header = this.headers[columnIndex];
      if (header && header.text) {
        return header.text;
      }
      return "";
    },
    refresh() {
      this.getDataFromApi();
    },
    getDataListSelection(ayaType) {
      let vm = this;
      //called when parent form needs the selected id's or the list view options needed to rehydrate the entire list of id's in the same order and filter
      //i.e. for reporting, batch operations etc

      let selectedRowIds = [];
      //get selected row id's if there are selections else get all row id's
      //called into from parent for things like reporting or batch ops
      //selected or records both are arrays with an id property that contains the row id conveniently for this method
      if (vm.selected.length > 0) {
        //return row id's of selected items only
        selectedRowIds = vm.selected.map(z => {
          return z.id;
        });
        selectedRowIds.reverse();
      }
      /*     public AyaType ObjectType { get; set; }
            public long[] SelectedRowIds { get; set; }      
            public string DataListKey { get; set; }      
            public string ListView { get; set; }//optional, if null or empty will use default list view built into DataList
      */
      return {
        ObjectType: ayaType,
        selectedRowIds: selectedRowIds,
        dataListKey: vm.dataListKey,
        listView: untokenizeListView(vm.listView),
        clientCriteria: vm.clientCriteria
      };
    },
    handleSelectChange() {
      //due to making own template for items need to handle singleselect which only affects if select all checkbox at top is visible when making own item template
      if (this.singleSelect) {
        this.selected.splice(0, this.selected.length - 1);
      }
      //emit event to parent form of selected rows
      //Note vm this bubbles up all the columns of all the selected rows
      //so, to be more efficient for now will just send the ID's until I see a need for other shit
      //de-lodash
      //this.$emit("selection-change", window.$gz. _.map(this.selected, "id"));

      this.$emit(
        "selection-change",
        this.selected.map(z => z.id)
      );
    },
    editListView() {
      this.$router.push({
        name: "ay-data-list-column-view",
        params: {
          dataListKey: this.dataListKey,
          formKey: this.formKey,
          listViewId: this.listViewId
        }
      });
    },
    resetListView: function() {
      let vm = this;
      vm.listViewId = 0;
      vm.listView = undefined;
      vm.dataTablePagingOptions.page = 1;
      saveFormSettings(vm);
      //needs to show as temp unsaved filter or overridden or something
    },

    listViewChanged: async function() {
      let vm = this;

      //If listview had changed it can only have changed *away* from the unsaved filter item if it's present so just remove the unsaved filter item if it exists

      //DANGER DANGER WARNING: if using lodash to remove item it might mess with vue reactivity
      //this example does remove from the array and does update a plain mustache rendition of it on the page but doesn't update the select itself
      //whereas using the native javascript array splice function *does* update the select because vue wraps splice and other native methods specifically
      //so it can properly update the dom
      // window.$gz. _.remove(vm.selectLists.listViews, function(n) {
      //   return n.id == -1;
      // });

      //first prevent the following changes from triggering a fetch
      vm.loading = true;
      //always go back to page one on a change of data list view
      vm.dataTablePagingOptions.page = 1;

      for (let i = vm.selectLists.listViews.length - 1; i >= 0; i--) {
        if (vm.selectLists.listViews[i].id === -1) {
          vm.selectLists.listViews.splice(i, 1);
        }
      }

      if (vm.listViewId == 0) {
        //default view, no saved, no cached
        vm.listView = undefined;
        saveFormSettings(vm);
      } else if (vm.listViewId > 0) {
        await fetchListView(vm);
        saveFormSettings(vm);
      }

      //fetch data because listview has changed
      vm.loading = false;
      vm.getDataFromApi();
    },
    gridCellButtonClick(key, i, ot) {
      let typeToOpen = null;
      //if ot is not null or undefined then *that* is the Openable Type otherwise it's in the header data
      if (ot) {
        typeToOpen = ot;
      } else {
        //translate key to actual object type from header data
        //key format is row-column e.g."500-2"
        //get the datatype of the column which matches the server columns array index
        typeToOpen = this.serverColumns[key.split("-")[1]].ay;
      }
      //i is the actual AyaNova index of vm record so we have all we need to open vm object
      window.$gz.eventBus.$emit("openobject", { type: typeToOpen, id: i });
    },
    cellStyle(c) {
      //does it have additional styling?
      //c.cst is styling added here by data builder in the form of an object with props set for compatibility with Vue
      //c.clr is a specific color coming from the server for items that support colors (reminders etc)
      if (c.cst || c.clr) {
        let clrBit = {};
        if (c.clr) {
          if (c.clr[0] != "#") {
            c.clr = "#" + c.clr;
          }
          clrBit["border-left"] = `5px solid ${c.clr}`; //warning: don't put a semicolon after any of these styles, it will then ignore the whole bit
        }
        return { ...c.cst, ...clrBit };
      } else {
        return null;
      }
    },
    async getDataFromApi(deSelectAll) {
      let vm = this;

      if (vm.loading) {
        return;
      }
      //start with defaults
      let listOptions = {
        DataListKey: vm.dataListKey,
        Limit: 5,
        Offset: 0
      };

      //calculate paging based on settings
      const { page, itemsPerPage } = vm.dataTablePagingOptions;
      if (itemsPerPage && itemsPerPage > 0) {
        listOptions.Offset = (page - 1) * itemsPerPage;
        listOptions.Limit = itemsPerPage;
      }

      vm.loading = true;
      //Weird bug that causes grid to show all items selected after async method runs below to fetch data
      //this puts a pin in it then resets it after the fetch is completed
      let preSelected = [];
      if (!deSelectAll) {
        //preserve selections
        preSelected = [...vm.selected];
      }

      try {
        //untokenize ListView date token criteria (if there are any)
        let untokenizedListView = untokenizeListView(vm.listView);

        let res = await window.$gz.api.upsert(vm.apiBaseUrl, {
          offset: listOptions.Offset,
          limit: listOptions.Limit,
          dataListKey: vm.dataListKey,
          listView: untokenizedListView,
          clientCriteria: vm.clientCriteria
        });

        if (res.error) {
          vm.formState.serverError = res.error;
          window.$gz.form.setErrorBoxErrors(vm);
        } else {
          //Save a copy of the server columns data for handling button clicks etc later
          vm.serverColumns = res.columns;
          //Make sure the translation keys are fetched
          await fetchTranslatedHeaderNames(res.columns); //Note can use await here because it's wrapped inside an async function call, it will wait then resume next stuff below

          await fetchEnums(res.columns);

          //build vm.headers here
          vm.headers = buildHeaders(res.columns);

          //Post process data here and then set vm.records
          vm.records = buildRecords(
            res.data,
            res.columns,
            vm.ridColumnOpenable
          );

          vm.totalRecords = res.totalRecordCount;
          //Put back selected items
          vm.selected = [...preSelected];

          //persist the paging options and listview stuff so user sees same page and list on refresh or leave and return scenario
          saveFormSettings(vm);
        }
      } catch (err) {
        window.$gz.errorHandler.handleFormError(err, vm);
      } finally {
        vm.loading = false;
        vm.formState.ready = true;
      }
    }
  },
  async created() {
    //get pick lists
    let vm = this;
    await initForm(vm);
    //rehydrate last form settings
    //loadFormSettings(vm);
    vm.loading = false;
    vm.getDataFromApi();
  },
  beforeUpdate() {
    if (this.clientCriteria != null && this.listViewId != 0) {
      this.resetListView();
    }
  }
};

//Called by getDataFromApi on retrieval of list with columnData
function buildHeaders(columnData) {
  //iterate columns, build headers and return
  if (!columnData) {
    return [];
  }
  let ret = [];
  //iterate the columns
  for (let i = 0; i < columnData.length; i++) {
    let cm = columnData[i];
    let h = {};
    h.text = window.$gz.translation.get(cm.cm);
    h.value = "columns.c" + i.toString(); //+".v";
    if (i == 0) {
      h.align = "start";
    }
    ret.push(h);
  }

  return ret;
}

//Called by getDataFromApi on retrieval of list with columnData
function buildRecords(listData, columndefinitions, ridColumnOpenable) {
  //iterate data, build each object keyed with index name and display set to correct translated filter and then return

  let ret = [];
  if (!listData) {
    return ret;
  }

  //cache display format stuff
  let timeZoneName = window.$gz.locale.getBrowserTimeZoneName();
  let languageName = window.$gz.locale.getBrowserLanguages();
  let hour12 = window.$gz.store.state.userOptions.hour12;
  let currencyName = window.$gz.store.state.userOptions.currencyName;

  //this will cache the first time it's required (if required)
  let availableRoles = null;

  //comes as an array of arrays, needs to leave as an array of objects representing each row
  for (let iRow = 0; iRow < listData.length; iRow++) {
    let row = listData[iRow];
    //iterate row and build object representing row data keyed to index

    //container object for row
    //id will be set later when code below encounters the id column which could be in any position but is identified by it's rid property
    let o = { id: undefined, columns: {} };

    for (let iColumn = 0; iColumn < row.length; iColumn++) {
      let column = row[iColumn];

      //rowId?
      if (column.rid) {
        o.id = column.i;
      }

      let dataType = columndefinitions[iColumn].dt;
      let display = column.v;
      let cstStyle = null; //custom additional styling here in grid e.g. negative numbers etc

      /*
         public enum UiFieldDataType : int
    {
        NoType = 0,
        DateTime = 1,
        Date = 2,
        Time = 3,
        Text = 4,
        Integer = 5,
        Bool = 6,
        Decimal = 7,
        Currency = 8,
        Tags = 9,
        Enum = 10,
        EmailAddress = 11,
        HTTP = 12,
        InternalId = 13,
        MemorySize=14,
         TimeSpan=15,
        PhoneNumber=16//this is so client can dial directly,
        Roles=17

    }
      */
      if (display != null) {
        switch (dataType) {
          case 1: //datetime format to shortdatetime
            display = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
              display,
              timeZoneName,
              languageName,
              hour12
            );
            if (!window.$gz.locale.dateIsPast(column.v)) {
              cstStyle = { "font-weight": "bold" }; //warning, don't put a semicolon after the style or it will not work
            }
            break;
          case 2: //date only
            display = window.$gz.locale.utcDateToShortDateLocalized(
              display,
              timeZoneName,
              languageName
            );
            if (!window.$gz.locale.dateIsPast(column.v)) {
              cstStyle = { "font-weight": "bold" }; //warning, don't put a semicolon after the style or it will not work
            }
            break;
          case 3: //time only
            display = window.$gz.locale.utcDateToShortTimeLocalized(
              display,
              timeZoneName,
              languageName,
              hour12
            );
            break;
          case 4: //text
            if (display.length > MAX_TEXT_COLUMN_LENGTH) {
              display = display.substring(0, MAX_TEXT_COLUMN_LENGTH) + "...";
            }
            break;
          case 5: //Integer
            //display is already set, just some styling to do
            if (window.$gz.util.isNegative(column.v)) {
              cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
            }
            break;
          case 7: //decimal
            display = window.$gz.locale.decimalLocalized(display, languageName);
            if (window.$gz.util.isNegative(column.v)) {
              cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
            }
            break;
          case 8: //currency
            display = window.$gz.locale.currencyLocalized(
              display,
              languageName,
              currencyName
            );
            if (window.$gz.util.isNegative(column.v)) {
              cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
            }
            break;
          case 9: //tags
            if (display && display.length > 0) {
              display = display.join(", ");
            } else {
              display = "";
            }

            break;
          case 10: //enum
            display = window.$gz.enums.get(
              columndefinitions[iColumn].et,
              display
            );
            break;
          case 14: //MemorySize (file size)
            display = window.$gz.locale.humanFileSize(
              display,
              languageName,
              false,
              2
            );
            break;
          case 17: //Authorization Roles
            if (availableRoles == null) {
              availableRoles = window.$gz.enums.getSelectionList(
                "AuthorizationRoles"
              );
            }
            let roles = display;
            let roleNames = [];

            if (roles != null && roles != 0) {
              for (let i = 0; i < availableRoles.length; i++) {
                let role = availableRoles[i];
                if (!!(roles & role.id)) {
                  roleNames.push(role.name);
                }
              }
            }
            display = roleNames.join(", ");

            break;
          default:
          //do nothing, allow it to stay as is (checkbox, plain text etc)
        }
      }
      //build the row column object vm will be used by the datatable
      let columnObject = {
        v: display,
        t: dataType,
        key: iRow + "-" + iColumn
      };

      //is the source dtalist field openable?  If so it will have an i property set for it's ID and we already know the types to open from the column headers data
      //so between the two we can make a clickable button in the grid vm triggers a function with the column index and the id and vm in turn will bubble up the event to open vm
      //object
      if (column.i) {
        columnObject["i"] = column.i;
      }

      if (!ridColumnOpenable && column.rid) {
        columnObject["nopen"] = true;
      }

      //Is there an overriding openable type already defined?
      //(this is used by dynamic columns with different object type and id's that are openable like attachments and event logs and Review list etc)
      if (column.ot) {
        columnObject["ot"] = column.ot;
      }

      //rgba color to decorate field with
      if (column.clr) {
        columnObject["clr"] = column.clr;
      }

      //additional styling
      if (cstStyle) {
        columnObject["cst"] = cstStyle;
      }

      o.columns["c" + iColumn.toString()] = columnObject;
    }
    ret.push(o);
  }
  return ret;
}

//////////////////////////////////////////////////////////
//
// Ensures column names are present in translation table
//
async function fetchTranslatedHeaderNames(columnData) {
  if (!columnData) {
    return;
  }
  let headerKeys = [];
  for (let i = 0; i < columnData.length; i++) {
    let cm = columnData[i];
    headerKeys.push(cm.cm);
  }
  //Now fetch all the keys
  await window.$gz.translation.cacheTranslations(headerKeys);
}

//////////////////////////////////////////////////////////
//
// Ensures column enums are present in enums list in store
//
async function fetchEnums(columnData) {
  if (!columnData) {
    return;
  }
  let headerKeys = [];
  for (let i = 0; i < columnData.length; i++) {
    let cm = columnData[i];
    if (cm.et) {
      await window.$gz.enums.fetchEnumList(cm.et);
    }
  }
}

/////////////////////////////////
//
//
async function initForm(vm) {
  await populateSelectionLists(vm);
  await loadFormSettings(vm);
}

////////////////////
//
async function populateSelectionLists(vm) {
  //http://localhost:7575/api/v8/data-list-view/viewlist?ListKey=TestWidgetDataList
  let res = await window.$gz.api.get(
    "data-list-filter/list?ListKey=" + vm.dataListKey
  );
  if (res.error) {
    vm.formState.serverError = res.error;
    window.$gz.form.setErrorBoxErrors(vm);
    //window.$gz.errorHandler.handleFormError(res.error, vm);
  } else {
    vm.selectLists.listViews = res.data;
    window.$gz.form.addNoSelectionItem(vm.selectLists.listViews);
  }
}

//////////////////////////////////////////////////////////
//
// Fetch and cache list view
//
async function fetchListView(vm) {
  if (!vm.listViewId) {
    return;
  }
  let res = await window.$gz.api.get("data-list-view/" + vm.listViewId);
  if (res.error) {
    if (res.error.code && res.error.code == "2010") {
      //list not found, probably deleted by another user
      //or on another browser and this one still had it as the last used list view
      vm.listViewId = 0; //indicate no list view
      return;
    } else {
      vm.formState.serverError = res.error;
      window.$gz.form.setErrorBoxErrors(vm);
    }
    // window.$gz.errorHandler.handleFormError(res.error, vm);
  } else {
    vm.listView = res.data.listView;
  }
}

////////////////////
//
function saveFormSettings(vm) {
  let unsavedlv = vm.listView;
  let cachedlv = vm.listView;

  if (vm.listViewId == 0) {
    //we aren't using any listview
    unsavedlv = undefined;
    cachedlv = undefined;
  }

  if (vm.listViewId == -1) {
    //we have an unsaved one in use so there is no need for a cached one
    cachedlv = undefined;
  }

  if (vm.listViewId > 0) {
    //we are using a saved lv so save cached one and clear anything in unsaved one
    unsavedlv = undefined;
  }

  window.$gz.form.setFormSettings(vm.formKey, {
    temp: { page: vm.dataTablePagingOptions.page, cachedListView: cachedlv },
    saved: {
      itemsPerPage: vm.dataTablePagingOptions.itemsPerPage,
      dataTable: { listViewId: vm.listViewId, unsavedListView: unsavedlv }
    }
  });
}

////////////////////
//
function loadFormSettings(vm) {
  let formSettings = window.$gz.form.getFormSettings(vm.formKey);

  //process SAVED formsettings
  if (formSettings.saved) {
    if (formSettings.saved.itemsPerPage) {
      vm.dataTablePagingOptions.itemsPerPage = formSettings.saved.itemsPerPage;
    }
    if (formSettings.saved.dataTable.listViewId != null) {
      vm.listViewId = formSettings.saved.dataTable.listViewId;
    }
    if (vm.listViewId == 0) {
      //default view, not unsaved and not saved
      vm.listView = undefined;
    }
    if (vm.listViewId == -1) {
      //-1 is code for unsaved list view
      //check if there is a local copy of a listview vm was edited but not saved
      if (formSettings.saved.dataTable.unsavedListView != null) {
        //add UNSAVED FILTER if -1

        vm.selectLists.listViews.unshift({
          name: vm.$ay.t("FilterUnsaved"),
          id: -1
        });

        vm.listView = formSettings.saved.dataTable.unsavedListView;
      } else {
        //listviewid is for unsaved but we have no unsaved so fix that up
        vm.listView = undefined;
        vm.listViewId = 0;
      }
    }
  }

  //process TEMP form settings

  if (formSettings.temp && formSettings.temp.page) {
    vm.dataTablePagingOptions.page = formSettings.temp.page;
  }

  //check for cached local copy of saved list view in use
  if (vm.listViewId > 0) {
    //0=no list view, -1=unsaved list view so any number greater than zero means there sb a cached local copy of a saved list view
    if (formSettings.temp && formSettings.temp.cachedListView != null) {
      vm.listView = formSettings.temp.cachedListView;
    } else {
      //fetch it and cache it
      return fetchListView(vm, vm.listViewId);
    }
  }
}

/*//EXAMPLE FROM INTEGRATION TEST OF BETWEEN TWO DATES
// dynamic fitem = new JObject();
// fitem.op = Util.OpGreaterThanOrEqualTo;
// fitem.value = new DateTime(2019, 3, 12, 10, 0, 0).ToOffsetAdjustedUniversalTime();
// items.Add(fitem);

// fitem = new JObject();
// fitem.op = Util.OpLessThanOrEqualTo;
// fitem.value = new DateTime(2019, 3, 12, 11, 0, 0).ToOffsetAdjustedUniversalTime();
// items.Add(fitem); */
////////////////////
//
function untokenizeListView(lvJson) {
  //if it has one or more tokens
  //iterate the array and build a new array with substituted tokens with the correct date and time in them

  //format of a date token filter
  //[{"fld":"widgetname"},{"fld":"widgetstartdate","filter":{"items":[{"op":"=","value":"*past90days*","token":true}]}},{"fld":"widgetenddate"}]
  if (lvJson == null) {
    return lvJson;
  }
  //See if it has any date tokens
  if (lvJson.indexOf('"token":true') == -1) {
    return lvJson;
  }

  //we have one or more tokens, substitute them in the filter array
  let ret = [];
  let lv = JSON.parse(lvJson);

  //iterate the incoming and copy to the outgoing directly
  //except if a date token filter then substitute our own filter object in place
  for (let ilv = 0; ilv < lv.length; ilv++) {
    //listview object
    let lvo = lv[ilv];
    //instantiate return object
    let reto = {};
    //copy over field name to return object
    reto.fld = lvo.fld;
    //sort?
    if (lvo.sort) {
      reto.sort = lvo.sort;
    }
    //does it have a filter?
    if (lvo.filter) {
      //yes, so copy / transform as required

      //create an empty filter items array on return object
      reto.filter = { items: [] };

      //"any" property set?
      if (lvo.filter.any) {
        reto.filter.any = true;
      }

      //iterate the filter items in the source lvo object
      for (let j = 0; j < lvo.filter.items.length; j++) {
        //get this filter item
        let fi = lvo.filter.items[j];
        //no token shortcut
        if (!fi.token) {
          //just copy it out
          reto.filter.items.push(fi);
        } else {
          //it has a date token so let's build it out
          //filter item value contains the token, op is always equals
          let filterDates = relativeDatefilterCalculator.tokenToDates(fi.value);
          //create and add a new filter item for each not undefined value
          //{ after: undefined, before: undefined }
          //AFTER DATE?
          if (filterDates.after) {
            reto.filter.items.push({
              op: ">", //was GreaterThanOrEqualTo but that doesn't make sense, it's only greater than was seeing wrong return data as relative date filter already adjusts for boundaries
              value: filterDates.after
            });
          }
          //BEFORE DATE?
          if (filterDates.before) {
            reto.filter.items.push({
              op: "<", //was LessThanOrEqualTo but see above for after ===-----^
              value: filterDates.before
            });
          }
        }
      }

      //end of has filter if condition
    }

    //push the return object into the return array
    ret.push(reto);
    //end of iterate lv loop
  }

  return JSON.stringify(ret);
} //[{"fld":"widgetname"},{"fld":"widgetstartdate","filter":{"items":[{"op":"=","value":"*past90days*","token":true}]}},{"fld":"widgetenddate"}]
</script>
