<script setup lang="ts" generic="ContextObjType extends { [key: string]: any }">
import type { Option } from '@bagelink/vue'
import { Btn, Icon, resolveI18n, sanitizeHtml } from '@bagelink/vue'
import { computed, ref, watch } from 'vue'
import { normalizeOption } from '../../../utils/options'
import type { BagelInputShellProps } from './bagelInputShell'
import { useBagelInputShell } from './bagelInputShell'

export interface RadioOption<T = any> {
	imgAlt?: string
	imgSrc?: string
	subLabel?: string
	label?: string
	icon?: string
	id?: string
	contextObj?: T
	value: any
}

type NormalizedRadioOption<T> = Required<Pick<RadioOption<T>, 'value'>> & Omit<RadioOption<T>, 'value'>

type RadioOptionsSource<T> = (Option | RadioOption<T>)[] | (() => Promise<(Option | RadioOption<T>)[]>)

const props = withDefaults(
	defineProps<{
		id?: string
		label?: string
		groupName?: string
		options: RadioOptionsSource<ContextObjType>
		deletable?: boolean
		required?: boolean
		error?: string
		disabled?: boolean
		thin?: boolean
		flat?: boolean
		invertedActive?: boolean
		align?: 'start' | 'center' | 'end' | 'top' | 'bottom'
		hideRadio?: boolean
		bgColor?: string
		activeBgColor?: string
		activeTextColor?: string
		borderColor?: string
		textColor?: string
		textAlign?: 'left' | 'center' | 'right'
		wrapperClass?: string
	} & BagelInputShellProps>(),
	{
		align: 'center'
	}
)

const emit = defineEmits(['delete', 'focus', 'blur', 'change'])

const { shellClass, shellStyle } = useBagelInputShell(props)

const name = computed(
	() => (
		props.groupName
		|| props.id
		|| Math.random().toString(36).substring(2, 15)
	)
)
const selectedOption = defineModel('modelValue')
const loadedOptions = ref<(Option | RadioOption<ContextObjType>)[]>([])
const visibleOptions = computed<NormalizedRadioOption<ContextObjType>[]>(() => {
	const raw = Array.isArray(props.options) ? props.options : loadedOptions.value
	return raw.map(opt => normalizeOption<RadioOption<ContextObjType>>(opt as any) as unknown as NormalizedRadioOption<ContextObjType>)
})

async function loadOptionsIfNeeded() {
	if (typeof props.options === 'function') {
		try {
			loadedOptions.value = await props.options()
		} catch {
			loadedOptions.value = []
		}
	}
}

watch(() => props.options, () => {
	loadedOptions.value = []
	loadOptionsIfNeeded()
}, { immediate: true, deep: true })
const isFocused = ref(false)

const containerClasses = computed(() => ({
	'bagel-input': true,
	...shellClass.value,
	'has-error': !!props.error,
	'is-disabled': props.disabled,
	'is-focused': isFocused.value,
	'has-value': selectedOption.value != null && selectedOption.value !== '',
	'open': isFocused.value,
}))

function handleFocus() {
	isFocused.value = true
	emit('focus')
}

function handleBlur() {
	isFocused.value = false
	emit('blur')
}

function handleChange() {
	emit('change', selectedOption.value)
}
</script>

<template>
	<div :class="containerClasses" :style="shellStyle">
		<p v-if="label" class="group-label">
			{{ resolveI18n(label) }} <span v-if="required">*</span>
		</p>
<div class="radio-group-wrap" :class="wrapperClass">
	<label v-for="(opt, index) in visibleOptions" :key="opt.id || `${name}-${index}`" class="border rounded flex active-list-item hover mb-05" :for="opt.id || `${name}-${index}`"
		:class="{ 'p-05 gap-025': thin, 'py-1 gap-075': !thin, 'ps-05': !hideRadio, 'bg-gray-light': !bgColor && !flat, 'align-items-start': align === 'start' || align === 'top', 'align-items-center': align === 'center', 'align-items-end': align === 'end' || align === 'bottom', invertedActive }"
				:style="{ backgroundColor: bgColor, borderColor }">
				<input :id="opt.id || `${name}-${index}`" v-model="selectedOption" :disabled class="radio-input-list" type="radio" :name :value="opt.value" :required="required" :class="{
					'mt-025': align === 'start' || align === 'top',
					'mb-025': align === 'end' || align === 'bottom',
					'hideRadio': hideRadio,
}" @focus="handleFocus" @blur="handleBlur" @change="handleChange">
				<div class="flex w-100 gap-1 flex-wrap m_gap-05 m_gap-row-025"
					:class="{ 'txt-center justify-content-center': textAlign === 'center', 'txt-end justify-content-end': textAlign === 'right', 'txt-start justify-content-start': textAlign === 'left' }"
					:style="{ color: textColor }">
					<img v-if="opt.imgSrc" class="py-025 radius-05 m_w40 object-fit" height="40" width="40" :src="opt.imgSrc" :alt="opt.imgAlt">
					<Icon v-else-if="opt.icon" :icon="opt.icon" :size="1" />
					<div class="">
						<div v-if="opt.label" class="m-0 m_txt-14 line-height-14" v-html="sanitizeHtml(opt.label)" />
						<p v-if="opt.subLabel" class="txt-gray txt-12 m-0 pt-025">{{ opt.subLabel }}</p>
					</div>
					<slot name="radioItem" v-bind="opt" />
				</div>
				<Btn v-if="deletable" class="ms-auto" flat thin icon="delete" @click="$emit('delete', opt)" />
			</label>
</div>
	</div>
</template>


<style scoped>

.group-label {
font-size: var(--bgl-label-font-size);
margin: 0 0 0.25rem 0;
}

.hideRadio.radio-input-list {
display: none !important;
}

.radio-input-list {
width: auto;
margin-inline-end: 0.5rem;
margin-top: 0;
accent-color: var(--bgl-input-label-active-color, var(--bgl-primary));
}

.radio-input-list.hidden {
display: none;
}

.active-list-item:has(:checked) {
background: v-bind('activeBgColor || "var(--bgl-primary-light)"') !important;
border-color: var(--bgl-input-label-active-color, var(--bgl-primary)) !important;
accent-color: var(--bgl-input-label-active-color, var(--bgl-primary));
color: v-bind('activeTextColor || null') !important;
}

.invertedActive:has(:checked) {
background: var(--bgl-primary) !important;
border-color: var(--bgl-primary) !important;
color: var(--bgl-white);
accent-color: var(--bgl-white);
}

.has-error .active-list-item {
border-color: var(--bgl-red) !important;
}

.has-error :is(input[type="radio"]) {
accent-color: var(--bgl-red);
}

</style>
