<script setup lang="ts">
import type { IconType } from '@bagelink/vue'
import { Icon, Btn } from '@bagelink/vue'
import { nextTick, onMounted, watch } from 'vue'

type NumberLayout = 'default' | 'vertical' | 'horizontal'

interface NumberInputProps {
	modelValue?: number | string
	min?: number
	max?: number
	step?: number
	spinner?: boolean
	iconStart?: IconType
	icon?: IconType
	label?: string
	defaultValue?: number | string
	placeholder?: string
	disabled?: boolean
	required?: boolean
	id?: string
	helptext?: string
	layout?: NumberLayout
	center?: boolean
	padZero?: number
	useGrouping?: boolean
}

const {
	modelValue,
	min,
	max,
	step = 1,
	spinner = true,
	iconStart,
	icon,
	label,
	placeholder,
	disabled,
	required,
	helptext,
	layout,
	id,
	padZero = 1,
	defaultValue,
	useGrouping = true
} = defineProps<NumberInputProps>()

const emit = defineEmits(['update:modelValue'])

let numberValue = $ref<number>()

onMounted(() => {
	const num = modelValue !== undefined ? Number.parseFloat(`${modelValue}`) : undefined
	const defaultNum = defaultValue !== undefined ? Number.parseFloat(`${defaultValue}`) : undefined
	numberValue = num ?? defaultNum
})

const btnLayouts: NumberLayout[] = ['horizontal', 'vertical']

function add(...numbers: (number | undefined)[]) {
	const numArr: number[] = numbers.map(n => n || 0)
	const precision = step.toString().split('.')[1]?.length || 0
	return Number.parseFloat(numArr.reduce((acc, curr) => acc + curr, 0).toFixed(precision))
}

function subtract(...numbers: (number | undefined)[]) {
	const numArr: number[] = numbers.map(n => n || 0)
	const firstNum = numArr.shift() || 0
	const precision = step.toString().split('.')[1]?.length || 0
	return Number.parseFloat(numArr.reduce((acc, curr) => acc - curr, firstNum).toFixed(precision))
}

const canAdd = $computed(() => max === undefined || add(numberValue, step) <= max)
const canDecrement = $computed(() => min === undefined || subtract(numberValue, step) >= min)

// Methods
function increment() {
	if (!canAdd) return
	numberValue = add(numberValue ?? 0, step)
	emit('update:modelValue', numberValue)
}

function decrement() {
	if (!canDecrement) return
	numberValue = subtract(numberValue || 0, step)
	emit('update:modelValue', numberValue)
}

function formatNumber(num: number) {
	if (Number.isNaN(num)) return ''
	const formatter = new Intl.NumberFormat('en-US', {
		minimumFractionDigits: 0,
		maximumFractionDigits: 20,
		useGrouping
	})

	const formattedNum = formatter.format(num)
	const [integerPart, decimalPart] = formattedNum.split('.')

	// Add padding zeros if needed
	const padding = padZero && padZero > integerPart.replace(/,/g, '').length
		? '0'.repeat(padZero - integerPart.replace(/,/g, '').length)
		: ''

	return decimalPart
		? `${padding}${integerPart}.${decimalPart}`
		: `${padding}${integerPart}`
}

let formattedValue = $ref('')
function inputHandler() {
	const numeric = formattedValue.replace(/[^\d.-]/g, '')
	const emptyValue = ['', '-', '.', '-.'].includes(numeric)
	const isTypingDecimal = formattedValue.endsWith('.')

	if (emptyValue || isTypingDecimal) {
		emit('update:modelValue', numeric === '-' ? '-' : numeric)
		return
	}

	numberValue = Number.parseFloat(numeric)
	formattedValue = emptyValue ? '' : formatNumber(numberValue)
	emit('update:modelValue', numberValue)
}

watch(() => numberValue, () => {
	nextTick(() => {
		formattedValue = numberValue !== undefined ? formatNumber(numberValue) : ''
	})
})

watch(() => modelValue, (newVal) => {
	if (newVal !== numberValue)	{
		if (typeof newVal === 'string' && (newVal === '-' || newVal.endsWith('.'))) {
			formattedValue = newVal
			return
		}
		numberValue = Number.parseFloat(`${newVal}`) || 0
	}
}, { immediate: true })
</script>

<template>
	<div
		class="bagel-input num-input"
		:class="{
			textInputSpinnerWrap: icon || spinner,
			txtInputIconStart: iconStart,
		}"
	>
		<div :for="id">
			<label v-if="label" class="block">
				{{ label }}
			</label>
			<div class="gap-025" :class="{ 'column flex': layout === 'vertical', 'flex': layout === 'horizontal' }">
				<Btn v-if="layout && btnLayouts.includes(layout)" flat icon="add" class="radius" :class="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="increment" />
				<input
					:id
					v-model.trim="formattedValue"
					v-pattern.number
					type="text"
					:class="{
						'txt-center': center,
						'min0': layout,
						'bgl-number-input': layout !== 'vertical' && layout !== 'horizontal',
					}"
					:placeholder="placeholder || label"
					:disabled
					:required
					:readonly="disabled"
					inputmode="decimal"
					autocomplete="off"
					:pattern="useGrouping ? '^-?\\d{1,3}(,\\d{3})*(\.\\d+)?$' : '^-?\\d*\.?\d*$'"
					autocorrect="off"
					spellcheck="false"
					@input="inputHandler"
					@keydown.up.prevent="increment"
					@keydown.down.prevent="decrement"
				>
				<p v-if="helptext" class="opacity-7 light">
					{{ helptext }}
				</p>
				<Icon
					v-if="iconStart"
					class="iconStart"
					:icon="iconStart"
				/>
				<Icon
					v-if="icon"
					:icon
				/>
				<Btn v-if="layout && btnLayouts.includes(layout)" flat icon="remove" class="radius" :class="[{ 'bgl-big-ctrl-num-btn': layout === 'vertical' }]" tabindex="-1" @click="decrement" />

				<div v-if="spinner && (!layout || !btnLayouts.includes(layout))" class="flex column spinner">
					<Btn icon="add" flat thin class="bgl-ctrl-num-btn ctrl-add top-bgl-ctrl-num-btn" :disabled="!canAdd" tabindex="-1" @click="increment" />
					<Btn icon="remove" flat thin class="bgl-ctrl-num-btn ctrl-remove" :disabled="!canDecrement" tabindex="-1" @click="decrement" />
				</div>
			</div>
		</div>
	</div>
</template>

<style scoped>
.txtInputIconStart input{
	padding-inline-start: calc(var(--input-height) / 1.5);
}
.txtInputIconStart .iconStart {
	color: var(--input-color);
	position: absolute;
    inset-inline-start:calc(var(--input-height) / 3 - 0.25rem);
	margin-top: calc(var(--input-height) / 2 );
	line-height: 0;
}

.textInputSpinnerWrap .spinner {
	color: var(--input-color);
	position: absolute;
	inset-inline-end: 0;
	margin-top: calc(var(--input-height) / -1 + 0.15rem);
	line-height: 0;
	margin-inline-end: 0.25rem;
	display: flex;
	flex-direction: column;
	gap: 0;
}

.top-bgl-ctrl-num-btn{
	margin-top: calc(var(--input-height) / 10) !important;
}
.bgl-ctrl-num-btn{
	height: calc(var(--input-height) / 2.5) !important;
	isolation: isolate;
}

.bgl-big-ctrl-num-btn{
	width: 100% !important;
	isolation: isolate;
}
.bgl-number-input{
	padding-inline-end: 1.75rem !important;
}
</style>
