<script setup lang="ts">
import type { IconType, ValidateInputBaseT } from '@bagelink/vue'
import {
	Icon,
	sleep,
	useDebounceFn,
	useValidateFieldValue,
	resolveI18n,
} from '@bagelink/vue'
import { until } from '@vueuse/core'

import { computed, onMounted, ref, watch, nextTick } from 'vue'

export interface TextInputProps extends ValidateInputBaseT {
	id?: string
	title?: string
	helptext?: string
	name?: string
	placeholder?: string
	modelValue?: string | number
	label?: string
	small?: boolean
	dense?: boolean
	required?: boolean
	pattern?: string
	defaultValue?: string | number
	shrink?: boolean
	underlined?: boolean
	disabled?: boolean
	type?: string
	nativeInputAttrs?: { [key: string]: any }
	icon?: IconType
	iconStart?: IconType
	multiline?: boolean
	autoheight?: boolean
	code?: boolean
	rows?: number | string
	autocomplete?: AutoFillField
	autofocus?: boolean
	error?: string
	onFocusout?: (e: FocusEvent) => void
	onFocus?: (e: FocusEvent) => void
}

const props = withDefaults(defineProps<TextInputProps>(), {
	type: 'text',
	modelValue: '',
})
const emit = defineEmits(['update:modelValue', 'debounce'])

// Normalize pattern for HTML5 validation
const normalizedPattern = computed(() => {
	if (!props.pattern) return undefined

	let { pattern } = props

	// Remove ^ and $ anchors (HTML5 patterns are implicitly anchored)
	pattern = pattern.replace(/^\^/, '').replace(/\$$/, '')

	// Validate the pattern by trying to create a RegExp
	try {
		const regexV = new RegExp(pattern, 'v')
		return pattern
	} catch {
		// If 'v' flag fails, try without it (older browsers)
		try {
			const regex = new RegExp(pattern)
			return pattern
		} catch (err) {
			console.error('Invalid pattern:', props.pattern, err)
			return undefined
		}
	}
})

const inputVal = ref<string | number>()

const input = ref<HTMLTextAreaElement | HTMLInputElement>()

useValidateFieldValue(
	() => inputVal.value,
	() => input.value,
	props.validate,
	props.getFormData
)

const inputRows = computed(() => {
	let rows = Number(props.rows) || 1
	if (props.autoheight) { rows = Math.max(rows, String(inputVal.value).split('\n').length) }
	if (props.multiline || props.code) { rows = Math.max(rows, 4) }
	return rows
})

const debouncedEmit = useDebounceFn(() => {
	emit('debounce', inputVal.value as string)
}, 700)

function autoResizeTextarea() {
	if (!props.autoheight || !input.value || input.value.tagName !== 'TEXTAREA') { return }

	const textarea = input.value as HTMLTextAreaElement
	textarea.style.height = 'auto'
	textarea.style.height = `${textarea.scrollHeight + 1}px`
}

function updateInputVal() {
	if (props.disabled) { return }
	emit('update:modelValue', inputVal.value as string)
	debouncedEmit()

	if (props.autoheight) {
		nextTick(() => { autoResizeTextarea() })
	}
}

watch(
	() => props.modelValue,
	(newVal) => {
		if (newVal !== inputVal.value) {
			inputVal.value = newVal
			if (props.autoheight) {
				nextTick(() => { autoResizeTextarea() })
			}
		}
	},
	{ immediate: true }
)

const hasFocus = () => document.activeElement === input.value
const focus = () => input.value?.focus()
defineExpose({ focus, hasFocus })

const hasValue = computed(() => {
	const val = inputVal.value
	return val !== undefined && val !== '' && String(val).length > 0
})

onMounted(async () => {
	if (props.autofocus) {
		await until(() => input.value).toBeTruthy()
		await sleep(400)
		input.value?.focus()
	}
	// Don't auto-restore defaultValue - let user control their own content

	if (props.autoheight) {
		await nextTick()
		autoResizeTextarea()
	}
})
</script>

<template>
	<div
		class="bagel-input text-input" :class="{
			dense,
			small,
			shrink,
			code,
			underlined,
			'textInputIconWrap': icon,
			'txtInputIconStart': iconStart,
			'has-error': !!error,
			'has-value': hasValue,
		}" :title="title"
	>
		<label :for="id">
			<span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>

			<input
				v-if="!multiline && !autoheight && !code && inputRows < 2" :id ref="input" v-model.trim="inputVal"
				:name :title :autocomplete :type :rows="1"
				:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))"
				:disabled :required v-bind="nativeInputAttrs" :pattern="normalizedPattern" @focusout="onFocusout"
				@focus="onFocus" @input="updateInputVal"
			>
			<textarea
				v-else :id ref="input" v-model="inputVal" :name :title :type :rows="inputRows"
				:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || resolveI18n(label))" :disabled :required
				v-bind="nativeInputAttrs" :pattern="normalizedPattern" @input="updateInputVal" @focusout="onFocusout"
			/>
			<p v-if="helptext" class="opacity-7 light">{{ helptext }}</p>
			<Icon v-if="iconStart" :class="{ ' -top-025 line-height-0 ': underlined }" class="iconStart" :icon="iconStart" />
			<Icon v-if="icon" :class="{ ' -top-025 line-height-0 ': underlined }" :icon />
		</label>
		<div v-if="error" class="error-message">
			{{ error }}
		</div>
	</div>
</template>

<style>
.bagel-input.shrink,
.bagel-input.shrink input {
	min-width: unset !important;
	/* width: auto; */
}

.bagel-input label {
	font-size: var(--label-font-size);
}

/* multiline textarea: reset gradient, use full border */
.bagel-input.underlined.text-input textarea {
	background: transparent !important;
	background-image: none !important;
	background-size: unset !important;
}
</style>

<style scoped>
.bagel-input textarea {
	min-height: unset;
	font-size: var(--input-font-size);
}

.bagel-input.text-input textarea {
	resize: none;
}

.code textarea {
	font-family: 'Inconsolata', monospace;
	background: var(--bgl-code-bg) !important;
	color: var(--bgl-light-text) !important;
}

.code textarea::placeholder {
	color: var(--bgl-light-text) !important;
	opacity: 0.3;
}

.bagel-input.small {
	margin-bottom: 0;
	height: 30px;
}

.bagel-input.dense label {
	display: flex;
	align-items: center;
	gap: 0.5rem;
}

.bagel-input input:disabled {
	background: #f5f5f5;
}

.bagel-input label {
	font-size: var(--label-font-size);
}

.textInputIconWrap .bgl_icon-font {
	color: var(--input-color);
	position: absolute;
	inset-inline-end: calc(var(--input-height) / 3 - 0.25rem);
	margin-top: calc(var(--input-height) / 2 + 0.1rem);
	line-height: 0;
}

.textInputIconWrap input,
.textInputIconWrap textarea {
	padding-inline-end: calc(var(--input-height) / 3 + 1.5rem);
}

.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;
}

.txtInputIconStart input,
.txtInputIconStart textarea {
	padding-inline-start: calc(var(--input-height) / 3 + 1.5rem);
}

.bagel-input.small textarea {
	height: 30px;
}

.bagel-input.has-error input,
.bagel-input.has-error textarea {
	border-color: var(--bgl-red, #dc3545) !important;
}
</style>
