<template>
  <div>
    <div class="search-container mb-4">
      <input type="text" v-model="search" class="form-control" placeholder="zoek..."
         @keydown.down.prevent="down" @keydown.up.prevent="up" @keydown.enter.prevent="enter" />
    </div>

    <table class="table table-striped">
      <thead>
        <th v-for="(name, key) in fieldNames" @click="sortBy(key)">
          {{name}}
          <span v-if="sort[0].key == key && sort[0].order == 1" class="icon-circle-down"></span>
          <span v-if="sort[0].key == key && sort[0].order == -1" class="icon-circle-up"></span>
        </th>
      </thead>
      <tbody :class="selectableClass">
        <tr v-for="(item, index) in displayItems"
          @click="actionCheck(item.$item)"
          :class="{ selected: selectedIndex == index }">
          <td v-for="key in keyNames">
            <component
              :is="customFields[fieldNames[key]] || $options.components.listingCell"
              :value="item[key]"
              :item="item"
              :property-name="fields[fieldNames[key]] || fieldNames[key]"
              :schema="schema && schema.properties[fields[fieldNames[key]]]">
            </component>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<style lang="scss" scoped>
.search-container {
  max-width: 500px;
}
.table-striped {
  thead {
    cursor: default;
    user-select: none;
  }
}
.tbody-selectable {
  cursor: pointer;
  tr:hover, tr.selected {
    background: white;
  }
}
</style>

<script>
import _orderBy from 'lodash/orderBy';
import listingCell from './listing-cell.vue';
import { getNameForRef } from '../schema/schema-scripts.js';

export default {
  docs: `Een sorteerbare, filterbare tabel`,
  props: {
    fields: {
      type: Object,
      required: true,
      docs: `Een object die mapt van vriendelijke namen naar property-namen.`,
      demoValue: () => ({
        'Naam': 'name',
        'Engels': 'english'
      })
    },
    customFields: {
      type: Object,
      docs: `Een object die mapt van vriendelijke namen naar een component-naam.
        Het component krijgt als properties <code>item</code>, <code>property-name</code>, <code>value</code> en <code>schema</code> mee.`,
      default: () => ({})
    },
    items: {
      type: Array,
      required: true,
      docs: `De lijst met objecten om te tonen.`,
      demoValue: () => [
        { id: 0, name: 'Boom', english: 'Tree'},
        { id: 1, name: 'Roos', english: 'Rose' },
        { id: 2, name: 'Vis', english: 'Fish' },
        { id: 3, name: 'Vuur', english: 'Fire' },
        { id: 4, name: 'Mus', english: 'Sparrow' }
      ]
    },
    action: {
      type: Function,
      docs: `De actie die wordt uitgevoerd als je op een regel klikt. Krijgt het gekozen object als argument.`,
      demoValue: item => window.alert('Dit heb je gekozen: ' + JSON.stringify(item))
    },
    schema: {
      type: Object,
      docs: `Schema die bij het object hoort`,
      demoValue: () => ({
        properties: {
          title: {
            'format': 'string-input',
            'title': 'Titel',
            'type': 'string'
          }
        }
      })
    }
  },
  data() {
    return {
      sort: [{order: 1}],
      search: '',
      selectedIndex: -1,
      searchQuery: this.$route.query.search
    };
  },
  computed: {
    columns() {
      return Object.keys(Object.assign({}, this.fields, this.customFields));
    },
    displayItems() {
      const filtersFromUrl = Object.entries(this.$route.query);
      return _orderBy(
        this.items
        .filter(item => filtersFromUrl.every(([key, value]) => !(key in item) || item[key] == value))
        .map(item => {
          var displayItem = { $item: item };

          for (var key in this.fieldNames) {
            const field = this.fields[this.fieldNames[key]];
            const type = this.schema && this.schema.properties[field];
            // Show title instead of Guid for foreign key references
            displayItem[key] = type && type.$ref ? getNameForRef(type.$ref, item[field]) : item[field];
          }
          return displayItem;
        })
        .filter(item => this.keyNames.some(name => (''+item[name]).toLowerCase().includes(this.search.toLowerCase())))
      , this.sort.map(({key}) => key), this.sort.map(({order}) => order === 1 ? 'asc' : 'desc'));
    },
    fieldNames() {
      var result = {};
      this.columns.forEach((name, ix) => {
        result['key' + ix] = name;
      });
      return result;
    },
    keyNames() {
      return Object.keys(this.fieldNames);
    },
    selectableClass() {
      return this.action != null ? 'tbody-selectable' : '';
    },
  },
  mounted() {
    if (this.searchQuery) {
      this.search = this.searchQuery;
    }

    this.sort = this.keyNames.map(key => ({key, order: 1}));
  },
  methods: {
    sortBy(key) {
      if (this.sort[0].key === key) {
        this.sort[0].order = -this.sort[0].order;
      } else {
        this.sort = this.sort.filter(item => item.key === key)
          .concat(this.sort.filter(item => item.key != key));
      }
    },
    up() {
      this.selectedIndex--;
      if (this.selectedIndex < 0)
        this.selectedIndex = this.$el.querySelectorAll('tbody tr').length - 1;
    },
    down() {
      this.selectedIndex++;
      if (this.selectedIndex >= this.$el.querySelectorAll('tbody tr').length)
        this.selectedIndex = 0;
    },
    enter() {
      var selected = this.$el.querySelector('tr.selected');
      if (selected)
        selected.click();
    },
    actionCheck(item) {
      if (this.action != null)
        this.action(item);
    }
  },
  watch: {
    search(searchValue) {
      this.$router.replace({query: searchValue ? {'search': searchValue} : {}});
    }
  },
  components: {
    listingCell
  }
}
</script>
