<script setup lang="ts" generic="T extends {[key:string]:any}, P extends Path<T>">
import type { BglFormSchemaT, Path } from '@bagelink/vue'

import type { MaybeRefOrGetter } from 'vue'
import type { ComponentExposed, ComponentProps } from 'vue-component-type-helpers'
import { BagelForm, Btn, useBglSchema, sleep } from '@bagelink/vue'
import { ref, watch, computed, nextTick, toValue } from 'vue'

const props = withDefaults(
	defineProps<{
		bagelFormProps?: Omit<
			ComponentProps<typeof BagelForm<T, P>>,
			(
			'schema' | `${string}modelValue` | `ref${string}` | `onVnode${string}` | 'onSubmit'
			)
		>
		schema?: MaybeRefOrGetter<BglFormSchemaT<T>>
		showProgress?: boolean
		rtl?: boolean
		stepLabels?: string[]
		allowStepNavigation?: boolean
		validateOnSteps?: boolean
		/** Animation direction - auto detects based on step change or can be forced */
		direction?: 'auto' | 'left' | 'right'
	}>(),
	{
		bagelFormProps: () => ({}),
		showProgress: false,
		rtl: false,
		allowStepNavigation: false,
		validateOnSteps: true,
		direction: 'auto'
	}
)

const emits = defineEmits(['submit', 'stepChange'])

type BagelFormT = ComponentExposed<typeof BagelForm<T, P>>

const formRef = ref<BagelFormT>()

const formData = defineModel<T>('modelValue', {
	default: () => ({}),
	required: true
})

const currentStep = ref(0)
const previousStep = ref(0)
const validatedSteps = ref<number[]>([])

function reportValidity() {
	if (!formRef.value) return false
	const isValid = formRef.value.validateForm()

	if (isValid && !validatedSteps.value.includes(currentStep.value)) {
		validatedSteps.value.push(currentStep.value)
	}

	return isValid
}

const computedSchema = computed(
	() => useBglSchema({ schema: toValue(props.schema) })
)

// Filter out steps with vIf conditions that evaluate to false
const filteredSchema = computed(() => {
	const schema = computedSchema.value
	return schema.filter((step, _index) => {
		// Skip steps that have vIf or v-if returning false
		if (step.vIf !== undefined || step['v-if'] !== undefined) {
			const condition = step.vIf ?? step['v-if']
			// If condition is a function, evaluate it with current form data
			if (typeof condition === 'function') {
				return condition(undefined, formData.value)
			}
			// If condition is a boolean, use it directly
			if (typeof condition === 'boolean') {
				return condition
			}
			// If condition is a string, evaluate it (treat non-empty as true)
			if (typeof condition === 'string') {
				return !!condition
			}
		}
		// Include steps with no vIf condition or ones that evaluate to true
		return true
	})
})

const numberOfSteps = computed(() => filteredSchema.value.length)

// Map the filtered step index to the actual schema index
const schemaIndexMap = computed(() => {
	const map: number[] = []
	computedSchema.value.forEach((step, index) => {
		const vIfCondition = step.vIf ?? step['v-if']
		let shouldInclude = true

		if (vIfCondition !== undefined) {
			if (typeof vIfCondition === 'function') {
				shouldInclude = vIfCondition(undefined, formData.value)
			} else if (typeof vIfCondition === 'boolean') {
				shouldInclude = vIfCondition
			} else if (typeof vIfCondition === 'string') {
				shouldInclude = !!vIfCondition
			}
		}

		if (shouldInclude) {
			map.push(index)
		}
	})
	return map
})

// Get current step schema using the mapping
const currentStepSchema = computed(() => {
	const actualIndex = schemaIndexMap.value[currentStep.value]
	return [computedSchema.value[actualIndex]]
})

// Tracks which way we're sliding (left or right)
const slideDirection = ref(props.rtl ? 'right' : 'left')

watch(
	() => currentStep.value,
	(newStep, oldStep) => {
		// Set direction based on step change
		if (props.direction === 'auto') {
			slideDirection.value = props.rtl
				? (newStep > oldStep ? 'right' : 'left')
				: (newStep > oldStep ? 'left' : 'right')
		} else {
			slideDirection.value = props.direction
		}

		previousStep.value = oldStep
		// We don't need isStepping flag anymore
		emits('stepChange', {
			newStep,
			oldStep,
			totalSteps: numberOfSteps.value,
			direction: slideDirection.value
		})
	}
)

const isLastStep = computed(() => currentStep.value === numberOfSteps.value - 1)

const isStepValidated = computed(() => (stepIndex: number) => validatedSteps.value.includes(stepIndex))

// Add a computed property to check if current step is valid
const isStepValid = ref(false)

async function checkCurrentStepValidity() {
	await nextTick()
	if (!props.validateOnSteps) isStepValid.value = true
	else isStepValid.value = formRef.value?.checkValidity() ?? false
}

function prevStep() {
	if (currentStep.value > 0) currentStep.value--
}

const formContainer = ref<HTMLElement>()

async function nextStep() {
	// Always use reportValidity when attempting to move to next step
	// This will show validation errors to the user
	if (props.validateOnSteps && reportValidity() === false) return
	if (!isLastStep.value) currentStep.value++
	await sleep(400)
	checkCurrentStepValidity()
}

function goToStep(stepIndex: number) {
	// Can always go back, or to already validated steps
	if (stepIndex < currentStep.value || validatedSteps.value.includes(stepIndex)) {
		currentStep.value = stepIndex
		return true
	}

	// For forward navigation to non-validated steps, validate current step first if needed
	// This will show validation errors to the user
	if (props.validateOnSteps && reportValidity() === false) {
		return false
	}

	// Can only move one step forward at a time (prevent skipping)
	if (stepIndex === currentStep.value + 1) {
		currentStep.value = stepIndex
		return true
	}

	return false
}

function handleSubmit() {
	// Show validation errors to the user when submitting
	if (reportValidity() === false) return
	emits('submit', formData.value)
}

function reset() {
	validatedSteps.value = []
	currentStep.value = 0
}

// Re-evaluate filtered steps when formData changes
watch(() => formData.value, () => {
	checkCurrentStepValidity()
	if (currentStep.value >= numberOfSteps.value && numberOfSteps.value > 0) {
		currentStep.value = 0
	}
}, { deep: true })

defineExpose({
	submit: handleSubmit,
	validateForm: reportValidity,
	checkValidity: formRef.value?.checkValidity,
	isDirty: computed(() => formRef.value?.isDirty),
	reset,
	isLastStep,
	goToStep,
	currentStep,
	totalSteps: computed(() => numberOfSteps.value),
	isStepValid,
	nextStep,
	prevStep,
})
</script>

<template>
	<div class="bgl-multi-step-form">
		<!-- Progress indicator -->
		<div v-if="showProgress" class="bgl-step-progress">
			<slot
				name="progress" v-bind="{
					currentStep,
					totalSteps: numberOfSteps,
					goToStep,
					stepLabels: props.stepLabels,
					allowStepNavigation: props.allowStepNavigation,
					isStepValidated,
				}"
			>
				<div class="bgl-steps-indicator">
					<div
						v-for="(_, index) in numberOfSteps"
						:key="index"
						class="bgl-step-indicator" :class="[
							{ active: index === currentStep },
							{ completed: index < currentStep || isStepValidated(index) },
							{ clickable: props.allowStepNavigation },
						]"
						@click="props.allowStepNavigation && goToStep(index)"
					>
						<span>{{ index + 1 }}</span>
						<span v-if="props.stepLabels && props.stepLabels[index]" class="bgl-step-label">
							{{ props.stepLabels[index] }}
						</span>
					</div>
				</div>
			</slot>
		</div>

		<div class="bgl-form-wrapper">
			<transition
				:name="slideDirection === 'right' ? 'slide-right' : 'slide-left'"
				mode="out-in"
			>
				<div :key="currentStep" ref="formContainer" class="bgl-form-container">
					<BagelForm
						ref="formRef"
						v-model="formData"
						:schema="currentStepSchema"
						v-bind="bagelFormProps"
					>
						<template v-if="$slots.success" #success>
							<slot name="success" />
						</template>
						<template v-if="$slots.error" #error>
							<slot name="error" />
						</template>
					</BagelForm>

					<!-- Navigation buttons inside the form container to animate together -->
				</div>
			</transition>
			<div class="bgl-step-controls">
				<slot
					name="steppers" v-bind="{
						prevStep,
						nextStep,
						submit: handleSubmit,
						currentStep,
						totalSteps: numberOfSteps,
						isLastStep,
						isStepValid,
					}"
				>
					<Btn v-if="currentStep !== 0" color="gray" icon="arrow_back" @click="prevStep" />
					<Btn
						v-if="!isLastStep"
						icon="arrow_forward"
						:disabled="props.validateOnSteps && !isStepValid"
						@click="nextStep"
					/>
					<Btn v-else value="Submit" @click="handleSubmit" />
				</slot>
			</div>
		</div>
	</div>
</template>

<style scoped>
.bgl-multi-step-form {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	width: 100%;
	/* Default transition duration */
	--transition-duration: 300ms;
	--move-distance: 35%;
	--ease-in: cubic-bezier(0.42, 0, 0.58, 1);
	--ease-out: cubic-bezier(0.42, 0, 0.58, 1);
}

.bgl-steps-indicator {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 1rem;
}

.bgl-step-indicator {
	display: flex;
	flex-direction: column;
	align-items: center;
	position: relative;
}

.bgl-step-indicator::before {
	content: '';
	position: absolute;
	top: 50%;
	right: 50%;
	height: 2px;
	width: 100%;
	background: #e0e0e0;
	z-index: -1;
}

.bgl-step-indicator:first-child::before {
	display: none;
}

.bgl-step-indicator > span:first-child {
	width: 30px;
	height: 30px;
	border-radius: 50%;
	background: #e0e0e0;
	color: #555;
	display: flex;
	align-items: center;
	justify-content: center;
	margin-bottom: 0.5rem;
}

.bgl-step-indicator.active > span:first-child {
	background: var(--primary-color, #4CAF50);
	color: white;
}

.bgl-step-indicator.completed > span:first-child {
	background: var(--success-color, #8BC34A);
	color: white;
}

.bgl-step-indicator.clickable {
	cursor: pointer;
}

.bgl-step-label {
	font-size: 0.8rem;
	max-width: 100px;
	text-align: center;
}

.bgl-form-wrapper {
	display: grid;
	overflow: clip;
	height: auto;
	transition: height var(--transition-duration) ease;
	interpolate-size: allow-keywords;
}

.bgl-form-wrapper > div {
	grid-area: 1 / 1;

}

.bgl-form-container {
	width: 100%;
	display: flex;
	flex-direction: column;
}

.bgl-step-controls {
	display: flex;
	justify-content: center;
	gap: 1rem;
	grid-area: unset !important;
	margin-top: 2rem;
}

/* Slide Left Animation (going forward) */
.slide-left-enter-active {
	transition: opacity var(--transition-duration) var(--ease-in),
	            transform var(--transition-duration) var(--ease-in);
}

.slide-left-leave-active {
	transition: opacity var(--transition-duration) var(--ease-out),
	            transform var(--transition-duration) var(--ease-out);
}

.slide-left-enter-from {
	opacity: 0;
	transform: translateX(var(--move-distance));
}

.slide-left-leave-to {
	opacity: 0;
	transform: translateX(-30%);
}

/* Slide Right Animation (going back) */
.slide-right-enter-active {
	transition: opacity var(--transition-duration) var(--ease-in),
	            transform var(--transition-duration) var(--ease-in);
}

.slide-right-leave-active {
	transition: opacity var(--transition-duration) var(--ease-out),
	            transform var(--transition-duration) var(--ease-out);
}

.slide-right-enter-from {
	opacity: 0;
	transform: translateX(-30%);
}

.slide-right-leave-to {
	opacity: 0;
	transform: translateX(var(--move-distance));
}
</style>
