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

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
		borderColor?: string
		textColor?: string
		textAlign?: 'left' | 'center' | 'right'
	}>(),
	{
		align: 'center'
	}
)

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

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(() => ({
	'has-error': !!props.error,
	'is-disabled': props.disabled,
	'is-focused': 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">
		<p v-if="label" class="group-label">
			{{ resolveI18n(label) }} <span v-if="required">*</span>
		</p>
		<label
			v-for="(opt, index) in visibleOptions" :key="opt.id || `${name}-${index}`"
			class="border rounded flex active-list-item hover" :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="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>
</template>

<style scoped>
.group-label {
	font-size: var(--label-font-size);
	margin: 0 0 0.25rem 0;
}

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

.radio-input-list {
	width: auto;
	transform: scale(1.2);
	margin-inline-end: 0.5rem;
	margin-top: 0;
}

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

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

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