<template>
  <view :class="`wd-input-number ${customClass} ${disabled ? 'is-disabled' : ''} ${withoutInput ? 'is-without-input' : ''}`" :style="customStyle">
    <view :class="`wd-input-number__action ${minDisabled || disableMinus ? 'is-disabled' : ''}`" @click="sub">
      <wd-icon name="decrease" custom-class="wd-input-number__action-icon"></wd-icon>
    </view>
    <view v-if="!withoutInput" class="wd-input-number__inner" @click.stop="">
      <input
        class="wd-input-number__input"
        :style="`${inputWidth ? 'width: ' + inputWidth : ''}`"
        type="digit"
        :disabled="disabled || disableInput"
        :value="String(inputValue)"
        :placeholder="placeholder"
        :adjust-position="adjustPosition"
        @input="handleInput"
        @focus="handleFocus"
        @blur="handleBlur"
      />
      <view class="wd-input-number__input-border"></view>
    </view>
    <view :class="`wd-input-number__action ${maxDisabled || disablePlus ? 'is-disabled' : ''}`" @click="add">
      <wd-icon name="add" custom-class="wd-input-number__action-icon"></wd-icon>
    </view>
  </view>
</template>

<script lang="ts">
export default {
  name: 'wd-input-number',
  options: {
    virtualHost: true,
    addGlobalClass: true,
    styleIsolation: 'shared'
  }
}
</script>

<script lang="ts" setup>
import wdIcon from '../wd-icon/wd-icon.vue'
import { computed, nextTick, ref, watch } from 'vue'
import { isDef, isEqual } from '../common/util'
import { inputNumberProps } from './types'
import { callInterceptor } from '../common/interceptor'

const props = defineProps(inputNumberProps)
const emit = defineEmits(['focus', 'blur', 'change', 'update:modelValue'])
const inputValue = ref<string | number>(getInitValue()) // 输入框的值

// 减号是否禁用
const minDisabled = computed(() => {
  const value = formatValue(inputValue.value)
  const { disabled, min, step } = props
  return disabled || Number(value) <= min || changeStep(value, -step) < min
})

// 加号是否禁用
const maxDisabled = computed(() => {
  const value = formatValue(inputValue.value)
  const { disabled, max, step } = props
  return disabled || Number(value) >= max || changeStep(value, step) > max
})

watch(
  () => props.modelValue,
  (value) => {
    updateValue(value)
  }
)

watch([() => props.max, () => props.min, () => props.precision], () => {
  const value = formatValue(inputValue.value)
  updateValue(value)
})

function isValueEqual(value1: number | string, value2: number | string) {
  return isEqual(String(value1), String(value2))
}

function getInitValue() {
  const formatted = formatValue(props.modelValue)
  if (!isValueEqual(formatted, props.modelValue)) {
    emit('update:modelValue', formatted)
  }
  return formatted
}

function toPrecision(value: number) {
  return Number(parseFloat(`${Math.round(value * Math.pow(10, props.precision)) / Math.pow(10, props.precision)}`).toFixed(props.precision))
}

function getPrecision(value?: number) {
  if (!isDef(value)) return 0
  const valueString = value.toString()
  const dotPosition = valueString.indexOf('.')
  let precision = 0
  if (dotPosition !== -1) {
    precision = valueString.length - dotPosition - 1
  }
  return precision
}

function toStrictlyStep(value: number | string) {
  const stepPrecision = getPrecision(props.step)
  const precisionFactory = Math.pow(10, stepPrecision)
  return (Math.round(Number(value) / props.step) * precisionFactory * props.step) / precisionFactory
}

function updateValue(value: string | number, fromUser: boolean = false) {
  if (isValueEqual(value, inputValue.value)) {
    return
  }

  const update = () => {
    inputValue.value = value
    const formatted = formatValue(value)
    nextTick(() => {
      inputValue.value = formatted
      emit('update:modelValue', inputValue.value)
      emit('change', { value: inputValue.value })
    })
  }

  if (fromUser) {
    callInterceptor(props.beforeChange, {
      args: [value],
      done: update
    })
  } else {
    update()
  }
}

function changeStep(val: string | number, step: number) {
  val = Number(val)
  if (isNaN(val)) {
    return props.min
  }
  const precisionFactory = Math.pow(10, props.precision)
  return toPrecision((val * precisionFactory + step * precisionFactory) / precisionFactory)
}

function changeValue(step: number) {
  if ((step < 0 && (minDisabled.value || props.disableMinus)) || (step > 0 && (maxDisabled.value || props.disablePlus))) return
  const value = changeStep(inputValue.value, step)
  updateValue(value, true)
}

function sub() {
  changeValue(-props.step)
}

function add() {
  changeValue(props.step)
}

function handleInput(event: any) {
  const value = event.detail.value || ''
  updateValue(value, true)
}

function handleFocus(event: any) {
  emit('focus', event.detail)
}

function handleBlur(event: any) {
  const value = event.detail.value || ''
  updateValue(value, true)
  emit('blur', {
    value
  })
}

function formatValue(value: string | number) {
  if (props.allowNull && (!isDef(value) || value === '')) {
    return ''
  }

  let formatted = Number(value)

  if (isNaN(formatted)) {
    formatted = props.min
  }

  if (props.stepStrictly) {
    formatted = toStrictlyStep(value)
  }

  formatted = Math.min(Math.max(formatted, props.min), props.max)

  if (isDef(props.precision)) {
    formatted = Number(formatted.toFixed(props.precision))
  }

  return formatted
}
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>
