<template>
  <div class="formcontrol">
    <label class="formcontrol-label" for="" v-if="optional || label">
      {{ label }}
      <div class="formcontrol-optional" v-if="optional">Optional</div>
    </label>
    <div ref="container" 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="chip" :key="item.id">
          <span class="chip-label">{{ item.text }}</span>
          <button
            aria-label="Clear"
            class="chip-remove-btn"
            @click="toggle(item.index, $event)"
          ></button>
        </div>
        <div v-if="checkedItems.length > maxItemDisplayed" class="chip">
          <div class="chip-label">+{{ checkedItems.length - maxItemDisplayed }}</div>
        </div>
        <input
          class="select-multiple-input"
          ref="input"
          :placeholder="currentPlaceholder"
          v-model="inputValue"
          :size="Math.max(inputValue.length, currentPlaceholder.length)"
          @keyup="keyUp"
        />
      </div>
      <div class="dropdown-options pickable">
        <div
          v-for="(item, index) in filteredItems"
          :class="{
            'select--checked': item.isChecked,
            'select--active': index == activeIndex,
          }"
          :key="item.value"
          @click="toggle(item.index, $event)"
          v-html="highlight(item.text)"
        ></div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import "@/assets/css/chip.css";
import AutocompleteValues from "@/models/AutocompleteOptions";
import { defineComponent, PropType } from "vue";
interface selectItem {
  index: number;
  isChecked: boolean;
  value: string;
  text: string;
}
export default defineComponent({
  name: "selectChip",
  props: {
    label: String,
    optional: Boolean,
    // Values selected by the component
    modelValue: {
      type: Array as PropType<Array<string>>,
      default: () => [],
    },
    /*
     * Potential values that could be selected by the user
     * type Array<String> | Array<AutocompleteValues>
     */
    autocompleteList: {
      type: Array as PropType<Array<string | AutocompleteValues>>,
      default: () => [],
    },
    placeholder: {
      type: String,
      default: "Choose among the list",
    },
    secondaryPlaceholder: {
      type: String,
      default: "+ ",
    },
    // 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: {
    modelValue: function () {
      this.updateItems();
    },
  },
  emit: ["update:modelValue"],
  methods: {
    focus() {
      if (this.input) {
        this.input.focus();
      }
    },
    toggle(idx: number, e: MouseEvent): void {
      e.stopPropagation();
      this.items[idx].isChecked = !this.items[idx].isChecked;
      this.emitValue();
      this.focus();
    },
    emitValue() {
      const values = this.items.filter((o) => o.isChecked).map((o) => o.value);
      this.$emit("update:modelValue", values);
    },
    updateItems(): void {
      this.items = this.autocompleteList.map(
        (o: AutocompleteValues | string, idx: number): selectItem => {
          let id: string, txt: string;
          if (typeof o == "object") {
            id = "id" in o ? o.id : o;
            txt = "name" in o ? o.name : id;
          } else {
            id = o;
            txt = o;
          }
          return {
            index: idx,
            isChecked: this.modelValue.includes(id),
            value: id,
            text: txt,
          };
        }
      );
    },
    openDropDown: function (e: MouseEvent) {
      this.expanded = true;
      this.focus();
      window.addEventListener("click", this.closeDropDown);
      e.stopPropagation();
    },
    closeDropDown: function (e: MouseEvent) {
      if (this.$refs["container"]) {
        if (!(this.$refs["container"] as HTMLElement).contains(e.target as Node)) {
          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;
        this.emitValue();
      }
      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];
    },
    currentPlaceholder(): string {
      return this.checkedItems.length > 0 ? this.secondaryPlaceholder : this.placeholder;
    },
  },
  beforeMount: function () {
    this.updateItems();
  },
});
</script>

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