<template>
  <div class="select-multiple" :class="{ 'select-multiple--expanded': expanded }">
    <div class="select-multiple-value" @click="openDropDown">
      <span v-if="checkedItems.length === 0"></span>
      <div v-for="item of displayedItems" class="ds-chip" :key="item.id">
        <span class="ds-chip-label">{{ item.text }}</span>
        <button aria-label="Clear" class="ds-chip-remove-btn" @click="toggle(item.id, $event)" />
      </div>
      <div v-if="checkedItems.length > maxItemDisplayed" class="ds-chip">
        <div class="ds-chip-label">+{{ checkedItems.length - maxItemDisplayed }}</div>
      </div>
      <input
        class="select-multiple-input"
        ref="input"
        :placeholder="placeholder"
        v-model="inputValue"
        :size="Math.max(inputValue.length, placeholder.length)"
        @keyup="keyUp"
      />
    </div>
    <div class="dropdown-options">
      <div
        v-for="(item, index) in filteredItems"
        :class="{
          'select--checked': item.isChecked,
          'select--active': index == activeIndex,
        }"
        :key="item.id"
        @click="toggle(item.id, $event)"
        v-html="highlight(item.text)"
      ></div>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType } from "vue";
interface selectItem {
  id: number;
  isChecked: boolean;
  value: any;
  text: string;
}
interface selectOption {
  value: any;
  text: string;
}
export default defineComponent({
  name: "selectChip",
  props: {
    // Values selected by the component
    valueModel: {
      type: Array as PropType<Array<string>>,
      default: () => [],
    },
    /*
     * Potential values that could be selected by the user
     * type Array<String> | Array<Option>
     * Option {
     *   value:Any,
     *   text:String ,
     * }
     */
    options: {
      type: Array as PropType<Array<string | selectOption>>,
      default: () => [],
    },
    placeholder: {
      type: String,
      default: "Choose among the list",
    },
    // Number of selected item displayed before displaying an ellipsis appear
    maxItemDisplayed: {
      type: Number,
      default: Infinity,
    },
  },
  data() {
    return {
      items: [] as Array<selectItem>,
      expanded: false,
      inputValue: "",
      activeIndex: -1,
    };
  },
  watch: {
    valueModel: function () {
      this.updateItems();
    },
  },
  emit: ["update:valueModel"],
  methods: {
    toggle(idx: number, e: MouseEvent): void {
      this.items[idx].isChecked = !this.items[idx].isChecked;
      this.$emit(
        "update:valueModel",
        this.items.filter((o) => o.isChecked).map((o) => o.value)
      );
      this.input.focus();
      e.stopPropagation();
    },
    updateItems(): void {
      this.items = this.options.map((o: selectOption | string, idx: number) => {
        let itemValue: any, text: string;
        if (typeof o == "object") {
          itemValue = "value" in o ? o.value : o;
          text = "text" in o ? o.text : itemValue;
        } else {
          itemValue = o;
          text = o;
        }
        return {
          id: idx,
          isChecked: this.valueModel.includes(itemValue),
          value: itemValue,
          text: text,
        };
      });
    },
    openDropDown: function (e: MouseEvent) {
      this.expanded = true;
      this.input.focus();
      window.addEventListener("click", this.closeDropDown);
      e.stopPropagation();
    },
    closeDropDown: function (e: MouseEvent) {
      if (!this.$el.contains(e.target)) {
        this.expanded = false;
        window.removeEventListener("click", this.closeDropDown);
        e.stopPropagation();
      }
    },
    highlight(txt: string): string {
      if (this.inputValue) {
        let reg = new RegExp(this.inputValue, "ig");
        let boldTxt = txt.match(reg);
        if (boldTxt !== null) {
          const plain = txt.split(reg);
          let output = "";
          for (let i = 0; i < boldTxt.length; i++) {
            output += plain[i] + "<b>" + boldTxt[i] + "</b>";
          }
          return output + plain.pop();
        }
      }
      return txt;
    },
    keyUp(e: KeyboardEvent) {
      if (e.key == "Enter") {
        if (this.selectedItem) this.selectedItem.isChecked = !this.selectedItem.isChecked;
      }
      if (e.key == "ArrowUp") {
        this.activeIndex--;
      }
      if (e.key == "ArrowDown") {
        this.activeIndex++;
      }
      this.boundActiveIndex();
      /*
      if(e.key=="Backspace" && this.inputValue==""){
        // Remove the last entry
        const lastItem  = this.checkedItems[this.checkedItems.length-1]
        if (lastItem){
          lastItem.isChecked = false
        }
      }
      */
    },
    boundActiveIndex() {
      if (this.activeIndex < 0) {
        this.activeIndex = this.filteredItems.length;
      }
      if (this.activeIndex > this.filteredItems.length) {
        this.activeIndex = 0;
      }
    },
  },
  computed: {
    checkedItems(): Array<selectItem> {
      return this.items.filter((i) => i.isChecked);
    },
    displayedItems(): Array<selectItem> {
      return this.checkedItems.filter((i, idx) => idx < this.maxItemDisplayed);
    },
    input(): HTMLInputElement {
      return this.$refs["input"] as HTMLInputElement;
    },
    filteredItems(): Array<selectItem> {
      if (this.inputValue) {
        const low = this.inputValue.toLowerCase();
        const output = this.items.filter((o) => o.text.toLowerCase().includes(low));
        return output;
      }
      return this.items;
    },
    selectedItem(): selectItem | undefined {
      return this.filteredItems[this.activeIndex];
    },
  },
  beforeMount: function () {
    this.updateItems();
  },
});
</script>

<style src="../assets/css/multiple-select.css"></style>
