import { createVNode as _createVNode, Fragment as _Fragment, mergeProps as _mergeProps } from "vue";
// Styles
import "./VNumberInput.css";

// Components
import { VBtn } from "../../components/VBtn/index.mjs";
import { VDefaultsProvider } from "../../components/VDefaultsProvider/index.mjs";
import { VDivider } from "../../components/VDivider/index.mjs";
import { makeVTextFieldProps, VTextField } from "../../components/VTextField/VTextField.mjs"; // Composables
import { useHold } from "./hold.mjs";
import { useFocus } from "../../composables/focus.mjs";
import { useForm } from "../../composables/form.mjs";
import { forwardRefs } from "../../composables/forwardRefs.mjs";
import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Utilities
import { computed, nextTick, onMounted, ref, shallowRef, watch, watchEffect } from 'vue';
import { clamp, genericComponent, omit, propsFactory, useRender } from "../../util/index.mjs"; // Types
const makeVNumberInputProps = propsFactory({
  controlVariant: {
    type: String,
    default: 'default'
  },
  inset: Boolean,
  hideInput: Boolean,
  modelValue: {
    type: Number,
    default: null
  },
  min: {
    type: Number,
    default: Number.MIN_SAFE_INTEGER
  },
  max: {
    type: Number,
    default: Number.MAX_SAFE_INTEGER
  },
  step: {
    type: Number,
    default: 1
  },
  precision: {
    type: Number,
    default: 0
  },
  ...omit(makeVTextFieldProps(), ['modelValue', 'validationValue'])
}, 'VNumberInput');
export const VNumberInput = genericComponent()({
  name: 'VNumberInput',
  props: {
    ...makeVNumberInputProps()
  },
  emits: {
    'update:modelValue': val => true
  },
  setup(props, _ref) {
    let {
      slots
    } = _ref;
    const vTextFieldRef = ref();
    const {
      holdStart,
      holdStop
    } = useHold({
      toggleUpDown
    });
    const form = useForm(props);
    const controlsDisabled = computed(() => form.isDisabled.value || form.isReadonly.value);
    const {
      isFocused,
      focus,
      blur
    } = useFocus(props);
    function correctPrecision(val) {
      let precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : props.precision;
      const fixed = precision == null ? String(val) : val.toFixed(precision);
      return isFocused.value ? Number(fixed).toString() // trim zeros
      : fixed;
    }
    const model = useProxiedModel(props, 'modelValue', null, val => val ?? null, val => val == null ? val ?? null : clamp(+val, props.min, props.max));
    const _inputText = shallowRef(null);
    watchEffect(() => {
      if (isFocused.value && !controlsDisabled.value) {
        // ignore external changes
      } else if (model.value == null) {
        _inputText.value = null;
      } else if (!isNaN(model.value)) {
        _inputText.value = correctPrecision(model.value);
      }
    });
    const inputText = computed({
      get: () => _inputText.value,
      set(val) {
        if (val === null || val === '') {
          model.value = null;
          _inputText.value = null;
        } else if (!isNaN(+val) && +val <= props.max && +val >= props.min) {
          model.value = +val;
          _inputText.value = val;
        }
      }
    });
    const canIncrease = computed(() => {
      if (controlsDisabled.value) return false;
      return (model.value ?? 0) + props.step <= props.max;
    });
    const canDecrease = computed(() => {
      if (controlsDisabled.value) return false;
      return (model.value ?? 0) - props.step >= props.min;
    });
    const controlVariant = computed(() => {
      return props.hideInput ? 'stacked' : props.controlVariant;
    });
    const incrementIcon = computed(() => controlVariant.value === 'split' ? '$plus' : '$collapse');
    const decrementIcon = computed(() => controlVariant.value === 'split' ? '$minus' : '$expand');
    const controlNodeSize = computed(() => controlVariant.value === 'split' ? 'default' : 'small');
    const controlNodeDefaultHeight = computed(() => controlVariant.value === 'stacked' ? 'auto' : '100%');
    const incrementSlotProps = computed(() => ({
      props: {
        onClick: onControlClick,
        onPointerup: onControlMouseup,
        onPointerdown: onUpControlMousedown
      }
    }));
    const decrementSlotProps = computed(() => ({
      props: {
        onClick: onControlClick,
        onPointerup: onControlMouseup,
        onPointerdown: onDownControlMousedown
      }
    }));
    watch(() => props.precision, () => formatInputValue());
    onMounted(() => {
      clampModel();
    });
    function inferPrecision(value) {
      if (value == null) return 0;
      const str = value.toString();
      const idx = str.indexOf('.');
      return ~idx ? str.length - idx : 0;
    }
    function toggleUpDown() {
      let increment = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
      if (controlsDisabled.value) return;
      if (model.value == null) {
        inputText.value = correctPrecision(clamp(0, props.min, props.max));
        return;
      }
      let inferredPrecision = Math.max(inferPrecision(model.value), inferPrecision(props.step));
      if (props.precision != null) inferredPrecision = Math.max(inferredPrecision, props.precision);
      if (increment) {
        if (canIncrease.value) inputText.value = correctPrecision(model.value + props.step, inferredPrecision);
      } else {
        if (canDecrease.value) inputText.value = correctPrecision(model.value - props.step, inferredPrecision);
      }
    }
    function onBeforeinput(e) {
      if (!e.data) return;
      const existingTxt = e.target?.value;
      const selectionStart = e.target?.selectionStart;
      const selectionEnd = e.target?.selectionEnd;
      const potentialNewInputVal = existingTxt ? existingTxt.slice(0, selectionStart) + e.data + existingTxt.slice(selectionEnd) : e.data;
      // Only numbers, "-", "." are allowed
      // AND "-", "." are allowed only once
      // AND "-" is only allowed at the start
      if (!/^-?(\d+(\.\d*)?|(\.\d+)|\d*|\.)$/.test(potentialNewInputVal)) {
        e.preventDefault();
      }
      if (props.precision == null) return;

      // Ignore decimal digits above precision limit
      if (potentialNewInputVal.split('.')[1]?.length > props.precision) {
        e.preventDefault();
      }
      // Ignore decimal separator when precision = 0
      if (props.precision === 0 && potentialNewInputVal.includes('.')) {
        e.preventDefault();
      }
    }
    async function onKeydown(e) {
      if (['Enter', 'ArrowLeft', 'ArrowRight', 'Backspace', 'Delete', 'Tab'].includes(e.key) || e.ctrlKey) return;
      if (['ArrowDown', 'ArrowUp'].includes(e.key)) {
        e.preventDefault();
        clampModel();
        // _model is controlled, so need to wait until props['modelValue'] is updated
        await nextTick();
        if (e.key === 'ArrowDown') {
          toggleUpDown(false);
        } else {
          toggleUpDown();
        }
      }
    }
    function onControlClick(e) {
      e.stopPropagation();
    }
    function onControlMouseup(e) {
      const el = e.currentTarget;
      el?.releasePointerCapture(e.pointerId);
      e.preventDefault();
      e.stopPropagation();
      holdStop();
    }
    function onUpControlMousedown(e) {
      const el = e.currentTarget;
      el?.setPointerCapture(e.pointerId);
      e.preventDefault();
      e.stopPropagation();
      holdStart('up');
    }
    function onDownControlMousedown(e) {
      const el = e.currentTarget;
      el?.setPointerCapture(e.pointerId);
      e.preventDefault();
      e.stopPropagation();
      holdStart('down');
    }
    function clampModel() {
      if (controlsDisabled.value) return;
      if (!vTextFieldRef.value) return;
      const actualText = vTextFieldRef.value.value;
      if (actualText && !isNaN(+actualText)) {
        inputText.value = correctPrecision(clamp(+actualText, props.min, props.max));
      } else {
        inputText.value = null;
      }
    }
    function formatInputValue() {
      if (controlsDisabled.value) return;
      if (model.value === null || isNaN(model.value)) {
        inputText.value = null;
        return;
      }
      inputText.value = props.precision == null ? String(model.value) : model.value.toFixed(props.precision);
    }
    function trimDecimalZeros() {
      if (controlsDisabled.value) return;
      if (model.value === null || isNaN(model.value)) {
        inputText.value = null;
        return;
      }
      inputText.value = model.value.toString();
    }
    function onFocus() {
      focus();
      trimDecimalZeros();
    }
    function onBlur() {
      blur();
      clampModel();
    }
    useRender(() => {
      const {
        modelValue: _,
        ...textFieldProps
      } = VTextField.filterProps(props);
      function incrementControlNode() {
        return !slots.increment ? _createVNode(VBtn, {
          "disabled": !canIncrease.value,
          "flat": true,
          "key": "increment-btn",
          "height": controlNodeDefaultHeight.value,
          "data-testid": "increment",
          "aria-hidden": "true",
          "icon": incrementIcon.value,
          "onClick": onControlClick,
          "onPointerup": onControlMouseup,
          "onPointerdown": onUpControlMousedown,
          "size": controlNodeSize.value,
          "tabindex": "-1"
        }, null) : _createVNode(VDefaultsProvider, {
          "key": "increment-defaults",
          "defaults": {
            VBtn: {
              disabled: !canIncrease.value,
              flat: true,
              height: controlNodeDefaultHeight.value,
              size: controlNodeSize.value,
              icon: incrementIcon.value
            }
          }
        }, {
          default: () => [slots.increment(incrementSlotProps.value)]
        });
      }
      function decrementControlNode() {
        return !slots.decrement ? _createVNode(VBtn, {
          "disabled": !canDecrease.value,
          "flat": true,
          "key": "decrement-btn",
          "height": controlNodeDefaultHeight.value,
          "data-testid": "decrement",
          "aria-hidden": "true",
          "icon": decrementIcon.value,
          "size": controlNodeSize.value,
          "tabindex": "-1",
          "onClick": onControlClick,
          "onPointerup": onControlMouseup,
          "onPointerdown": onDownControlMousedown
        }, null) : _createVNode(VDefaultsProvider, {
          "key": "decrement-defaults",
          "defaults": {
            VBtn: {
              disabled: !canDecrease.value,
              flat: true,
              height: controlNodeDefaultHeight.value,
              size: controlNodeSize.value,
              icon: decrementIcon.value
            }
          }
        }, {
          default: () => [slots.decrement(decrementSlotProps.value)]
        });
      }
      function controlNode() {
        return _createVNode("div", {
          "class": "v-number-input__control"
        }, [decrementControlNode(), _createVNode(VDivider, {
          "vertical": controlVariant.value !== 'stacked'
        }, null), incrementControlNode()]);
      }
      function dividerNode() {
        return !props.hideInput && !props.inset ? _createVNode(VDivider, {
          "vertical": true
        }, null) : undefined;
      }
      const appendInnerControl = controlVariant.value === 'split' ? _createVNode("div", {
        "class": "v-number-input__control"
      }, [_createVNode(VDivider, {
        "vertical": true
      }, null), incrementControlNode()]) : props.reverse || controlVariant.value === 'hidden' ? undefined : _createVNode(_Fragment, null, [dividerNode(), controlNode()]);
      const hasAppendInner = slots['append-inner'] || appendInnerControl;
      const prependInnerControl = controlVariant.value === 'split' ? _createVNode("div", {
        "class": "v-number-input__control"
      }, [decrementControlNode(), _createVNode(VDivider, {
        "vertical": true
      }, null)]) : props.reverse && controlVariant.value !== 'hidden' ? _createVNode(_Fragment, null, [controlNode(), dividerNode()]) : undefined;
      const hasPrependInner = slots['prepend-inner'] || prependInnerControl;
      return _createVNode(VTextField, _mergeProps({
        "ref": vTextFieldRef,
        "modelValue": inputText.value,
        "onUpdate:modelValue": $event => inputText.value = $event,
        "validationValue": model.value,
        "onBeforeinput": onBeforeinput,
        "onFocus": onFocus,
        "onBlur": onBlur,
        "onKeydown": onKeydown,
        "class": ['v-number-input', {
          'v-number-input--default': controlVariant.value === 'default',
          'v-number-input--hide-input': props.hideInput,
          'v-number-input--inset': props.inset,
          'v-number-input--reverse': props.reverse,
          'v-number-input--split': controlVariant.value === 'split',
          'v-number-input--stacked': controlVariant.value === 'stacked'
        }, props.class]
      }, textFieldProps, {
        "style": props.style,
        "inputmode": "decimal"
      }), {
        ...slots,
        'append-inner': hasAppendInner ? function () {
          for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
          }
          return _createVNode(_Fragment, null, [slots['append-inner']?.(...args), appendInnerControl]);
        } : undefined,
        'prepend-inner': hasPrependInner ? function () {
          for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
            args[_key2] = arguments[_key2];
          }
          return _createVNode(_Fragment, null, [prependInnerControl, slots['prepend-inner']?.(...args)]);
        } : undefined
      });
    });
    return forwardRefs({}, vTextFieldRef);
  }
});
//# sourceMappingURL=VNumberInput.mjs.map