<template>
  <div class="relation-picker">
    <div class="form-control" @click="showPopup" ref="popup-anchor">
      <template v-if="!multi && valueCopy">
        <span class="value">
          <component :is="itemComponent" :item="indexed[valueCopy]" :display-field="displayField"></component>
          <span class="icon-cross" @click.stop="valueCopy = null" v-if="!required && !readonly"></span>
        </span>
      </template>
      <template v-if="multi">
        <draggable :list="valueCopy">
          <span class="value multi" v-for="(guid, key, index) in valueCopy" :key="key">
            <component :is="itemComponent" :item="indexed[guid]" :display-field="displayField"></component>
            <span class="icon-cross" @click.stop="valueCopy.splice(index || key, 1)" v-if="(!required || valueCopy.length > 1) && !readonly"></span>
          </span>
        </draggable>
      </template>

      <input class="search" ref="search" v-model="search" :placeholder="displayField ? 'zoek...' : ''"
             :readonly="!displayField"
             @focus="focusSearch"
             @keydown.down.prevent="down"
             @keydown.up.prevent="up"
             @keydown.esc.prevent="search = ''">
    </div>
    <popup ref="popup" overflow>
      <div class="empty-tester">
        <div :class="type" v-for="(item, key) in filteredSource" :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"></component>
          </label>
        </div>
      </div>
    </popup>
  </div>
</template>

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

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

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

  .value {
    display: inline-block;
    vertical-align: top;
    margin: 2px;
    padding: 2px 6px;
    background: #eee;
    border: 1px solid #ccc;
    border-radius: 0.25rem;
    overflow: hidden;
    text-overflow: ellipsis;

    &.multi {
      cursor: move;
    }

    .icon-cross {
      font-size: 75%;
    }
  }

  .search {
    border: none;
    outline: none;
    margin: 2px;
    padding: 3px 7px;
    color: #ccc;
    width: 25%;
    align-self: flex-start;

    &:focus {
      color: inherit;
    }
  }

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

<script>
import _orderBy from 'lodash/orderBy';
import moment from 'moment';
import draggable from 'vuedraggable';
import { ValueSyncMixin } from '../value-sync-mixin.js';
import popup from '../popup.vue';
import relationPickerItemDefault from './relation-picker-item-default.vue';

export default {
  mixins: [ValueSyncMixin],
  props: {
    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.`,
    },
    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: () => [],
    },
    source: {
      type: Array,
      required: true,
      docs: `De lijst van items om uit te kiezen.`,
      demoValue: () => [
        { id: 0, name: 'Aap' },
        { id: 1, name: 'Noot' },
        { id: 2, name: 'Mies' },
        { id: 3, name: 'Wim' },
        { id: 4, name: 'Zus' },
        { id: 5, name: 'Jet' },
      ],
    },
    fields: {
      docs: `Fields uit het CMS item waarin gezocht kan worden.`,
      demoValue: () => ({ name: 'name' }),
    },
    'display-field': {
      type: String,
      docs: `Veldnaam van het item om te tonen (als <code>display-component</code> niet wordt gebruikt), op te sorteren en te filteren.`,
      demoValue: 'name',
    },
    'display-component': {
      docs: `Component die een item (via een <code>item</code> prop) uit <code>source</code> weergeeft.`,
    },
  },
  data () {
    return {
      search: '',
      selectedIndex: -1,
    };
  },
  computed: {
    indexed () {
      var indexed = {};
      this.source.forEach(item => {
        indexed[item.id] = item;
      });
      return indexed;
    },
    type () {
      return this.multi ? 'checkbox' : 'radio';
    },
    itemComponent () {
      return this.displayComponent || this.$options.components.relationPickerItemDefault;
    },
    filteredSource () {
      // if there is a custom display component don't order by default displayfield
      return this.displayComponent || !this.displayField
        ? this.source.filter(item => this.getSearchedItems(item))
        : _orderBy(this.source, this.displayField).filter(item => this.getSearchedItems(item));
    },
  },
  watch: {
    // overwrite valueCopy from ValueSyncMixin
    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();
    },
    search () {
      // Reposition
      this.showPopup();
    },
  },
  methods: {
    getSearchedItems (item) {
      if (this.fields) {
        // get all fieldnames from item to search in
        const fieldNames = Object.values(this.fields);

        return fieldNames.some(value => {
          let itemValue = item[value];
          // if value is date format it to make it searchable
          if (item[value] instanceof Date) {
            itemValue = this.formattedDate(item[value]);
          }

          return ('' + itemValue).toLowerCase().includes(this.search.toLowerCase());
        });
      } else {
        return item;
      }
    },
    formattedDate (date) {
      return moment(date).format('Y-MM-DD LTS');
    },
    showPopup () {
      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;
      }
    },
    focusSearch () {
      this.selectedIndex = -1;
      this.showPopup();
    },
    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;
    },
  },
  components: {
    popup,
    relationPickerItemDefault,
    draggable,
  },
};
</script>
