<template>
  <ui-input
    ref="inputVm"
    v-model="localValue"
    v-bind="$attrs"
    :disabled="disabled"
    :readonly="readonly"
    :name="name"
    :suffix-icon="suffixIcon"
    :prefix-icon="prefixIcon"
    class="mm-input-number"
    v-on="listeners">
    <div
      v-if="controls"
      slot="suffix"
      class="mm-input-number-control-wrapper">
      <span
        :class="{
          'mm-input-number-control': true,
          'mm-input-number-control--disabled': realValue === min || disabled
        }"
        @mousedown.stop.prevent
        @click.stop="stepHandler(-1)">
        <ui-icon
          name="down"
          size="12px"
          fill="#606266"></ui-icon>
      </span>
      <span
        :class="{
          'mm-input-number-control': true,
          'mm-input-number-control--disabled': realValue === max || disabled
        }"
        @mousedown.stop.prevent
        @click.stop="stepHandler(1)">
        <ui-icon
          name="up"
          size="12px"
          fill="#606266"></ui-icon>
      </span>
    </div>
  </ui-input>
</template>

<script>
import isUndefined from "lodash/isUndefined"
import isObject from "lodash/isObject"
import isNumber from "lodash/isNumber"
import Icon from "../icon"
import Input from "../input"
import input from "../../mixins/input"
import { getListeners, decimalAdd } from "../../utils/helper"
import keyCode from "../../utils/key-code"
import config from "../../managers/config"

const processPrecision = (value, precision) => {
  let finalValue = value
  if (value && isNumber(precision.length)) {
    const [intPart, floatPart = ""] = String(value).split(".")
    const supply = floatPart.length < precision.length && precision.fill
      ? Array(precision.length - floatPart.length).fill(precision.content).join("")
      : ""
    const f = (floatPart + supply).slice(0, precision.length)

    if (f.length > 0) {
      finalValue = `${intPart}.${f}`
    } else {
      finalValue = intPart
    }
  }
  return typeof value === "number" ? Number(finalValue) : finalValue
}

config.defaults({
  "InputNumber.step": 1,
  "InputNumber.precision": undefined,
  "InputNumber.controls": true,
})

export default {
  name: "mm-input-number",
  components: {
    "ui-icon": Icon,
    "ui-input": Input,
  },
  mixins: [input],
  inheritAttrs: false,
  model: {
    prop: "value",
    event: "input",
  },
  props: {
    value: {
      type: [Number, String],
      default: "",
    },
    max: {
      type: Number,
      default: 9007199254740991,
    },
    min: {
      type: Number,
      default: -9007199254740991,
    },
    step: {
      type: Number,
      default: () => config.get("InputNumber.step"),
      validator(value) {
        return value > 0
      },
    },
    precision: {
      type: [Number, Object],
      default: () => config.get("InputNumber.precision"),
      validator(value) {
        if (isNumber(value)) {
          return value >= 0 && value === parseInt(value, 10)
        }
        return isObject(value)
      },
    },
    controls: {
      type: Boolean,
      default: () => config.get("InputNumber.controls"),
    },
    lazy: {
      type: Boolean,
      default: true,
    },
    prefixIcon: String,
    suffixIcon: String,
  },
  data() {
    return {
      realValue: "",
      localValue: "",
    }
  },
  computed: {
    precisionConfig() {
      const config = {
        length: undefined,
        fill: true,
        content: 0,
      }
      if (isNumber(this.precision)) {
        config.length = this.precision
      } else if (isObject(this.precision)) {
        Object.assign(config, this.precision)
      }

      return config
    },
    defaultListeners() {
      return getListeners(
        ["click", "focus", "blur", "keyup", "keypress"],
        this
      )
    },
    listeners() {
      return {
        ...this.defaultListeners,
        input: () => {
          const value = this.localValue
          if (!isUndefined(value)) {
            const realValue = this.getRealValue(value, {
              ...this.precisionConfig,
              fill: false,
            })
            this.setRealValue(realValue)
            if (!this.lazy) {
              this.$nextTick(() => {
                this.repairLocalValue(realValue, value)
              })
            }
          }
        },
        change: (e) => {
          const value = this.getRealValue(this.realValue)
          if (!isUndefined(value)) {
            this.setLocalValue(value)
          }
          this.$emit("change", e)
        },
        keydown: e => {
          if (e.keyCode === keyCode.UP) {
            e.preventDefault()
            this.stepHandler(1)
          }

          if (e.keyCode === keyCode.DOWN) {
            e.preventDefault()
            this.stepHandler(-1)
          }

          this.$emit("keydown", e)
        },
      }
    },
    valueEffect() {
      return [this.min, this.max, this.precisionConfig]
    },
  },
  watch: {
    value: {
      immediate: true,
      handler(value) {
        if (value !== this.realValue) {
          this.realValue = value
          this.setLocalValue(value)
        }
      },
    },
    valueEffect() {
      const value = this.getRealValue(this.realValue)

      this.realValue = value
      this.setLocalValue(value)
    },
  },
  methods: {
    repairLocalValue(realValue, value) {
      const match = String(value).match(/^[-+]?[0-9]*([.]?[0-9]*)?/)
      const numberPart = match && match[0] // +1, -1, 1., 1, 1.0
      const isValidInput = match && (
        numberPart.endsWith(".") || numberPart.endsWith("-") || (!this.lazy && numberPart.endsWith("0"))
      )
      if (isValidInput) {
        this.setLocalValue(numberPart)
      } else {
        this.setLocalValue(realValue)
      }
    },
    stepHandler(change) {
      if (this.disabled) {
        return
      }

      const value = this.getRealValue(decimalAdd(this.localValue || 0, this.step * change))

      this.setRealValue(value)
      this.setLocalValue(value)
      this.$refs.inputVm && this.$refs.inputVm.focus()
    },
    getRealValue(v, precisionConfig = this.precisionConfig) {
      let value = parseFloat(v)

      if (v === "" || isNaN(value)) {
        return ""
      }

      value = processPrecision(value, precisionConfig)

      if (value > this.max) {
        value = this.max
      }

      if (value < this.min) {
        value = this.min
      }

      return value
    },
    setRealValue(value) {
      if (this.realValue === value) {
        return
      }
      this.realValue = value
      this.$emit("input", value)
    },
    setLocalValue(value) {
      this.localValue = value
    },
  },
}
</script>
