<template>
  <div>
    <input
        ref="textInput"
        type="text"
        autocomplete="off"

        :id="id"
        :name="name"
        :placeholder="placeholder"
        :value="rawValue"
        :class="finalInputClass"

        @click="clickHandler"
        @blur="blurHandler"
        @input="inputHandler"
        @keydown="keydownHandler"
    />
    <div v-show="isListOpen" class="list">
      <ul>
        <li v-for="(opt, idx) in options" :key="optionKey(opt)" :class="optionClass(idx)" @mousemove="optionMousemove(idx)" @click="optionClick(opt)">
          <b class="opt_value">{{ optionValue(opt) }}</b>
          <span v-if="optionLabel(opt) !== null" class="opt_label" v-html="optionLabel(opt)"></span>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>

  import debounce from 'lodash/debounce';

  export default {
    props: {
      value: String,
      id: String,
      placeholder: String,
      name: String,
      inputClass: {
        type: [String, Object, Array],
        required: false,
        default: null
      },
      minLength: {
        type: Number,
        default: 0
      },
      debounce: {
        type: Number,
        required: false,
        default: 250
      },

      valueAttribute: String,
      labelAttribute: String,
      keyAttribute: String,

      onGetOptions: Function,
      searchOptions: Array
    },

    data() {
      return {
        options: [],
        rawValue: "",
        isListOpen: false,
        activeListIndex: 0
      }
    },

    created() {
      this.rawValue = this.value;

    },

    watch: {
      value(newValue) {
        this.rawValue = newValue;
      }
    },

    computed: {
      finalInputClass() {
        let cls = ['input'];
        if (this.inputClass === null) {
          return cls;
        } else if (Array.isArray(this.inputClass)) {
          return cls.concat(this.inputClass);
        } else {
          cls.push(this.inputClass);
          return cls;
        }
      },

      debouncedUpdateOptions() {
        return debounce(this.updateOptions, this.debounce);
      }
    },

    methods: {
      optionClass(idx) {
        return this.activeListIndex === idx ? 'option active' : 'option';
      },

      optionClick(opt) {
        this.selectOption(opt);
      },

      optionKey(opt) {
        if (this.keyAttribute) {
          return opt[this.keyAttribute]
        } else if (this.valueAttribute) {
          return opt[this.valueAttribute];
        } else {
          return opt.toString();
        }
      },

      optionValue(opt) {
        if (this.valueAttribute) {
          return opt[this.valueAttribute];
        } else {
          return opt.toString();
        }
      },

      optionLabel(opt) {
        if (this.labelAttribute) {
          return opt[this.labelAttribute];
        } else {
          return null;
        }
      },

      optionMousemove(idx) {
        this.activeListIndex = idx;
      },

      clickHandler(evt) {
        this.$emit("inputClick", evt);
      },

      blurHandler(evt) {
        // blur fires before click.  If the blur was fired because the user clicked a list item, immediately hiding the list here
        // would prevent the click event from firing
        setTimeout(() => {
          this.isListOpen = false;
        },250);
      },

      inputHandler(evt) {
        const newValue = evt.target.value;

        if (this.rawValue !== newValue) {

          this.rawValue = newValue;

          this.$emit("input", newValue);

          if (newValue.length >= Math.max(1, this.minLength)) {
            this.debouncedUpdateOptions(newValue);
          } else {
            this.isListOpen = false;
          }
        }
      },

      keydownHandler(evt) {
        if (this.isListOpen === false)
          return;

        switch (evt.key) {
          case "ArrowUp":
            evt.preventDefault();
            this.activeListIndex = Math.max(0, this.activeListIndex - 1);
            break;
          case "ArrowDown":
            evt.preventDefault();
            this.activeListIndex = Math.min(this.options.length - 1, this.activeListIndex + 1);
            break;
          case "Enter":
            evt.preventDefault();
            this.selectOption(this.options[this.activeListIndex]);
            break;
          case "Escape":
            evt.preventDefault();
            this.isListOpen = false;
            break;
        }
      },

      selectOption(opt) {
        this.rawValue = this.optionValue(opt);
        this.$emit("input", this.rawValue);
        this.$emit("optionSelected", opt);
        this.isListOpen = false;
      },

      updateOptions(value) {
        let p = null;
        if (this.searchOptions) {
          const reg = new RegExp("^" + value, "i");
          const matcher = o => reg.test(this.optionValue(o));
          p = Promise.resolve(this.searchOptions.filter(matcher));
        } else {
          p = this.onGetOptions(value)
        }

        p.then(opts => {
          this.options = opts;
          this.isListOpen = opts.length > 0;
          this.activeListIndex = 0;
        })
      }
    }
  }

</script>

<style lang="scss" scoped>

  @import "../styles/variables";

  $labelLineHeight: 0.8rem;

  input.input {
    &::placeholder {
      color: $grey-darker;
    }
  }

  .list {
    position: relative;
    z-index: 150;

    ul {
      background-color: white;
      position: absolute;
      width: 100%;
      border: 1px solid black;
    }
  }

  li.option {

    padding: 4px;
    margin-bottom: 2px;

    //transition: background-color 0.25s;

    &.active {
      color: white;
      background-color: $turquoise;
    }

    .opt_value {
    }

    .opt_label {
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
      font-size: 0.8rem;
      line-height: $labelLineHeight;
      max-height: $labelLineHeight * 2;
    }
  }

</style>