<template>
  <div class="relation-picker">
    <div :class="['form-control', { readonly: readonly }]" @click="showPopup" ref="popup-anchor">
      <template v-if="!multi && valueCopy">
        <picker-value :ondelete="() => (valueCopy = null)" :can-delete="!required && !readonly">
          <router-link @click.native.stop :to="{ name: `${module}-editor`, params: { id: valueCopy } }">
            <component :is="itemComponent" :item="item" :display-field="displayField" :relationItemSchema="crudItemSchema"></component>
          </router-link>
        </picker-value>
      </template>

      <template v-if="multi">
        <draggable :list="valueCopy">
          <picker-value
            v-for="(guid, key, index) in valueCopy"
            :key="key"
            :ondelete="() => valueCopy.splice(index || key, 1)"
            :can-delete="!readonly"
            class="multi"
          >
            <span v-if="!readonly" class="vuedraggable-handle icon-paragraph-justify"></span>
            <router-link @click.native.stop :to="{ name: `${module}-editor`, params: { id: guid } }">
              <component
                :is="itemComponent"
                :item="indexed[guid]"
                :display-field="displayField"
                :relationItemSchema="crudItemSchema"
              ></component>
            </router-link>
          </picker-value>
        </draggable>
      </template>

      <input
        v-if="!readonly"
        class="search form-control"
        ref="search"
        v-model="searchQuery"
        :placeholder="displayField && !readonly ? placeholder : ''"
        :disabled="!displayField || readonly"
        @focus="focusSearch"
        @keydown.down.prevent="down"
        @keydown.up.prevent="up"
        @keydown.esc.prevent="search = ''"
      />
    </div>
    <popup ref="popup" overflow>
      <div class="empty-tester">
        <label class="language-filter" v-if="crudItemSchema && crudItemSchema.isTranslatable && languageInfos.length > 1">
          Taalfilter:
          <select v-model="language" class="form-control">
            <option value>Alle</option>
            <option v-for="option in languageInfos" :value="option.language" v-bind:key="option.language" :disabled="option.isDisabled">
              {{ option.displayName }}
            </option>
          </select>
        </label>
        <span v-if="initialItemsAmount && searchItems.length" class="amount-info">
          {{ itemsAmount }} {{ itemsAmount === 1 ? 'resultaat' : 'resultaten' }}
        </span>
        <div :class="type" v-for="(item, key) in searchItems" :key="key">
          <label>
            <input
              type="radio"
              v-if="!multi"
              v-model="valueCopy"
              :value="item.id"
              :name="'radio' + _uid"
              @focus="scrollPopup"
              @keydown.down.prevent="down"
              @keydown.up.prevent="up"
              @keydown.enter.prevent="$event.target.click()"
            />

            <input
              type="checkbox"
              v-if="multi"
              v-model="valueCopy"
              :value="item.id"
              :name="'radio' + _uid"
              @focus="scrollPopup"
              @keydown.down.prevent="down"
              @keydown.up.prevent="up"
              @keydown.enter.prevent="$event.target.click()"
            />
            <component
              :is="itemComponent"
              :item="indexed[item.id]"
              :display-field="displayField"
              :relationItemSchema="crudItemSchema"
            ></component>
          </label>
        </div>
        <span v-if="searchItems.length && itemsAmount > initialItemsAmount" class="amount-info">
          De eerste {{ initialItemsAmount }} resultaten zijn zichtbaar, gebruik de zoekfunctie voor de overige resultaten.
        </span>
      </div>
    </popup>
  </div>
</template>

<style lang="scss" scoped>
.relation-picker {
  position: relative;
  user-select: none;

  .checkbox,
  .radio {
    label {
      display: flex;
      gap: 4px;
    }
  }
}

.form-control {
  padding: 1px 4px;
  display: flex;
  height: auto;
  flex-wrap: wrap;
}

.checkbox:last-child,
.radio:last-child {
  margin-bottom: 0;
}

.language-filter {
  display: flex;
  align-items: center;
  margin-bottom: 15px;

  select {
    margin-left: 5px;
    font-size: 14px;
  }
}

.amount-info {
  display: flex;
  margin-bottom: 10px;
  font-weight: 700;
}

.search {
  border: none;
  outline: none;
  margin: 2px;
  padding: 3px 7px;
}

.empty-tester:empty::after {
  content: '(geen)';
  display: block;
  opacity: 0.5;
  text-align: center;
}

.checkbox,
.radio {
  label {
    display: flex;
    gap: 4px;
  }
}
</style>

<script>
import _debounce from 'lodash/debounce';
import draggable from 'vuedraggable';
import { ValueSyncMixin } from '../value-sync-mixin.js';
import CrudHelpers from '../../crud/crud-helpers';
import { mapState } from 'vuex';
import popup from '../popup.vue';
import pickerValue from '../picker-value.vue';
import { ITEMS_PER_PAGE } from '../../api/crud-api';
import relationPickerItemDefault from './relation-picker-item-default.vue';

export default {
  mixins: [ValueSyncMixin],
  inject: ['getCurrentLanguage'],
  props: {
    module: String,
    multi: {
      type: Boolean,
      default: false,
      docs: `Optie of je 1 of meerdere items kan kiezen.`,
      demoValue: true
    },
    required: {
      type: Boolean,
      default: false,
      docs: `Optie of je ook geen item(s) kan kiezen.`
    },
    placeholder: {
      type: String,
      default: 'zoek...',
      docs: `Hulptekst toevoegen wanneer nog geen waarde is ingevuld`
    },
    readonly: {
      type: Boolean,
      default: false,
      docs: `Optie of je de item(s) kan aanpassen.`
    },
    value: {
      validator: (value) => value === null || typeof value === 'string' || value instanceof Array,
      docs: `Een string als <code>multi</code> uitstaat, anders een array van strings.`,
      demoValue: () => []
    },
    'display-component': {
      docs: `Component die een item (via een <code>item</code> prop) uit <code>source</code> weergeeft.`
    }
  },
  data() {
    return {
      searchQuery: '',
      selectedIndex: -1,
      language: null,
      searchItems: [],
      initialItemsAmount: ITEMS_PER_PAGE
    };
  },
  mounted() {
    // fetch data from server
    if (this.multi && this.value) {
      this.$store.dispatch(`${this.module}/getItemByIds`, this.value);
    } else if (!this.multi && this.value) {
      // should be of type 'GetItemByIdPayload', when TS is added as lang for this file
      const payload = { id: this.value, ignoreCache: false };
      this.$store.dispatch(`${this.module}/getItemById`, payload);
    }
  },
  computed: {
    ...mapState('translations', ['languageInfos']),
    ...mapState({
      // this syntax is needed to get 'this' working in mapState
      itemsCache(state) {
        return state[this.module].itemsCache;
      },
      fields(state) {
        return state[this.module].overview.fields;
      },
      crudItemSchema(state) {
        return state[this.module].schema;
      }
    }),
    item() {
      if (!this.multi && this.value) {
        return this.itemsCache.find((item) => item.id === this.value);
      }

      return null;
    },
    indexed() {
      var indexed = {};
      this.itemsCache.forEach((item) => {
        indexed[item.id] = item;
      });
      return indexed;
    },
    type() {
      return this.multi ? 'checkbox' : 'radio';
    },
    itemComponent() {
      return this.displayComponent || (this.crudItemSchema && this.$options.components.relationPickerItemDefault);
    },
    displayField() {
      return CrudHelpers.getDisplayFieldForSchemaProperties(this.crudItemSchema ? this.crudItemSchema.properties : undefined);
    },
    itemsAmount() {
      const results = this.$store.getters[`${this.module}/result`]();
      return results ? results.totalQueryItems : 0;
    }
  },
  watch: {
    // overwrite valueCopy from ValueSyncMixin
    async valueCopy(newValue) {
      this.$emit('input', newValue); // vue syntax: update prop 'value' in the parent

      if (this.multi) {
        // Reposition if visible
        if (this.$refs.popup.popupVisible) this.showPopup();
      } else {
        this.hidePopup();
      }
    },
    selectedIndex() {
      var selected = this.$el.querySelectorAll('.popup label')[this.selectedIndex];
      if (selected) selected.focus();
    },
    searchQuery() {
      this.showPopup();
      this.search();
    },
    language() {
      this.search();
    }
  },
  methods: {
    showPopup() {
      if (this.language == null && this.crudItemSchema && this.crudItemSchema.isTranslatable) {
        this.language = this.getCurrentLanguage();
      }

      if (!this.readonly) {
        this.$refs.popup.show(this.$refs['popup-anchor']);
      }
    },
    hidePopup() {
      this.$refs.popup.hide();
    },
    scrollPopup(evt) {
      var el = evt.target.parentNode; // select label
      var elBcr = el.getBoundingClientRect();
      for (var anc = el.parentNode; anc.offsetHeight == anc.scrollHeight; anc = anc.parentNode) {
        // do nothing;
      }
      var scrollBcr = anc.getBoundingClientRect();
      var scollbarSize = anc.offsetHeight - anc.clientHeight;
      var bottom = scrollBcr.bottom - scollbarSize;
      if (elBcr.bottom > bottom) {
        anc.scrollTop += elBcr.bottom - bottom;
      } else if (elBcr.top < scrollBcr.top) {
        anc.scrollTop += elBcr.top - scrollBcr.top;
      }
    },
    up() {
      this.selectedIndex--;
      if (this.selectedIndex < 0) this.selectedIndex = this.$el.querySelectorAll('.popup label').length - 1;
    },
    down() {
      this.selectedIndex++;
      if (this.selectedIndex >= this.$el.querySelectorAll('.popup label').length) this.selectedIndex = 0;
    },
    search: _debounce(async function () {
      const searchPayload = { query: this.searchQuery, disableLoading: true };
      if (this.language && this.crudItemSchema.isTranslatable) {
        searchPayload.languageFilter = this.language;
      }

      const searchItems = await this.$store.dispatch(`${this.module}/fetchItems`, searchPayload);
      this.searchItems = searchItems;
    }, 300),
    focusSearch() {
      this.selectedIndex = -1;
      this.showPopup();
      this.search();
    }
  },
  components: {
    popup,
    relationPickerItemDefault,
    draggable,
    pickerValue
  }
};
</script>
