<script setup lang="ts">
import type { IconType, ValidateInputBaseT } from '@bagelink/vue'
import {
	Icon,
	useDebounceFn,
	useValidateFieldValue,
	resolveI18n,
} from '@bagelink/vue'
import { computed, onMounted, ref, watch, nextTick } from 'vue'
import type { BagelInputShellProps } from './bagelInputShell'
import { useBagelInputShell } from './bagelInputShell'

export interface TextInputProps extends ValidateInputBaseT, BagelInputShellProps {
	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?: string
	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'])

const { shellClass, shellStyle } = useBagelInputShell(props)

// 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 {
		new RegExp(pattern, 'v')
		return pattern
	} catch {
		// If 'v' flag fails, try without it (older browsers)
		try {
			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(() => String(inputVal.value ?? '').length > 0)

onMounted(async () => {
	if (props.autofocus) { setTimeout(() => input.value?.focus(), 50) }

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

<template>
	<div
		class="bagel-input text-input" :class="[
			shellClass,
			{
				dense,
				small,
				shrink,
				code,
				underlined,
				'textInputIconWrap': icon,
				'txtInputIconStart': iconStart,
				'has-error': !!error,
				'has-value': hasValue,
			},
		]" :style="shellStyle" :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="!autoheight && 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(--bgl-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(--bgl-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;
}

.textInputIconWrap .bgl_icon-font {
color: var(--bgl-input-color);
position: absolute;
inset-inline-end: calc(var(--bgl-input-height) / 3 - 0.25rem);
/* center on the input row regardless of input height (dense/small/default) */
top: calc(var(--bgl-input-height) / 2);
transform: translateY(-50%);
line-height: 0;
}

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

.txtInputIconStart .iconStart {
color: var(--bgl-input-color);
position: absolute;
inset-inline-start: calc(var(--bgl-input-height) / 3 - 0.25rem);
/* center on the input row regardless of input height (dense/small/default) */
top: calc(var(--bgl-input-height) / 2);
transform: translateY(-50%);
line-height: 0;
}

.txtInputIconStart input,
.txtInputIconStart textarea {
padding-inline-start: calc(var(--bgl-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) !important;
}

</style>
