<script setup lang="ts">
import type { Country } from '@bagelink/vue'
import type { CountryCode } from 'libphonenumber-js'
import { Dropdown, Flag, Icon, TextInput, allCountries, ipapi, useI18n, resolveI18n } from '@bagelink/vue'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import { computed, onMounted, ref, watch } from 'vue'

const props = defineProps<{
	id?: string
	label?: string
	modelValue?: string
	placeholder?: string
	excludeCountries?: string[]
	onlyCountries?: string[]
	required?: boolean
	disabled?: boolean
	underlined?: boolean
	error?: string
}>()

const emit = defineEmits(['update:modelValue', 'blur', 'focus', 'keydown', 'input', 'paste'])

const { $t } = useI18n()

const phoneNumber = ref(props.modelValue ?? '')
const search = ref('')
const activeCountry = ref<Country>()
const searchInput = ref<InstanceType<typeof TextInput>>()
const open = ref(false)
const inputRef = ref<HTMLInputElement | null>(null)
const isValid = ref(true)

// Watch for changes to phoneNumber and emit update events
watch(() => phoneNumber.value, (newValue) => {
	emit('update:modelValue', newValue)
})

// Watch for changes to the modelValue prop
watch(() => props.modelValue, (newValue) => {
	if (newValue !== phoneNumber.value) {
		phoneNumber.value = newValue ?? ''
	}
})

const countries = computed(() => {
	let filteredCountries = allCountries
	if (props.excludeCountries && props.excludeCountries.length) {
		const excludeCountries = props.excludeCountries.map(c => c.toLowerCase())
		filteredCountries = filteredCountries.filter(c => !excludeCountries.includes(c.iso2.toLowerCase()))
	}
	if (props.onlyCountries && props.onlyCountries.length) {
		const onlyCountries = props.onlyCountries.map(c => c.toLowerCase())
		filteredCountries = filteredCountries.filter(c => onlyCountries.includes(c.iso2.toLowerCase()))
	}
	if (search.value.length) {
		const lowerCaseSearch = search.value.toLowerCase()
		filteredCountries = filteredCountries.filter(c => c.name.toLowerCase().includes(lowerCaseSearch)
			|| c.iso2.toLowerCase().includes(lowerCaseSearch)
			|| c.dialCode.includes(search.value.replace('+', ''))
		)
	}
	return filteredCountries
})

const activeCountryCode = computed(() => activeCountry.value?.iso2)

const computedPlaceholder = computed(() => {
	if (props.placeholder) return resolveI18n(props.placeholder)
	if (activeCountry.value) {
		return `+${activeCountry.value.dialCode} ${resolveI18n(props.label) || $t('tel.phoneNumber')}`
	}
	return resolveI18n(props.label) || $t('tel.phoneNumber')
})

function selectCountry(country: Country) {
	activeCountry.value = country
	open.value = false
	search.value = ''

	if (!phoneNumber.value) {
		// Don't auto-fill for non-required empty fields
		if (props.required) {
			phoneNumber.value = `+${activeCountry.value.dialCode}`
		}
	} else if (phoneNumber.value.startsWith('+')) {
		// Replace existing country code with the new one
		const existingCountry = countries.value.find(c => phoneNumber.value.startsWith(`+${c.dialCode}`))
		if (existingCountry && existingCountry !== country) {
			// Remove the old country code and replace with new one
			const nationalPart = phoneNumber.value.substring(`+${existingCountry.dialCode}`.length).trim()
			phoneNumber.value = `+${activeCountry.value.dialCode}${nationalPart ? ` ${nationalPart}` : ''}`
		}
	} else {
		// Phone number doesn't start with +, prepend the country code
		phoneNumber.value = `+${activeCountry.value.dialCode} ${phoneNumber.value.replace(/^0+/, '')}`
	}
}

async function getIp() {
	const { country_code } = await ipapi()
	selectCountry(countries.value.find(c => c.iso2 === country_code) ?? countries.value[0])
}

// Get the country code for use with libphonenumber-js
function getCountryCode(): CountryCode | undefined {
	return activeCountry.value?.iso2 ? activeCountry.value.iso2.toUpperCase() as CountryCode : undefined
}

// Parse and format the phone number
function parseAndFormatPhoneNumber(value: string): string {
	if (!value) { return value }

	try {
		const parsedNumber = parsePhoneNumberFromString(value, getCountryCode())
		if (parsedNumber && parsedNumber.isValid()) {
			return parsedNumber.formatInternational()
		}
	} catch (error) {
		console.error('Error parsing phone number:', error)
	}
	return value
}

// Validate the phone number and set custom validity
function validatePhoneNumber() {
	if (!inputRef.value) { return }

	// If not required and empty (or just a country code), skip validation
	if (!props.required) {
		const strippedValue = phoneNumber.value.replace(/[\s+]/g, '')
		const isJustDialCode = activeCountry.value && strippedValue === activeCountry.value.dialCode
		if (!phoneNumber.value || isJustDialCode) {
			inputRef.value.setCustomValidity('')
			isValid.value = true
			return
		}
	}

	try {
		const parsedNumber = parsePhoneNumberFromString(phoneNumber.value, getCountryCode())
		if (parsedNumber && parsedNumber.isValid()) {
			inputRef.value.setCustomValidity('')
			isValid.value = true
		} else {
			inputRef.value.setCustomValidity($t('tel.invalidPhone'))
			isValid.value = false
		}
	} catch (error) {
		console.error('Error validating phone number:', error)
		inputRef.value.setCustomValidity($t('tel.invalidPhone'))
		isValid.value = false
	}
}

function detectCountryFromNumber(value: string): boolean {
	if (!value.startsWith('+')) { return false }

	const digits = value.replace(/\D/g, '')
	if (digits.length <= 1) { return false }

	for (const country of countries.value) {
		if (digits.startsWith(country.dialCode) && country !== activeCountry.value) {
			selectCountry(country)
			return true
		}
	}
	return false
}

async function initializeCountry() {
	if (phoneNumber.value) {
		detectCountryFromNumber(phoneNumber.value)
	} else {
		await getIp()
	}
	const formatted = parseAndFormatPhoneNumber(phoneNumber.value)
	if (formatted !== phoneNumber.value) {
		phoneNumber.value = formatted
		emit('input', phoneNumber.value)
		validatePhoneNumber()
	}
	validatePhoneNumber()
}

function handlePhoneInput(event: Event) {
	const input = event.target as HTMLInputElement
	const { value } = input
	detectCountryFromNumber(value)

	if (value.startsWith('+')) {
		const formatted = parseAndFormatPhoneNumber(value)
		if (formatted !== value) {
			phoneNumber.value = formatted
			emit('input', event)
			validatePhoneNumber()
			return
		}
	}

	phoneNumber.value = value
	emit('input', event)
	validatePhoneNumber()
}

function handleBlur(event: Event) {
	if (phoneNumber.value && !phoneNumber.value.startsWith('+') && activeCountry.value) {
		const nationalNumber = phoneNumber.value.replace(/^0+/, '')
		phoneNumber.value = `+${activeCountry.value.dialCode} ${nationalNumber}`
	} else if (phoneNumber.value) {
		phoneNumber.value = parseAndFormatPhoneNumber(phoneNumber.value)
	}

	validatePhoneNumber()
	emit('blur', event)
}

const disableDropdown = computed(() => countries.value.length === 1 && !search.value)
const searchable = computed(() => countries.value.length > 7 || search.value)

const hasValue = computed(() => {
	const val = phoneNumber.value
	return Boolean(val && String(val).length > 0)
})

function focusSearchInput() {
	setTimeout(() => searchInput.value?.focus(), 100)
}

onMounted(initializeCountry)
</script>

<template>
	<div class="bagel-input text-input" :class="{ 'invalid': !isValid, 'has-error': !!error, underlined, 'has-value': hasValue }">
		<label>
			<span v-if="label || (underlined && placeholder)" class="label-text">{{ resolveI18n(label) || resolveI18n(placeholder) }}<span v-if="required"> *</span></span>
			<div
				dir="ltr" class="flex gap-05 tel-input" tabindex="-1" aria-label="Country Code Selector"
				aria-haspopup="listbox" :aria-expanded="open"
			>
				<Dropdown
					v-model:shown="open" placement="bottom-start" :disabled="disableDropdown"
					@show="focusSearchInput"
				>
					<template #trigger>
						<span class="flex gap-05 country-code-display" @click="open = !open">
							<Icon v-if="!disableDropdown" :icon="open ? 'collapse_all' : 'expand_all'" />
							<Flag v-if="activeCountryCode" :country="activeCountryCode" />
						</span>
					</template>
					<div class="p-075 tel-countryp-dropdown">
						<TextInput
							v-if="searchable" ref="searchInput" v-model="search"
							aria-label="Search by country name or country code" placeholder="Search" icon="search"
						/>

						<ul class="overflow-y p-0 max-h-300px" role="listbox">
							<li
								v-for="(pb) in countries" :key="pb.iso2" role="option"
								class="flex gap-075 pointer hover" tabindex="-1"
								:aria-selected="activeCountryCode === pb.iso2" @click="selectCountry(pb)"
							>
								<Flag :country="pb.iso2" />
								<p class="tel-country">{{ pb.name }}</p>
								<span>
									+{{ pb.dialCode }}
								</span>
							</li>
						</ul>
					</div>
				</Dropdown>
				<input
					:id="id" ref="inputRef" v-model="phoneNumber" v-pattern.tel :required="required"
					:placeholder="underlined ? ' ' : computedPlaceholder" :disabled="disabled" type="tel" autocomplete="tel" :name="id"
					tabindex="0" class="national-number-input" @blur="handleBlur($event)" @focus="emit('focus', $event)"
					@keydown="emit('keydown', $event)" @input="handlePhoneInput($event)" @paste="emit('paste', $event)"
				>
			</div>
		</label>
		<div v-if="error" class="error-message">
			{{ error }}
		</div>
	</div>
</template>

<style scoped>
.tel-input {
	direction: ltr;
	text-align: left;
	background: var(--input-bg);
	border: none;
	padding-inline-start: 0.7rem;
	border-radius: var(--input-border-radius);
	color: var(--input-color);
	min-width: calc(var(--input-height) * 3);
	width: 100%;
	display: flex;
	align-items: center;
}

.tel-input:focus-within {
	outline: none;
	box-shadow: inset 0 0 10px #00000012;
}

.tel-input input {
	background: transparent;
	text-align: left;
	flex: 1;
}

.tel-input input:focus-visible {
	box-shadow: none;
}

.country-code-display {
	align-items: center;
	white-space: nowrap;
}

.dial-code {
	font-size: var(--input-font-size);
	color: var(--input-color);
	opacity: 0.6;
}

.tel-country {
	font-size: var(--input-font-size);
	max-width: 200px;
	white-space: nowrap;
	text-overflow: ellipsis;
	overflow: hidden;
	margin-top: 0;
	margin-bottom: 0;
}

.tel-countryp-dropdown {
	direction: ltr;
	text-align: left;
}

.national-number-input {
	margin-left: 4px;
}

.country-changed {
	animation: highlight-country 1.5s ease-in-out;
}

.invalid input {
	border-color: var(--error-color, red);
}

.bagel-input.has-error .tel-input {
	border: 1px solid var(--bgl-red, #dc3545) !important;
}
.bagel-input.has-error .tel-input {
	--border-color: var(--bgl-red, #dc3545) !important;
}

@keyframes highlight-country {

	0%,
	100% {
		background-color: transparent;
	}

	30% {
		background-color: var(--primary-color-light, rgba(0, 123, 255, 0.2));
	}

	70% {
		background-color: var(--primary-color-light, rgba(0, 123, 255, 0.2));
	}
}
/* Underlined mode styling */
.bagel-input.underlined {
	padding-top: 1.1rem;
}

/* In underlined mode, always show label at top */
.bagel-input.underlined .label-text {
	position: absolute;
	top: -0.65rem;
	inset-inline-start: 0.25rem;
	font-size: 0.65rem;
	color: var(--bgl-text-color, #555);
	opacity: 0.8;
	line-height: 1;
	z-index: 1;
}

/* When focused in underlined mode, make label primary color */
.bagel-input.underlined:focus-within .label-text {
	color: var(--bgl-primary);
	opacity: 1;
}

.bagel-input.underlined .tel-input {
	outline: none !important;
	box-shadow: none !important;
	background: transparent !important;
	border-radius: 0 !important;
	border: none !important;
	border-bottom: 1.5px solid var(--border-color) !important;
	padding-inline: 0.25rem !important;
}

.bagel-input.underlined .tel-input:focus-within {
	outline: none !important;
	box-shadow: none !important;
}

/* Remove border from internal input to avoid double border */
.bagel-input.underlined .tel-input input {
	border: none !important;
	border-bottom: none !important;
}
</style>
