<template>
  <div :style="{ width, height }">
    <div
      :class="{
        'snt-number-input--controls-around': controls && justifyControls === 'around',
        'snt-number-input--controls-end': controls && justifyControls === 'end',
        'snt-number-input--center': center,
        'snt-number-input--right': right,
        'snt-number-input--rounded': rounded,
      }"
      class="snt-number-field">
      <button
        v-if="controls && justifyControls === 'around'"
        :style="{ borderColor: borderColor }"
        :disabled="disabled"
        class="snt-number-input__button snt-number-input__button--minus"
        type="button"
        tabindex="-1"
        @click="decrease($event)" />
      <!-- onkeypress="return event.charCode >= 48 && event.charCode <= 57" -->
      <input
        ref="input"
        :value="newValue"
        :min="min"
        :max="max"
        :disabled="disabled"
        class="snt-number-field__input"
        :placeholder="placeholder"
        :style="{ borderColor: borderColor }"
        autocomplete="off"
        @input="change($event)"
        @paste="paste($event)"
        @blur="blur($event)"
        @keyup.enter="keyupEnter()" />
      <button
        v-if="controls && justifyControls === 'around'"
        :style="{ borderColor: borderColor }"
        :disabled="disabled"
        class="snt-number-input__button snt-number-input__button--plus"
        type="button"
        tabindex="-1"
        @click="increase($event)" />
      <div
        v-if="controls && justifyControls === 'end'"
        :style="{ borderColor: borderColor }"
        class="snt-number-input__button-group">
        <button
          :disabled="disabled"
          class="snt-number-input__button-end"
          type="button"
          tabindex="-1"
          @click="increase($event)">
          <v-icon size="15.3">
            mdi-chevron-up
          </v-icon>
        </button>
        <button
          :disabled="disabled"
          :style="{ borderColor: borderColor }"
          class="snt-number-input__button-end"
          type="button"
          tabindex="-1"
          @click="decrease($event)">
          <v-icon size="15.3">
            mdi-chevron-down
          </v-icon>
        </button>
      </div>
      <v-btn
        v-if="!controls && clearable && !!value"
        :ripple="false"
        class="snt-number-input__button-close"
        text
        x-small
        icon
        @click="clear()">
        <v-icon size="16">
          mdi-close
        </v-icon>
      </v-btn>
    </div>
    <div
      v-if="!hideDetails"
      class="snt-number-input_details">
      <v-input
        :value="newValue"
        :rules="rules"
        :disabled="disabled"
        :hide-details="hideDetails"
        type="number" />
    </div>
  </div>
</template>

<script>
const REGEXP_NUMBER = /^-?(?:\d+|\d+\.\d+|\.\d+)(?:[eE][-+]?\d+)?$/
const REGEXP_DECIMALS = /\.\d*(?:0|9){10}\d*$/
const normalizeDecimalNumber = (value, times = 100000000000) => (
  REGEXP_DECIMALS.test(String(value)) ? (Math.round(value * times) / times) : value
)

export default {
  props: {
    value: {
      type: [Number, String],
      default: undefined
    },
    min: {
      type: Number,
      default: -Infinity
    },
    max: {
      type: Number,
      default: Infinity
    },
    step: {
      type: Number,
      default: 1
    },
    justifyControls: {
      type: String,
      validator (value) {
        return ['around', 'end'].indexOf(value) !== -1
      },
      default: 'end'
    },
    width: {
      type: String,
      default: '100%'
    },
    height: {
      type: String,
      default: 'auto'
    },
    borderColor: {
      type: String,
      default: null
    },
    placeholder: {
      type: String,
      default: ''
    },
    rules: {
      type: Array,
      default: () => ([])
    },
    hideDetails: Boolean,
    controls: Boolean,
    center: Boolean,
    right: Boolean,
    disabled: Boolean,
    rounded: Boolean,
    autofocus: Boolean,
    decimal: Boolean,
    clearable: Boolean
  },
  data () {
    return {
      newValue: NaN
    }
  },
  computed: {
    validate () {
      return false // typeof this.$listeners.input !== 'undefined'
    },
    increasable () {
      const value = this.value || 0
      return this.$_.isNaN(value) || value < this.max
    },
    decreasable () {
      const value = this.value || 0
      return this.$_.isNaN(value) || value > this.min
    }
  },
  watch: {
    value: {
      immediate: true,
      handler (newValue, oldValue) {
        if (
          // Avoid triggering change event when created
          !(this.$_.isNaN(newValue) && (typeof oldValue === 'undefined'))
          // Avoid infinite loop
          && newValue !== this.newValue
        ) {
          this.setValue(newValue)
        }
      }
    }
  },
  mounted () {
    if (this.autofocus) {
      setTimeout(() => {
        this.$refs.input.focus()
      }, 150)
    }
  },
  methods: {
    change (e) {
      const value = e.target.value
      this.setValue(value, this.validate)
    },
    paste (e) {
      const clipboardData = e.clipboardData || window.clipboardData
      if (clipboardData && !REGEXP_NUMBER.test(clipboardData.getData('text'))) {
        e.preventDefault()
      }
    },
    decrease () {
      if (this.decreasable) {
        let { value } = this
        if (this.$_.isNaN(value)) {
          value = 0
        }
        const newValue = normalizeDecimalNumber(value - this.step) || 0
        this.setValue(newValue)
        this.$emit('change', newValue)
      }
    },
    increase () {
      if (this.increasable) {
        let { value } = this

        if (this.$_.isNaN(value)) {
          value = 0
        }
        const newValue = normalizeDecimalNumber(value + this.step) || 0
        this.setValue(newValue)
        this.$emit('change', newValue)
      }
    },
    clear () {
      this.setValue(0)
    },
    setValue (value, validate = true) {
      if (/^\d+\.$/.test(value) && this.decimal) return
      const oldValue = this.newValue
      let newValue = Number(value)
      if (value !== '' && !this.$_.isNaN(newValue)) {
        if (validate) {
          if (this.min <= this.max) {
            newValue = Math.min(this.max, Math.max(this.min, newValue))
          } else {
            newValue = this.min
          }
        }

        if (this.decimal) {
          newValue = this.$_.floor(newValue, 3)
        } else {
          newValue = this.$_.floor(newValue)
        }
      } else if (validate) {
        newValue = (this.min && this.min !== -Infinity) ? this.min : undefined
      } else {
        newValue = undefined
      }
      this.newValue = newValue
      if (newValue === oldValue) {
        let str = ''
        if (typeof newValue !== 'undefined') {
          str = newValue
          if (String(value).slice(-1) === '0') {
            str = value
          }
        }
        this.$refs.input.value = String(str)
      }
      this.$emit('input', newValue)
    },
    blur (e) {
      if (typeof this.newValue === 'undefined') {
        this.newValue = 0
      }
      this.$refs.input.value = String(this.newValue)
      this.setValue(this.newValue)
      this.$emit('change', this.newValue)
      this.$emit('blur', e)
    },
    keyupEnter () {
      this.$emit('keyupEnter')
      this.$refs.input.blur()
    }
  }
}

</script>

<style lang="scss" scoped>
@import '~vuetify/src/styles/styles.sass';
@import '~@/assets/scss/variables.scss';
.snt-number-field {
  display: block;
  font-size: 0;
  max-width: 100%;
  overflow: hidden;
  position: relative;
  height: 100%;
}
.snt-number-field__input {
  -moz-appearance: textfield;
  color: var(--v-secondary-base);
  background-color: #fff;
  border-width: 1px;
  border-color: map-get(map-get($material-light, 'text-fields'), 'outlined');
  border-style: solid;
  display: block;
  font-size: 1rem;
  line-height: 1.5;
  max-width: 100%;
  min-height: 1.5rem;
  min-width: 3rem;
  padding: 0.4375rem 0.875rem;
  transition: border-color 0.15s;
  width: 100%;
  height: 100%;
  &::-webkit-outer-spin-button,
  &::-webkit-inner-spin-button {
    -webkit-appearance: none;
  }
  &:hover,
  &:focus {
    border-color: var(--v-primary-base) !important;
    outline: none;
  }
  &:disabled,
  &[readonly] {
    border-color: #e2e2e481;
    color: rgba(0, 0, 0, 0.38);
  }
  &::placeholder {
    color: #B5B5B5;
  }
}
.snt-number-input--controls-around {
  & > input {
    padding-left: 2.275rem;
    padding-right: 2.275rem;
  }
}
.snt-number-input--controls-end {
  display: flex;
  & > input {
    padding-right: 2.275rem;
  }
}

.snt-number-input--center {
  & > input {
    text-align: center;
  }
}
.snt-number-input--right {
  & > input {
    text-align: right;
  }
}
.snt-number-input--rounded {
  & > input {
    border-radius: 4px;
  }
}
.snt-number-input__button {
  background-color: #fff;
  border: 0;
  border-radius: 0.25rem;
  bottom: 1px;
  position: absolute;
  top: 1px;
  width: 2rem;
  z-index: 1;
  &:focus {
    outline: none;
  }
  &:hover {
    &::before,
    &::after {
      background-color: #0074d9;
    }
  }
  &:disabled {
    opacity: 0.65;
    &::before,
    &::after {
      background-color: #ddd;
    }
  }
  &::before,
  &::after {
    background-color: #111;
    content: "";
    left: 50%;
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    transition: background-color 0.15s;
  }
  &::before {
    height: 1px;
    width: 32%;
  }
  &::after {
    height: 32%;
    width: 1px;
  }
  &--minus {
    border-bottom-right-radius: 0;
    border-right-width: 1px;
    border-right-color: map-get(map-get($material-light, 'text-fields'), 'outlined');;
    border-right-style: solid;
    border-top-right-radius: 0;
    left: 1px;
    &::after {
      visibility: hidden;
    }
  }
  &--plus {
    border-bottom-left-radius: 0;
    border-left-width: 1px;
    border-left-color: map-get(map-get($material-light, 'text-fields'), 'outlined');;
    border-left-style: solid;
    border-top-left-radius: 0;
    right: 1px;
  }
}
.snt-number-input__button-group {
  position: absolute;
  right: 0;
  height: 100%;
  width: 30px;
  border-left-width: 1px;
  border-left-style: solid;
  border-left-color: map-get(map-get($material-light, 'text-fields'), 'outlined');;
}
.snt-number-input__button-end {
  display: block;
  height: 50%;
  width: 30px;
  &:not(:first-child) {
    border-top-width: 1px;
    border-top-style: solid;
    border-top-color: map-get(map-get($material-light, 'text-fields'), 'outlined');;
  }
}
.snt-number-input__button-close {
  position: absolute;
  right: 12px;
  top: 10px;
}
.snt-number-input_details {
  display: flex;
  flex: 1 0 auto;
  max-width: 100%;
  min-height: 14px;
  overflow: hidden;
  padding: 0 12px;
  margin-bottom: 8px;
  height: 18px;
}
</style>
