import { defineComponent, h, ref, Ref, computed, reactive, inject, nextTick, watch, onMounted, createCommentVNode, onBeforeUnmount, PropType } from 'vue'
import XEUtils from 'xe-utils'
import { getConfig, getIcon, getI18n, globalEvents, GLOBAL_EVENT_KEYS, createEvent, useSize } from '../../ui'
import { getFuncText, eqEmptyValue } from '../../ui/src/utils'
import { hasClass, getEventTargetNode } from '../../ui/src/dom'
import { getSlotVNs } from '../..//ui/src/vn'
import { handleNumber, toFloatValueFixed } from './util'

import type { VxeNumberInputConstructor, NumberInputInternalData, VxeNumberInputEmits, NumberInputReactData, NumberInputMethods, VxeNumberInputPropTypes, InputPrivateRef, VxeFormConstructor, VxeFormPrivateMethods, VxeFormDefines, ValueOf } from '../../../types'

export default defineComponent({
  name: 'VxeNumberInput',
  props: {
    modelValue: [String, Number] as PropType<VxeNumberInputPropTypes.ModelValue>,
    immediate: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Immediate>,
      default: true
    },
    name: String as PropType<VxeNumberInputPropTypes.Name>,
    type: {
      type: String as PropType<VxeNumberInputPropTypes.Type>,
      default: 'number'
    },
    clearable: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Clearable>,
      default: () => getConfig().numberInput.clearable
    },
    readonly: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Readonly>,
      default: null
    },
    disabled: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Disabled>,
      default: null
    },
    placeholder: String as PropType<VxeNumberInputPropTypes.Placeholder>,
    maxLength: {
      type: [String, Number] as PropType<VxeNumberInputPropTypes.MaxLength>,
      default: () => getConfig().numberInput.maxLength
    },
    autoComplete: {
      type: String as PropType<VxeNumberInputPropTypes.AutoComplete>,
      default: 'off'
    },
    align: String as PropType<VxeNumberInputPropTypes.Align>,
    form: String as PropType<VxeNumberInputPropTypes.Form>,
    className: String as PropType<VxeNumberInputPropTypes.ClassName>,
    size: {
      type: String as PropType<VxeNumberInputPropTypes.Size>,
      default: () => getConfig().numberInput.size || getConfig().size
    },

    // number、integer、float
    min: {
      type: [String, Number] as PropType<VxeNumberInputPropTypes.Min>,
      default: null
    },
    max: {
      type: [String, Number] as PropType<VxeNumberInputPropTypes.Max>,
      default: null
    },
    step: [String, Number] as PropType<VxeNumberInputPropTypes.Step>,
    exponential: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Exponential>,
      default: () => getConfig().numberInput.exponential
    },
    showCurrency: {
      type: Boolean as PropType<VxeNumberInputPropTypes.ShowCurrency>,
      default: () => getConfig().numberInput.showCurrency
    },
    currencySymbol: {
      type: String as PropType<VxeNumberInputPropTypes.CurrencySymbol>,
      default: () => getConfig().numberInput.currencySymbol
    },

    // number、integer、float
    controls: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Controls>,
      default: () => getConfig().numberInput.controls
    },

    // float
    digits: {
      type: [String, Number] as PropType<VxeNumberInputPropTypes.Digits>,
      default: null
    },
    autoFill: {
      type: Boolean as PropType<VxeNumberInputPropTypes.AutoFill>,
      default: () => getConfig().numberInput.autoFill
    },
    editable: {
      type: Boolean as PropType<VxeNumberInputPropTypes.Editable>,
      default: true
    },

    prefixIcon: String as PropType<VxeNumberInputPropTypes.PrefixIcon>,
    suffixIcon: String as PropType<VxeNumberInputPropTypes.SuffixIcon>,

    // 已废弃
    maxlength: [String, Number] as PropType<VxeNumberInputPropTypes.Maxlength>,
    // 已废弃
    autocomplete: String as PropType<VxeNumberInputPropTypes.Autocomplete>
  },
  emits: [
    'update:modelValue',
    'input',
    'change',
    'keydown',
    'keyup',
    'wheel',
    'click',
    'focus',
    'blur',
    'clear',
    'prev-number',
    'next-number',
    'prefix-click',
    'suffix-click'
  ] as VxeNumberInputEmits,

  setup (props, context) {
    const { slots, emit } = context
    const $xeForm = inject<VxeFormConstructor & VxeFormPrivateMethods | null>('$xeForm', null)
    const formItemInfo = inject<VxeFormDefines.ProvideItemInfo | null>('xeFormItemInfo', null)

    const xID = XEUtils.uniqueId()

    const { computeSize } = useSize(props)

    const reactData = reactive<NumberInputReactData>({
      isFocus: false,
      isActivated: false,
      inputValue: props.modelValue
    })

    const internalData: NumberInputInternalData = {
      // dnTimeout: undefined,
      // isUM: undefined
    }

    const refElem = ref() as Ref<HTMLDivElement>
    const refInputTarget = ref() as Ref<HTMLInputElement>
    const refInputPanel = ref() as Ref<HTMLDivElement>

    const refMaps: InputPrivateRef = {
      refElem,
      refInput: refInputTarget
    }

    const $xeNumberInput = {
      xID,
      props,
      context,
      reactData,
      internalData,
      getRefMaps: () => refMaps
    } as unknown as VxeNumberInputConstructor

    let numberInputMethods = {} as NumberInputMethods

    const computeFormReadonly = computed(() => {
      const { readonly } = props
      if (readonly === null) {
        if ($xeForm) {
          return $xeForm.props.readonly
        }
        return false
      }
      return readonly
    })

    const computeIsDisabled = computed(() => {
      const { disabled } = props
      if (disabled === null) {
        if ($xeForm) {
          return $xeForm.props.disabled
        }
        return false
      }
      return disabled
    })

    const computeDigitsValue = computed(() => {
      const { type, digits } = props
      let defDigits: any = digits
      if (defDigits === null) {
        defDigits = getConfig().numberInput.digits
        if (defDigits === null) {
          if (type === 'amount') {
            defDigits = 2
          }
        }
      }
      return XEUtils.toInteger(defDigits) || 1
    })

    const computeDecimalsType = computed(() => {
      const { type } = props
      return type === 'float' || type === 'amount'
    })

    const computeStepValue = computed(() => {
      const { type } = props
      const digitsValue = computeDigitsValue.value
      const decimalsType = computeDecimalsType.value
      const step = props.step
      if (type === 'integer') {
        return XEUtils.toInteger(step) || 1
      } else if (decimalsType) {
        return XEUtils.toNumber(step) || (1 / Math.pow(10, digitsValue))
      }
      return XEUtils.toNumber(step) || 1
    })

    const computeIsClearable = computed(() => {
      return props.clearable
    })

    const computeInputReadonly = computed(() => {
      const { editable } = props
      const formReadonly = computeFormReadonly.value
      return formReadonly || !editable
    })

    const computeInpPlaceholder = computed(() => {
      const { placeholder } = props
      if (placeholder) {
        return getFuncText(placeholder)
      }
      const globalPlaceholder = getConfig().numberInput.placeholder
      if (globalPlaceholder) {
        return getFuncText(globalPlaceholder)
      }
      return getI18n('vxe.base.pleaseInput')
    })

    const computeInpMaxLength = computed(() => {
      const { maxLength, maxlength } = props
      // 数值最大长度限制 16 位，包含小数
      return XEUtils.toNumber(maxLength || maxlength) || 16
    })

    const computeInpImmediate = computed(() => {
      const { immediate } = props
      return immediate
    })

    const computeNumValue = computed(() => {
      const { type } = props
      const { inputValue } = reactData
      return type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
    })

    const computeNumLabel = computed(() => {
      const { type, showCurrency, currencySymbol, autoFill } = props
      const { inputValue } = reactData
      const digitsValue = computeDigitsValue.value
      if (type === 'amount') {
        const num = XEUtils.toNumber(inputValue)
        let amountLabel = XEUtils.commafy(num, { digits: digitsValue })
        if (!autoFill) {
          const [iStr, dStr] = amountLabel.split('.')
          if (dStr) {
            const dRest = dStr.replace(/0+$/, '')
            amountLabel = dRest ? [iStr, '.', dRest].join('') : iStr
          }
        }
        if (showCurrency) {
          return `${currencySymbol || getI18n('vxe.numberInput.currencySymbol') || ''}${amountLabel}`
        }
        return amountLabel
      }
      return XEUtils.toString(inputValue)
    })

    const computeIsDisabledSubtractNumber = computed(() => {
      const { min } = props
      const { inputValue } = reactData
      const numValue = computeNumValue.value
      // 当有值时再进行判断
      if ((inputValue || inputValue === 0) && min !== null) {
        return numValue <= XEUtils.toNumber(min)
      }
      return false
    })

    const computeIsDisabledAddNumber = computed(() => {
      const { max } = props
      const { inputValue } = reactData
      const numValue = computeNumValue.value
      // 当有值时再进行判断
      if ((inputValue || inputValue === 0) && max !== null) {
        return numValue >= XEUtils.toNumber(max)
      }
      return false
    })

    const handleNumberString = (val: any) => {
      if (XEUtils.eqNull(val)) {
        return ''
      }
      return `${val}`
    }

    const getNumberValue = (val: any) => {
      const { exponential, autoFill } = props
      const inpMaxLength = computeInpMaxLength.value
      const digitsValue = computeDigitsValue.value
      const decimalsType = computeDecimalsType.value
      let restVal = ''
      if (decimalsType) {
        restVal = toFloatValueFixed(val, digitsValue)
        if (!autoFill) {
          restVal = handleNumberString(XEUtils.toNumber(restVal))
        }
      } else {
        restVal = handleNumberString(val)
      }
      if (exponential && (val === restVal || handleNumberString(val).toLowerCase() === XEUtils.toNumber(restVal).toExponential())) {
        return val
      }
      return restVal.slice(0, inpMaxLength)
    }

    const triggerEvent = (evnt: Event & { type: 'input' | 'change' | 'keydown' | 'keyup' | 'wheel' | 'click' | 'focus' | 'blur' }) => {
      const { inputValue } = reactData
      numberInputMethods.dispatchEvent(evnt.type, { value: inputValue }, evnt)
    }

    const handleChange = (val: number | null, inputValue: string, evnt: Event | { type: string }) => {
      const value = eqEmptyValue(val) ? null : Number(val)
      const isChange = value !== props.modelValue
      if (isChange) {
        internalData.isUM = true
        emit('update:modelValue', value)
      }
      if (reactData.inputValue !== inputValue) {
        nextTick(() => {
          reactData.inputValue = inputValue || ''
        })
      }
      numberInputMethods.dispatchEvent('input', { value }, evnt as Event)
      if (isChange) {
        numberInputMethods.dispatchEvent('change', { value }, evnt as Event)
        // 自动更新校验状态
        if ($xeForm && formItemInfo) {
          $xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, value)
        }
      }
    }

    const emitInputEvent = (inputValue: any, evnt: Event) => {
      const inpImmediate = computeInpImmediate.value
      const value = eqEmptyValue(inputValue) ? null : XEUtils.toNumber(inputValue)
      reactData.inputValue = inputValue
      if (inpImmediate) {
        handleChange(value, inputValue, evnt)
      } else {
        numberInputMethods.dispatchEvent('input', { value }, evnt)
      }
    }

    const inputEvent = (evnt: Event & { type: 'input' }) => {
      const inputElem = evnt.target as HTMLInputElement
      const value = inputElem.value
      emitInputEvent(value, evnt)
    }

    const changeEvent = (evnt: Event & { type: 'change' }) => {
      const inpImmediate = computeInpImmediate.value
      if (!inpImmediate) {
        triggerEvent(evnt)
      }
    }

    const focusEvent = (evnt: Event & { type: 'focus' }) => {
      const inputReadonly = computeInputReadonly.value
      if (!inputReadonly) {
        const { inputValue } = reactData
        reactData.inputValue = eqEmptyValue(inputValue) ? '' : `${XEUtils.toNumber(inputValue)}`
        reactData.isFocus = true
        reactData.isActivated = true
        triggerEvent(evnt)
      }
    }

    const clickPrefixEvent = (evnt: Event) => {
      const isDisabled = computeIsDisabled.value
      if (!isDisabled) {
        const { inputValue } = reactData
        numberInputMethods.dispatchEvent('prefix-click', { value: inputValue }, evnt)
      }
    }

    const clearValueEvent = (evnt: Event, value: VxeNumberInputPropTypes.ModelValue) => {
      focus()
      handleChange(null, '', evnt)
      numberInputMethods.dispatchEvent('clear', { value }, evnt)
    }

    const clickSuffixEvent = (evnt: Event) => {
      const isDisabled = computeIsDisabled.value
      if (!isDisabled) {
        const { inputValue } = reactData
        numberInputMethods.dispatchEvent('suffix-click', { value: inputValue }, evnt)
      }
    }

    const updateModel = (val: any) => {
      const { autoFill } = props
      const { inputValue } = reactData
      const digitsValue = computeDigitsValue.value
      const decimalsType = computeDecimalsType.value
      if (eqEmptyValue(val)) {
        reactData.inputValue = ''
      } else {
        let textValue = `${val}`
        if (decimalsType) {
          textValue = toFloatValueFixed(val, digitsValue)
          if (!autoFill) {
            textValue = `${XEUtils.toNumber(textValue)}`
          }
        }
        if (textValue !== inputValue) {
          reactData.inputValue = textValue
        }
      }
    }

    /**
     * 检查初始值
     */
    const initValue = () => {
      const { autoFill } = props
      const { inputValue } = reactData
      const digitsValue = computeDigitsValue.value
      const decimalsType = computeDecimalsType.value
      if (decimalsType) {
        if (inputValue) {
          let textValue = ''
          let validValue: number | null = null
          if (inputValue) {
            textValue = toFloatValueFixed(inputValue, digitsValue)
            validValue = XEUtils.toNumber(textValue)
            if (!autoFill) {
              textValue = `${validValue}`
            }
          }
          if (inputValue !== validValue) {
            handleChange(validValue, textValue, { type: 'init' })
          } else {
            reactData.inputValue = textValue
          }
        }
      }
    }

    const validMaxNum = (num: number | string) => {
      return props.max === null || XEUtils.toNumber(num) <= XEUtils.toNumber(props.max)
    }

    const validMinNum = (num: number | string) => {
      return props.min === null || XEUtils.toNumber(num) >= XEUtils.toNumber(props.min)
    }

    const afterCheckValue = () => {
      const { type, min, max, exponential } = props
      const { inputValue } = reactData
      const inputReadonly = computeInputReadonly.value
      if (!inputReadonly) {
        if (eqEmptyValue(inputValue)) {
          let inpNumVal = null
          let inpValue = inputValue
          if (min || min === 0) {
            inpNumVal = XEUtils.toNumber(min)
            inpValue = `${inpNumVal}`
          }
          handleChange(inpNumVal, `${inpValue || ''}`, { type: 'check' })
          return
        }
        if (inputValue || (min || max)) {
          let inpNumVal: number | string = type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
          if (!validMinNum(inpNumVal)) {
            inpNumVal = min
          } else if (!validMaxNum(inpNumVal)) {
            inpNumVal = max
          }
          if (exponential) {
            const inpStringVal = handleNumberString(inputValue).toLowerCase()
            if (inpStringVal === XEUtils.toNumber(inpNumVal).toExponential()) {
              inpNumVal = inpStringVal
            }
          }
          const inpValue = getNumberValue(inpNumVal)
          handleChange(eqEmptyValue(inpValue) ? null : Number(inpValue), inpValue, { type: 'check' })
        }
      }
    }

    const blurEvent = (evnt: Event & { type: 'blur' }) => {
      const { inputValue } = reactData
      const inpImmediate = computeInpImmediate.value
      const value = inputValue ? Number(inputValue) : null
      if (!inpImmediate) {
        handleChange(value, handleNumberString(inputValue), evnt)
      }
      afterCheckValue()
      reactData.isFocus = false
      reactData.isActivated = false
      numberInputMethods.dispatchEvent('blur', { value }, evnt)
      // 自动更新校验状态
      if ($xeForm && formItemInfo) {
        $xeForm.triggerItemEvent(evnt, formItemInfo.itemConfig.field, value)
      }
    }

    // 数值
    const numberChange = (isPlus: boolean, evnt: Event) => {
      const { min, max, type } = props
      const { inputValue } = reactData
      const stepValue = computeStepValue.value
      const numValue = type === 'integer' ? XEUtils.toInteger(handleNumber(inputValue)) : XEUtils.toNumber(handleNumber(inputValue))
      const newValue = isPlus ? XEUtils.add(numValue, stepValue) : XEUtils.subtract(numValue, stepValue)
      let restNum: number | string
      if (!validMinNum(newValue)) {
        restNum = min
      } else if (!validMaxNum(newValue)) {
        restNum = max
      } else {
        restNum = newValue
      }
      emitInputEvent(getNumberValue(restNum), evnt as (Event & { type: 'input' }))
    }

    const numberNextEvent = (evnt: Event) => {
      const isDisabled = computeIsDisabled.value
      const formReadonly = computeFormReadonly.value
      const isDisabledSubtractNumber = computeIsDisabledSubtractNumber.value
      numberStopDown()
      if (!isDisabled && !formReadonly && !isDisabledSubtractNumber) {
        numberChange(false, evnt)
      }
      reactData.isActivated = true
      numberInputMethods.dispatchEvent('next-number', { value: reactData.inputValue }, evnt)
    }

    const numberDownNextEvent = (evnt: Event) => {
      internalData.dnTimeout = setTimeout(() => {
        numberNextEvent(evnt)
        numberDownNextEvent(evnt)
      }, 60)
    }

    const numberPrevEvent = (evnt: Event) => {
      const isDisabled = computeIsDisabled.value
      const formReadonly = computeFormReadonly.value
      const isDisabledAddNumber = computeIsDisabledAddNumber.value
      numberStopDown()
      if (!isDisabled && !formReadonly && !isDisabledAddNumber) {
        numberChange(true, evnt)
      }
      reactData.isActivated = true
      numberInputMethods.dispatchEvent('prev-number', { value: reactData.inputValue }, evnt)
    }

    const numberKeydownEvent = (evnt: KeyboardEvent) => {
      const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)
      const isDwArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)
      if (isUpArrow || isDwArrow) {
        evnt.preventDefault()
        if (isUpArrow) {
          numberPrevEvent(evnt)
        } else {
          numberNextEvent(evnt)
        }
      }
    }

    const keydownEvent = (evnt: KeyboardEvent & { type: 'keydown' }) => {
      const { exponential, controls } = props
      const inputReadonly = computeInputReadonly.value
      const isCtrlKey = evnt.ctrlKey
      const isShiftKey = evnt.shiftKey
      const isAltKey = evnt.altKey
      const isMetaKey = evnt.metaKey
      const keyCode = evnt.keyCode
      const isEsc = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ESCAPE)
      const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP)
      const isDwArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN)
      if (!isCtrlKey && !isShiftKey && !isAltKey && !isMetaKey && (globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.SPACEBAR) || ((!exponential || keyCode !== 69) && (keyCode >= 65 && keyCode <= 90)) || (keyCode >= 186 && keyCode <= 188) || keyCode >= 191)) {
        evnt.preventDefault()
      }
      if (isEsc) {
        afterCheckValue()
      } else if (isUpArrow || isDwArrow) {
        if (controls && !inputReadonly) {
          numberKeydownEvent(evnt)
        }
      }
      triggerEvent(evnt)
    }

    const keyupEvent = (evnt: KeyboardEvent & { type: 'keyup' }) => {
      triggerEvent(evnt)
    }

    // 数值

    const numberStopDown = () => {
      const { dnTimeout } = internalData
      if (dnTimeout) {
        clearTimeout(dnTimeout)
        internalData.dnTimeout = undefined
      }
    }

    const numberDownPrevEvent = (evnt: Event) => {
      internalData.dnTimeout = setTimeout(() => {
        numberPrevEvent(evnt)
        numberDownPrevEvent(evnt)
      }, 60)
    }

    const numberMousedownEvent = (evnt: MouseEvent) => {
      numberStopDown()
      if (evnt.button === 0) {
        const isPrevNumber = hasClass(evnt.currentTarget, 'is--prev')
        if (isPrevNumber) {
          numberPrevEvent(evnt)
        } else {
          numberNextEvent(evnt)
        }
        internalData.dnTimeout = setTimeout(() => {
          if (isPrevNumber) {
            numberDownPrevEvent(evnt)
          } else {
            numberDownNextEvent(evnt)
          }
        }, 500)
      }
    }

    const wheelEvent = (evnt: WheelEvent & {
      type: 'wheel';
      wheelDelta: number;
    }) => {
      const inputReadonly = computeInputReadonly.value
      if (props.controls && !inputReadonly) {
        if (reactData.isActivated) {
          evnt.stopPropagation()
          evnt.preventDefault()
          const delta = evnt.deltaY
          if (delta > 0) {
            numberNextEvent(evnt)
          } else if (delta < 0) {
            numberPrevEvent(evnt)
          }
        }
      }
      triggerEvent(evnt)
    }

    const clickEvent = (evnt: Event & { type: 'click' }) => {
      triggerEvent(evnt)
    }

    // 全局事件
    const handleGlobalMousedownEvent = (evnt: Event) => {
      const { isActivated } = reactData
      const el = refElem.value
      const panelElem = refInputPanel.value
      const isDisabled = computeIsDisabled.value
      const inputReadonly = computeInputReadonly.value
      const inpImmediate = computeInpImmediate.value
      if (!isDisabled && !inputReadonly && isActivated) {
        reactData.isActivated = getEventTargetNode(evnt, el).flag || getEventTargetNode(evnt, panelElem).flag
        if (!reactData.isActivated) {
          if (!inpImmediate) {
            const { inputValue } = reactData
            const value = inputValue ? Number(inputValue) : null
            handleChange(value, handleNumberString(inputValue), evnt)
          }
          afterCheckValue()
        }
      }
    }

    const handleGlobalKeydownEvent = (evnt: KeyboardEvent) => {
      const { clearable } = props
      const isDisabled = computeIsDisabled.value
      const inputReadonly = computeInputReadonly.value
      if (!isDisabled && !inputReadonly) {
        const isTab = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.TAB)
        const isDel = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.DELETE)
        let isActivated = reactData.isActivated
        if (isTab) {
          if (isActivated) {
            afterCheckValue()
          }
          isActivated = false
          reactData.isActivated = isActivated
        }
        if (isDel && clearable) {
          if (isActivated) {
            clearValueEvent(evnt, null)
          }
        }
      }
    }

    const handleGlobalBlurEvent = () => {
      const { isActivated } = reactData
      if (isActivated) {
        afterCheckValue()
      }
    }

    const renderNumberIcon = () => {
      const isDisabledAddNumber = computeIsDisabledAddNumber.value
      const isDisabledSubtractNumber = computeIsDisabledSubtractNumber.value
      return h('div', {
        class: 'vxe-input--control-icon'
      }, [
        h('div', {
          class: 'vxe-input--number-icon'
        }, [
          h('div', {
            class: ['vxe-input--number-btn is--prev', {
              'is--disabled': isDisabledAddNumber
            }],
            onMousedown: numberMousedownEvent,
            onMouseup: numberStopDown,
            onMouseleave: numberStopDown
          }, [
            h('i', {
              class: getIcon().NUMBER_INPUT_PREV_NUM
            })
          ]),
          h('div', {
            class: ['vxe-input--number-btn is--next', {
              'is--disabled': isDisabledSubtractNumber
            }],
            onMousedown: numberMousedownEvent,
            onMouseup: numberStopDown,
            onMouseleave: numberStopDown
          }, [
            h('i', {
              class: getIcon().NUMBER_INPUT_NEXT_NUM
            })
          ])
        ])
      ])
    }

    const renderPrefixIcon = () => {
      const { prefixIcon } = props
      const prefixSlot = slots.prefix
      return prefixSlot || prefixIcon
        ? h('div', {
          class: 'vxe-number-input--prefix',
          onClick: clickPrefixEvent
        }, [
          h('div', {
            class: 'vxe-number-input--prefix-icon'
          }, prefixSlot
            ? getSlotVNs(prefixSlot({}))
            : [
                h('i', {
                  class: prefixIcon
                })
              ])
        ])
        : null
    }

    const renderSuffixIcon = () => {
      const { suffixIcon } = props
      const { inputValue } = reactData
      const suffixSlot = slots.suffix
      const isDisabled = computeIsDisabled.value
      const isClearable = computeIsClearable.value
      return h('div', {
        class: ['vxe-number-input--suffix', {
          'is--clear': isClearable && !isDisabled && !(inputValue === '' || XEUtils.eqNull(inputValue))
        }]
      }, [
        isClearable
          ? h('div', {
            class: 'vxe-number-input--clear-icon',
            onClick: clearValueEvent
          }, [
            h('i', {
              class: getIcon().INPUT_CLEAR
            })
          ])
          : createCommentVNode(),
        renderExtraSuffixIcon(),
        suffixSlot || suffixIcon
          ? h('div', {
            class: 'vxe-number-input--suffix-icon',
            onClick: clickSuffixEvent
          }, suffixSlot
            ? getSlotVNs(suffixSlot({}))
            : [
                h('i', {
                  class: suffixIcon
                })
              ])
          : createCommentVNode()
      ])
    }

    const renderExtraSuffixIcon = () => {
      const { controls } = props
      const inputReadonly = computeInputReadonly.value
      if (controls && !inputReadonly) {
        return renderNumberIcon()
      }
      return createCommentVNode()
    }

    const dispatchEvent = (type: ValueOf<VxeNumberInputEmits>, params: Record<string, any>, evnt: Event | null) => {
      emit(type, createEvent(evnt, { $numberInput: $xeNumberInput }, params))
    }

    numberInputMethods = {
      dispatchEvent,

      focus () {
        const inputReadonly = computeInputReadonly.value
        if (!inputReadonly) {
          const inputElem = refInputTarget.value
          reactData.isActivated = true
          inputElem.focus()
        }
        return nextTick()
      },
      blur () {
        const inputElem = refInputTarget.value
        inputElem.blur()
        reactData.isActivated = false
        return nextTick()
      },
      select () {
        const inputElem = refInputTarget.value
        inputElem.select()
        reactData.isActivated = false
        return nextTick()
      }
    }

    Object.assign($xeNumberInput, numberInputMethods)

    const renderVN = () => {
      const { className, controls, type, align, name, autocomplete, autoComplete } = props
      const { inputValue, isFocus, isActivated } = reactData
      const vSize = computeSize.value
      const isDisabled = computeIsDisabled.value
      const formReadonly = computeFormReadonly.value
      const numLabel = computeNumLabel.value
      if (formReadonly) {
        return h('div', {
          ref: refElem,
          class: ['vxe-number-input--readonly', `type--${type}`, className]
        }, numLabel)
      }
      const inputReadonly = computeInputReadonly.value
      const inpMaxLength = computeInpMaxLength.value
      const inpPlaceholder = computeInpPlaceholder.value
      const isClearable = computeIsClearable.value
      const prefix = renderPrefixIcon()
      const suffix = renderSuffixIcon()
      return h('div', {
        ref: refElem,
        class: ['vxe-number-input', `type--${type}`, className, {
          [`size--${vSize}`]: vSize,
          [`is--${align}`]: align,
          'is--controls': controls && !inputReadonly,
          'is--prefix': !!prefix,
          'is--suffix': !!suffix,
          'is--disabled': isDisabled,
          'is--active': isActivated,
          'show--clear': isClearable && !isDisabled && !(inputValue === '' || XEUtils.eqNull(inputValue))
        }],
        spellcheck: false
      }, [
        prefix || createCommentVNode(),
        h('div', {
          class: 'vxe-number-input--wrapper'
        }, [
          h('input', {
            ref: refInputTarget,
            class: 'vxe-number-input--inner',
            value: !isFocus && type === 'amount' ? numLabel : inputValue,
            name,
            type: 'text',
            placeholder: inpPlaceholder,
            maxlength: inpMaxLength,
            readonly: inputReadonly,
            disabled: isDisabled,
            autocomplete: autoComplete || autocomplete,
            onKeydown: keydownEvent,
            onKeyup: keyupEvent,
            onWheel: wheelEvent,
            onClick: clickEvent,
            onInput: inputEvent,
            onChange: changeEvent,
            onFocus: focusEvent,
            onBlur: blurEvent
          })
        ]),
        suffix || createCommentVNode()
      ])
    }

    $xeNumberInput.renderVN = renderVN

    watch(() => props.modelValue, (val) => {
      if (!internalData.isUM) {
        updateModel(val)
      }
      internalData.isUM = false
    })

    watch(() => props.type, () => {
      // 切换类型是重置内置变量
      Object.assign(reactData, {
        inputValue: props.modelValue
      })
      initValue()
    })

    onMounted(() => {
      globalEvents.on($xeNumberInput, 'mousedown', handleGlobalMousedownEvent)
      globalEvents.on($xeNumberInput, 'keydown', handleGlobalKeydownEvent)
      globalEvents.on($xeNumberInput, 'blur', handleGlobalBlurEvent)
    })

    onBeforeUnmount(() => {
      reactData.isFocus = false
      numberStopDown()
      afterCheckValue()
      globalEvents.off($xeNumberInput, 'mousedown')
      globalEvents.off($xeNumberInput, 'keydown')
      globalEvents.off($xeNumberInput, 'blur')
    })

    initValue()

    return $xeNumberInput
  },
  render () {
    return this.renderVN()
  }
})
