<script setup lang="ts">
import { crudLabel } from '../../../crud/style'
import type { HTMLAttributes } from "vue"
import { computed } from "vue"
import { useVModel } from "@vueuse/core"
import { cn } from "../../../lib/cn"

const props = withDefaults(
  defineProps<{
    defaultValue?: string | number
    modelValue?: string | number | null
    class?: HTMLAttributes["class"]
    /** 原生 input type；数字用 number */
    type?: string
    /** 小数位数；blur 时按位四舍五入（仅 type=number） */
    precision?: number
    /**
     * 是否显示 number 步进器；false 隐藏（对齐常见 InputNumber.controls）
     * 默认 true（保持原生外观）；业务常传 false
     */
    controls?: boolean
    step?: string | number
    placeholder?: string
    disabled?: boolean
    /** 有值时显示清除；默认 true（password 除外） */
    clearable?: boolean
  }>(),
  {
    controls: true,
    clearable: undefined,
  },
)

const emits = defineEmits<{
  (e: "update:modelValue", payload: string | number | null): void
}>()

const modelValue = useVModel(props, "modelValue", emits, {
  passive: true,
  defaultValue: props.defaultValue,
})

const isNumber = computed(() => props.type === "number")
const isPassword = computed(() => props.type === "password")
const showClear = computed(() => {
  if (props.disabled || isPassword.value) return false
  const on = props.clearable !== false
  if (!on) return false
  const v = modelValue.value
  return v != null && v !== ""
})
const hideControls = computed(
  () => isNumber.value && props.controls === false,
)

/** number 未显式传 step 时，按 precision 生成（避免原生默认 step=1 挡小数） */
const resolvedStep = computed(() => {
  if (!isNumber.value) return props.step
  if (props.step != null && props.step !== "") return props.step
  if (props.precision == null) return "any"
  const p = Math.max(0, Math.floor(props.precision))
  if (p === 0) return 1
  return (1 / 10 ** p).toFixed(p)
})

function roundToPrecision(n: number) {
  if (props.precision == null || !Number.isFinite(n)) return n
  const p = Math.max(0, Math.floor(props.precision))
  const f = 10 ** p
  return Math.round(n * f) / f
}

function onUpdate(raw: string | number) {
  if (!isNumber.value) {
    modelValue.value = raw
    return
  }
  if (raw === "" || raw == null) {
    modelValue.value = null
    return
  }
  const n = typeof raw === "number" ? raw : Number(raw)
  modelValue.value = Number.isFinite(n) ? n : raw
}

function onBlur() {
  if (!isNumber.value || props.precision == null) return
  const cur = modelValue.value
  if (cur == null || cur === "") return
  const n = typeof cur === "number" ? cur : Number(cur)
  if (!Number.isFinite(n)) return
  modelValue.value = roundToPrecision(n)
}

function clear() {
  modelValue.value = isNumber.value ? null : ""
}
</script>

<template>
  <div
    data-slot="input-wrap"
    :class="cn('vm-input-wrap', showClear && 'has-clear', props.class)"
  >
    <input
      data-slot="input"
      :type="type"
      :step="resolvedStep"
      :placeholder="placeholder"
      :disabled="disabled"
      :value="modelValue ?? ''"
      :class="cn(
        'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-[var(--vm-control-height)] w-full min-w-0 rounded-[var(--vm-control-radius)] border bg-transparent px-[var(--vm-control-padding-x)] py-0 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
        'focus-visible:border-[var(--focus-border)] focus-visible:ring-0 focus-visible:shadow-none',
        'aria-invalid:border-destructive aria-invalid:ring-0',
        hideControls && 'is-no-controls',
        showClear && 'is-clearable',
      )"
      @input="onUpdate(($event.target as HTMLInputElement).value)"
      @blur="onBlur"
    >
    <i
      v-if="showClear"
      class="ri-close-circle-fill vm-input-wrap__clear"
      role="button"
      tabindex="-1"
      :aria-label="crudLabel('clear')"
      @mousedown.prevent
      @click="clear"
    />
  </div>
</template>

<style scoped>
/* controls: false → 隐藏原生 number 步进器 */
input.is-no-controls[type='number'] {
  -moz-appearance: textfield;
}
input.is-no-controls[type='number']::-webkit-outer-spin-button,
input.is-no-controls[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.vm-input-wrap {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
}

.vm-input-wrap input.is-clearable {
  padding-right: 28px;
}

.vm-input-wrap__clear {
  position: absolute;
  top: 50%;
  right: var(--vm-control-padding-x);
  z-index: 1;
  color: var(--muted-foreground);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transform: translateY(-50%);
}

.vm-input-wrap__clear:hover {
  color: var(--foreground);
}
</style>

<style>
/* 与 CRUD 表单控件高度约定对齐（原 base.css data-slot 规则） */
[data-slot='input'] {
  height: var(--vm-control-height) !important;
  min-height: var(--vm-control-height);
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  font-size: var(--vm-control-font-size) !important;
  line-height: 1.4;
}
</style>
