<template>
  <div>
    <v-autocomplete
      dense
      :value="value"
      :readonly="readonly"
      :disabled="disabled"
      return-object
      :items="searchResults"
      :label="label"
      :hint="hint"
      persistent-hint
      item-text="name"
      item-value="id"
      item-disabled="!active"
      :rules="rules"
      :error-messages="errorMessages"
      :loading="fetching"
      :placeholder="$ay.t('Search')"
      :search-input.sync="searchEntry"
      :filter="customFilter"
      hide-no-data
      :clearable="!readonly && canClear"
      :no-filter="isTagFilter"
      :append-icon="errorIcon"
      @input="selectionMade($event)"
      @click:append="handleErrorClick"
      @mousedown="dropdown"
    >
      <template v-if="hasError()" v-slot:prepend-item>
        <div class="pl-2">
          <span class="error--text"> {{ entryError }}</span>
        </div>
      </template>

      <template v-slot:prepend>
        <v-icon @click="handleEditClick">{{ editIcon() }}</v-icon>
      </template>
    </v-autocomplete>
  </div>
</template>
<script>
export default {
  props: {
    value: {
      type: Number,
      default: null
    },
    rules: { type: Array, default: undefined },
    errorMessages: { type: Array, default: null },
    readonly: { type: Boolean, default: false },
    disabled: { type: Boolean, default: false },
    ayaType: {
      type: Number,
      default: 0
    },
    includeInactive: {
      type: Boolean,
      default: false
    },
    showEditIcon: {
      type: Boolean,
      default: false
    },
    allowNoSelection: {
      type: Boolean,
      default: true
    },
    canClear: { type: Boolean, default: true },
    label: { type: String, default: "" },
    variant: {
      type: String,
      default: null
    },
    template: {
      type: String,
      default: null
    },
    hint: {
      type: String,
      default: null
    }
  },
  data() {
    return {
      searchResults: [],
      entryError: null,
      searchEntry: null,
      lastSelection: null,
      fetching: false,
      isTagFilter: false,
      errorIcon: null,
      initialized: false
    };
  },
  watch: {
    ayaType(val, oldVal) {
      if (val != oldVal && oldVal != null) {
        //change of type so clear out the list
        this.searchResults = [];
        this.searchEntry = null;
        this.lastSelection = null;
        this.initialized = false;
      }
    },
    value() {
      this.fetchValueIfNotPresent();
    },
    searchEntry(val) {
      this.clearErrors();
      //if the search entry is in the results list then it's a drop down selection not a typed search so bail
      for (let i = 0; i < this.searchResults.length; i++) {
        if (this.searchResults[i].name == val) {
          return;
        }
      }
      if (!val || this.fetching || !this.initialized) {
        if (!this.initialized) {
          this.$nextTick(() => {
            this.initialized = true;
          });
        }
        return;
      }
      this.doSearch(val);
    },
    entryError() {
      if (this.hasError()) {
        this.errorIcon = "$ayiQuestionCircle";
      } else {
        this.errorIcon = null;
      }
    }
  },
  created() {
    this.fetchValueIfNotPresent();
  },
  methods: {
    //Get full selection for use on forms where we need the
    //full selection including name, active, id
    getFullSelectionValue() {
      return this.lastSelection;
    },
    hasError: function() {
      return this.entryError != null;
    },
    clearErrors: function() {
      this.entryError = null;
    },
    handleErrorClick: function() {
      //open help nav for picklist
      window.$gz.eventBus.$emit("menu-click", {
        key: "app:help",
        data: "ay-start-form-autocomplete/#searching"
      });
    },
    editIcon: function() {
      if (!this.showEditIcon) {
        return null;
      }
      return "$ayiEdit";
    },
    handleEditClick: function() {
      let idToOpen = 0;
      if (this.lastSelection != null && this.lastSelection.id) {
        idToOpen = this.lastSelection.id;
      }
      window.$gz.eventBus.$emit("openobject", {
        type: this.ayaType,
        id: idToOpen
      });
    },
    selectionMade(e) {
      this.clearErrors();
      if (e == undefined) {
        //this will happen when clear clicked
        //simulate empty selection:
        e = window.$gz.form.getNoSelectionItem(true);
      }
      this.lastSelection = e;
      //this is required for the control to update and parent form to detect it
      this.$emit("input", e.id);
      //this is sometimes required for forms that need more than the ID (contract etc)
      this.$emit("update:name", e.name);
      this.$emit("update:active", e.active);
    },
    fetchValueIfNotPresent() {
      //is there a value that might require fetching?
      const val = this.value;
      if (val == null) {
        return;
      }
      //check if it's in the list of items we have here
      for (let i = 0; i < this.searchResults.length; i++) {
        if (this.searchResults[i].id == val) {
          return;
        }
      }
      //is it the no selection item?
      if (val == null) {
        window.$gz.form.addNoSelectionItem(this.searchResults, true);
      } else {
        const pickListParams = {
          ayaType: this.ayaType,
          preselectedIds: [this.value]
        };
        if (this.variant != null) {
          pickListParams["listVariant"] = this.variant;
        }
        if (this.template != null) {
          pickListParams["template"] = this.template;
        }
        this.getList(pickListParams);
      }
    },
    replaceLastSelection() {
      //check if searchResults has last selection, if not then add it back in again
      if (this.lastSelection == null) {
        //it might be initializing
        for (let i = 0; i < this.searchResults.length; i++) {
          if (this.searchResults[i].id == this.value) {
            this.lastSelection = this.searchResults[i];
            return;
          }
        }
        return;
      }
      for (let i = 0; i < this.searchResults.length; i++) {
        if (this.searchResults[i].id == this.lastSelection.id) {
          return; //already there
        }
      }
      //Not there so insert it
      this.searchResults.push(this.lastSelection);
    },
    dropdown() {
      const vm = this;
      //check if we have only the initial loaded item and no selection item
      if (vm.searchResults.length < 3) {
        //get the default list
        vm.getList();
      }
    },
    customFilter(item, queryText) {
      //NOTE: I wanted this to work with tags but all it does is highlight all of each row if tag query is present
      //I guess because it later on attempts to do the highlighting and can't find all the entered query
      //it's not clean so I'm just going to make it only highlight if it's a non tag query for now
      //and do no filtering (highlighting) at all if it's a tag query
      if (queryText.includes(" ") || queryText.startsWith("..")) {
        this.isTagFilter = true;
        return false;
      }
      if (this.$store.state.globalSettings.filterCaseSensitive == true) {
        return item.name.indexOf(queryText) > -1;
      } else {
        //need to do a case insensitive filter and hopefully it mirrors postgres at the backend
        return item.name.toLowerCase().indexOf(queryText.toLowerCase()) > -1;
      }
    },
    getList: async function(pickListParams) {
      if (this.fetching) {
        return;
      }
      this.fetching = true;
      //default params for when called on init
      if (!pickListParams) {
        pickListParams = {
          ayaType: this.ayaType
        };
        if (this.value != null) {
          pickListParams["preselectedIds"] = [this.value];
        }
        if (this.includeInactive) {
          pickListParams["inactive"] = true;
        }
        if (this.variant != null) {
          pickListParams["listVariant"] = this.variant;
        }
        if (this.template != null) {
          pickListParams["template"] = this.template;
        }
      }
      try {
        const res = await window.$gz.api.upsert(
          "pick-list/list",
          pickListParams
        );

        this.fetching = false;
        if (!Object.prototype.hasOwnProperty.call(res, "data")) {
          return Promise.reject(res);
        }
        this.searchResults = res.data;
        if (this.allowNoSelection) {
          window.$gz.form.addNoSelectionItem(this.searchResults, true);
        }
        this.replaceLastSelection();
      } catch (err) {
        window.$gz.errorHandler.handleFormError(err);
        this.fetching = false;
      }
    },
    doSearch: debounce(function(searchFor) {
      //NOTE debounce with a watcher is a bit different, currently it has to be done exactly this way, nothing else will work properly
      //https://vuejs.org/v2/guide/migration.html#debounce-Param-Attribute-for-v-model-removed
      //-----------------

      const vm = this;
      let isATwoTermQuery = false;
      let queryTerms = [];
      //NOTE: empty query is valid; it means get the top 100 ordered by template order
      let emptyQuery = false;
      if (searchFor == null || searchFor == "") {
        emptyQuery = true;
      } else {
        //Pre-process the query to validate and send conditionally
        //get the discrete search terms and verify there are max two
        if (searchFor.includes(" ")) {
          queryTerms = searchFor.split(" ");
          if (queryTerms.length > 2) {
            vm.entryError = vm.$ay.t("ErrorPickListQueryInvalid");
            return;
          }
          isATwoTermQuery = true;
        } else {
          //one term only so push it into array
          queryTerms.push(searchFor);
          //Marker term, will be weeded back out later
          queryTerms.push("[?]");
        }

        //Now vet the terms
        //Is user in mid entry of a second tag (space only?)
        //will appear as an empty string post split
        if (queryTerms[1] == "") {
          //mid entry of a second term, just return
          return;
        }

        //Is user in mid entry of tag query, just bounce back
        if (
          queryTerms[0] == "." ||
          queryTerms[0] == ".." ||
          queryTerms[1] == "." ||
          queryTerms[1] == ".."
        ) {
          return;
        }

        if (isATwoTermQuery) {
          //check that both terms aren't tags
          if (
            //note: de-lodashed here not sure if I need to add a null check or not
            queryTerms[0].startsWith("..") &&
            queryTerms[1].startsWith("..")
          ) {
            vm.entryError = vm.$ay.t("ErrorPickListQueryInvalid");
            return;
          }

          //check that both aren't non-tags
          if (
            !queryTerms[0].startsWith("..") &&
            !queryTerms[1].startsWith("..")
          ) {
            vm.entryError = vm.$ay.t("ErrorPickListQueryInvalid");
            return;
          }
        }
      }

      //build parameter
      const pickListParams = { ayaType: vm.ayaType };
      if (!emptyQuery) {
        let query = queryTerms[0];
        if (queryTerms[1] != "[?]") {
          query += " " + queryTerms[1];
        }
        pickListParams["query"] = query;
      }

      if (vm.includeInactive) {
        pickListParams["inactive"] = true;
      }
      if (vm.variant != null) {
        pickListParams["listVariant"] = vm.variant;
      }
      if (vm.template != null) {
        pickListParams["template"] = vm.template;
      }
      this.getList(pickListParams);
      //------------
    }, 300) //did some checking, 200-300ms seems to be the most common debounce time for ajax search queries
  }
};

//https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_debounce
function debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
    if (immediate && !timeout) func.apply(context, args);
  };
}
</script>
