<script lang="ts">
	import { Button } from '$lib/components/ui/button/index.js';
	import * as Card from '$lib/components/ui/card/index.js';
	import { Input } from '$lib/components/ui/input/index.js';
	import { Label } from '$lib/components/ui/label/index.js';
	import { Textarea } from '$lib/components/ui/textarea/index.js';
	import { Separator } from '$lib/components/ui/separator/index.js';
	// INTENTIONAL: We use the browser's local timezone (getLocalTimeZone) because Crussell is a UK-only
	// salon app. All customers are physically in the UK and book UK appointment slots. We do NOT
	// auto-adjust for international timezones — the slot time shown is the actual UK salon time.
	// Cloudflare geo-blocking prevents non-UK access. BST/GMT transitions are handled manually by
	// staff adjusting working hours; the app does not need timezone-aware scheduling logic.
	import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
	import { authStore } from '$lib/stores/auth.svelte';
	import { toast } from 'svelte-sonner';
	import { SvelteMap, SvelteDate } from 'svelte/reactivity';

	// Components
	import BookingActions from '$lib/components/booking/BookingActions.svelte';
	import BookingSummary from '$lib/components/booking/BookingSummary.svelte';
	import StepIndicator from '$lib/components/booking/StepIndicator.svelte';
	import DatePicker from '$lib/components/booking/DatePicker.svelte';
	import TimeSlotPicker from '$lib/components/booking/TimeSlotPicker.svelte';
	import ServiceSelector from '$lib/components/booking/ServiceSelector.svelte';
	import {
		extractBookedSlots,
		getLunchProtectionForSlots,
		type TimeSlot
	} from '$lib/lunchProtection';

	import type {
		Service,
		CustomerInfo,
		WorkingHoursDay,
		AvailableHoursDay
	} from '$lib/types/booking';

	// =============== State Management ===============
	let currentStep = $state<number>(1);
	let selectedServices = $state<Service[]>([]);
	let selectedDate = $state<CalendarDate | undefined>(undefined);
	let selectedTime = $state<string | null>(null);
	let customerInfo = $state<CustomerInfo>({
		firstName: '',
		lastName: '',
		email: '',
		phone: '',
		specialRequests: ''
	});
	let isSubmitting = $state(false);
	let idempotencyKey = $state<string>('');

	// =============== Slot Reservation System ===============
	let reservationId = $state<string | null>(null);
	let reservationExpiresAt = $state<Date | null>(null);
	let reservationCountdown = $state<string>('');
	let reservationExpired = $state(false);
	let isReserving = $state(false);

	// =============== Slot Reservation Functions ===============
	async function reserveSlot() {
		isReserving = true;
		try {
			if (!selectedDate || !selectedTime) {
				toast.error('Please select a date and time');
				return false;
			}

			const [hours, minutes] = selectedTime.split(':').map(Number);
			const bookingDate = selectedDate.toDate(getLocalTimeZone());
			bookingDate.setHours(hours, minutes, 0, 0);
			const startTimeISO = bookingDate.toISOString();
			const serviceIds = selectedServices.map((s) => s.id);

			const response = await fetch('/api/bookings/reserve', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ start_time: startTimeISO, service_ids: serviceIds })
			});

			if (!response.ok) {
				const errorText = await response.text();
				if (response.status === 429) {
					toast.error('Too many active reservations. Please wait or log in.');
				} else if (response.status === 409) {
					toast.error('This time slot is no longer available. Please choose a different time.');
					await refreshAvailableHours();
				} else {
					toast.error('Failed to reserve slot. Please try again.');
				}
				return false;
			}

			const data = await response.json();
			reservationId = data.id;
			reservationExpiresAt = new Date(data.expires_at);
			reservationExpired = false;
			startCountdown();
			return true;
		} catch (error) {
			toast.error('Network error while reserving slot.');
			return false;
		} finally {
			isReserving = false;
		}
	}

	function startCountdown() {
		if (!reservationExpiresAt) return;

		const updateCountdown = () => {
			if (!reservationExpiresAt) {
				reservationCountdown = '';
				return;
			}

			const now = new Date();
			const diff = reservationExpiresAt.getTime() - now.getTime();

			if (diff <= 0) {
				reservationCountdown = '00:00';
				reservationExpired = true;
				reservationId = null;
				reservationExpiresAt = null;
				toast.error('Your reservation has expired. Please select a new time slot.');
				return;
			}

			const minutes = Math.floor(diff / 60000);
			const seconds = Math.floor((diff % 60000) / 1000);
			reservationCountdown = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
		};

		updateCountdown();
		const interval = setInterval(() => {
			if (reservationExpired) {
				clearInterval(interval);
				return;
			}
			updateCountdown();
		}, 1000);
	}

	async function refreshAvailableHours() {
		if (!selectedDate) return;

		const monthKey = `${selectedDate.year}-${String(selectedDate.month).padStart(2, '0')}`;
		availableHoursCache.delete(monthKey);
		await fetchHoursForMonth(selectedDate);
	}

	// =============== Services Management ===============
	let services = $state<Service[]>([]);
	let servicesLoading = $state(true);

	async function fetchServices() {
		servicesLoading = true;
		try {
			const response = await fetch('/api/services', {
				method: 'GET',
				headers: {
					'Content-Type': 'application/json',
					Authorization: `Bearer ${authStore.currentToken}`
				}
			});

			if (response.ok) {
				const data: Service[] = await response.json();
				// Sort: valid patch tests first (by name), then grayed out (by name)
				const valid: Service[] = [];
				const grayedOut: Service[] = [];
				
				for (const service of data) {
					if (service.patch_test_status === 'required' || service.patch_test_status === 'expired') {
						grayedOut.push(service);
					} else {
						valid.push(service);
					}
				}
				
				// Sort each group alphabetically
				valid.sort((a, b) => a.name.localeCompare(b.name));
				grayedOut.sort((a, b) => a.name.localeCompare(b.name));
				
				// Combine: valid first, then grayed out
				services = [...valid, ...grayedOut];
			} else {
				console.error('Failed to fetch services:', response.status);
				toast.error('Failed to load services');
			}
		} catch (err) {
			console.error('Error fetching services:', err);
			toast.error('Network error loading services');
		} finally {
			servicesLoading = false;
		}
	}

	// =============== ADD: Lunch Protection ===============
	const lunchProtectionStatus = $derived(() => {
		if (!selectedDate || !workingHours || !availableHours || selectedServices.length === 0) {
			return new Map();
		}

		const dateStr = selectedDate.toString();
		const dayWorkingHours = workingHours[dateStr];
		const dayAvailableHours = availableHours[dateStr];

		if (!dayWorkingHours?.isOpen || !dayAvailableHours?.slots) {
			return new Map();
		}

		// Extract existing bookings from the gap between working hours and available hours
		const existingBookings = extractBookedSlots(
			dayWorkingHours.startTime,
			dayWorkingHours.endTime,
			dayAvailableHours.slots
		);

		// Get lunch protection status for all slots
		return getLunchProtectionForSlots(
			dayWorkingHours.startTime,
			dayWorkingHours.endTime,
			existingBookings,
			getTotalDuration(),
			15, // 15 minute slot intervals
			false // User journey - requires 1h minimum
		);
	});

	// =============== Working Hours & Available Hours ===============
	let workingHours = $state<Record<
		string,
		{ isOpen: boolean; startTime: string; endTime: string }
	> | null>(null);

	let availableHours = $state<Record<
		string,
		{ isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }
	> | null>(null);

	let loadingWorkingHours = $state<boolean>(false);
	let loadingAvailableHours = $state<boolean>(false);

	const workingHoursCache = new SvelteMap<
		string,
		Record<string, { isOpen: boolean; startTime: string; endTime: string }>
	>();

	const availableHoursCache = new SvelteMap<
		string,
		Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }>
	>();

	$effect(() => {
		return () => {
			workingHoursCache.clear();
			availableHoursCache.clear();
		};
	});

	// Initialize date boundaries
	const today = new SvelteDate();
	const tomorrow = new SvelteDate(today);
	tomorrow.setDate(today.getDate() + 1);
	const maxDate = new SvelteDate();
	maxDate.setMonth(today.getMonth() + 6);

	// Create CalendarDate objects
	const minDate = new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate());
	const maxCalendarDate = new CalendarDate(
		maxDate.getFullYear(),
		maxDate.getMonth() + 1,
		maxDate.getDate()
	);

	let placeholder = $state<CalendarDate>(minDate);

	$effect(() => {
		fetchServices();
	});
	$effect(() => {
		const monthKey = `${placeholder.year}-${String(placeholder.month).padStart(2, '0')}`;
		if (!workingHoursCache.has(monthKey) || !availableHoursCache.has(monthKey)) {
			fetchHoursForMonth(placeholder);
		}
	});

	async function fetchHoursForMonth(date: CalendarDate) {
		const monthKey = `${date.year}-${String(date.month).padStart(2, '0')}`;

		if (workingHoursCache.has(monthKey) && availableHoursCache.has(monthKey)) {
			workingHours = workingHoursCache.get(monthKey)!;
			availableHours = availableHoursCache.get(monthKey)!;
			return;
		}

		loadingWorkingHours = true;
		loadingAvailableHours = true;

		try {
			const startOfMonth = new CalendarDate(date.year, date.month, 1);
			const endOfMonth = new CalendarDate(
				date.year,
				date.month,
				date.calendar.getDaysInMonth(date)
			);

			const startStr = startOfMonth.toString();
			const endStr = endOfMonth.toString();

			// Fetch working hours
			const workingHoursResponse = await fetch(
				`/api/scheduling/working-hours?start=${startStr}&end=${endStr}`
			);
			if (!workingHoursResponse.ok) {
				throw new Error(`HTTP error! status: ${workingHoursResponse.status}`);
			}

			const workingHoursData: Array<WorkingHoursDay> = await workingHoursResponse.json();
			const workingHoursMap: Record<
				string,
				{ isOpen: boolean; startTime: string; endTime: string }
			> = {};

			workingHoursData.forEach((day) => {
				workingHoursMap[day.date] = {
					isOpen: day.isOpen,
					startTime: day.startTime,
					endTime: day.endTime
				};
			});

			workingHoursCache.set(monthKey, workingHoursMap);
			workingHours = workingHoursMap;

			// Fetch available hours
			const availableHoursResponse = await fetch(
				`/api/scheduling/available-hours?start=${startStr}&end=${endStr}`
			);
			if (!availableHoursResponse.ok) {
				throw new Error(`HTTP error! status: ${availableHoursResponse.status}`);
			}

			const availableHoursData: Array<AvailableHoursDay> = await availableHoursResponse.json();
			const availableHoursMap: Record<
				string,
				{ isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }
			> = {};

			availableHoursData.forEach((day) => {
				availableHoursMap[day.date] = {
					isOpen: day.isOpen,
					slots: day.slots
				};
			});

			availableHoursCache.set(monthKey, availableHoursMap);
			availableHours = availableHoursMap;

			if (!selectedDate) {
				setDefaultSelectedDate(workingHoursMap);
			}
		} catch (error) {
			console.error('Failed to fetch hours:', error);
			if (!selectedDate) {
				selectedDate = minDate;
			}
		} finally {
			loadingWorkingHours = false;
			loadingAvailableHours = false;
		}
	}

	function setDefaultSelectedDate(
		hoursMap: Record<string, { isOpen: boolean; startTime: string; endTime: string }>
	) {
		const currentDate = new SvelteDate();
		const maxDateJs = new SvelteDate(
			maxCalendarDate.year,
			maxCalendarDate.month - 1,
			maxCalendarDate.day
		);

		const daysDifference = Math.floor(
			(maxDateJs.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24)
		);
		const daysToCheck = Math.min(daysDifference, 180);

		for (let i = 1; i <= daysToCheck; i++) {
			const nextDate = new SvelteDate(currentDate);
			nextDate.setDate(currentDate.getDate() + i);
			const dateStr = nextDate.toISOString().split('T')[0];

			if (hoursMap[dateStr]?.isOpen) {
				selectedDate = new CalendarDate(
					nextDate.getFullYear(),
					nextDate.getMonth() + 1,
					nextDate.getDate()
				);
				// Also update placeholder to show the month with first available date
				placeholder = new CalendarDate(
					nextDate.getFullYear(),
					nextDate.getMonth() + 1,
					1 // First day of the month
				);
				break;
			}
		}

		if (!selectedDate) {
			const tomorrow = new SvelteDate();
			tomorrow.setDate(tomorrow.getDate() + 1);
			selectedDate = new CalendarDate(
				tomorrow.getFullYear(),
				tomorrow.getMonth() + 1,
				tomorrow.getDate()
			);
			placeholder = new CalendarDate(tomorrow.getFullYear(), tomorrow.getMonth() + 1, 1);
		}
	}

	// =============== Time Slot Generation ===============
	function calculateEndTime(startTime: string, durationMinutes: number): string {
		const [hours, minutes] = startTime.split(':').map(Number);
		const date = new SvelteDate();
		date.setHours(hours, minutes, 0, 0);
		date.setMinutes(date.getMinutes() + durationMinutes);
		const endHours = date.getHours().toString().padStart(2, '0');
		const endMinutes = date.getMinutes().toString().padStart(2, '0');
		return `${endHours}:${endMinutes}`;
	}

	function timeToMinutes(time: string): number {
		const [hours, minutes] = time.split(':').map(Number);
		return hours * 60 + minutes;
	}

	function calculatePreviousTime(time: string): string {
		const [hours, minutes] = time.split(':').map(Number);
		let totalMinutes = hours * 60 + minutes;
		totalMinutes -= 15;

		const prevHours = Math.floor(totalMinutes / 60);
		const prevMinutes = totalMinutes % 60;
		return `${String(prevHours).padStart(2, '0')}:${String(prevMinutes).padStart(2, '0')}`;
	}

	function generateAvailableTimeSlots(duration: number, date: CalendarDate | undefined): string[] {
		if (!date || !workingHours || !availableHours) {
			return [];
		}

		const dateStr = date.toString();
		const dayWorkingHours = workingHours[dateStr];
		const dayAvailableHours = availableHours[dateStr];

		if (
			!dayWorkingHours ||
			!dayWorkingHours.isOpen ||
			!dayAvailableHours ||
			!dayAvailableHours.slots
		) {
			return [];
		}

		const slots: string[] = [];
		const now = new SvelteDate();
		const today = new CalendarDate(now.getFullYear(), now.getMonth() + 1, now.getDate());
		const isToday = date.compare(today) === 0;

		for (const slot of dayAvailableHours.slots) {
			const [startHour, startMinute] = slot.startTime.split(':').map(Number);
			const [endHour, endMinute] = slot.endTime.split(':').map(Number);

			let startTotalMinutes = startHour * 60 + startMinute;
			const endTotalMinutes = endHour * 60 + endMinute;

			if (isToday) {
				const currentMinutes = now.getHours() * 60 + now.getMinutes();
				const minimumStartMinutes = currentMinutes + 120;
				startTotalMinutes = Math.max(startTotalMinutes, minimumStartMinutes);
			}

			for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 15) {
				const slotEndMinutes = minutes + duration;

				if (slotEndMinutes <= endTotalMinutes) {
					const hour = Math.floor(minutes / 60);
					const minute = minutes % 60;
					const timeStr = `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
					slots.push(timeStr);
				}
			}
		}

		return slots;
	}

	function generateGroupedTimeSlots(
		duration: number,
		date: CalendarDate | undefined,
		lunchProtection: Map<string, { isBlocked: boolean; showWarning: boolean; warningMessage?: string }> = new Map()
	): Array<{
		type: 'available' | 'unavailable';
		startTime: string;
		endTime: string;
		isGrouped?: boolean;
	}> {
		if (!date || !workingHours) {
			return [];
		}

		const dateStr = date.toString();
		const dayWorkingHours = workingHours[dateStr];

		if (!dayWorkingHours || !dayWorkingHours.isOpen) {
			return [];
		}

		const groupedSlots: Array<{
			type: 'available' | 'unavailable';
			startTime: string;
			endTime: string;
			isGrouped?: boolean;
		}> = [];
		const [startHour, startMinute] = dayWorkingHours.startTime.split(':').map(Number);
		const [endHour, endMinute] = dayWorkingHours.endTime.split(':').map(Number);

		let startTotalMinutes = startHour * 60 + startMinute;
		const endTotalMinutes = endHour * 60 + endMinute;

		const now = new SvelteDate();
		const today = new CalendarDate(now.getFullYear(), now.getMonth() + 1, now.getDate());
		const isToday = date.compare(today) === 0;

		if (isToday) {
			const currentMinutes = now.getHours() * 60 + now.getMinutes();
			const minimumStartMinutes = currentMinutes + 120;
			startTotalMinutes = Math.max(startTotalMinutes, minimumStartMinutes);
		}

		const availableSlots = generateAvailableTimeSlots(duration, date);

		let currentUnavailableStart: string | null = null;
		let lastAvailableEndTime: string | null = null;

		for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 15) {
			const hour = Math.floor(minutes / 60);
			const minute = minutes % 60;
			const timeStr = `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;

			const isAvailable = availableSlots.includes(timeStr) && !lunchProtection.get(timeStr)?.isBlocked;

			if (isAvailable) {
				if (currentUnavailableStart !== null) {
					const groupEndTime = calculatePreviousTime(timeStr);
					const unavailableStartTime = currentUnavailableStart || lastAvailableEndTime;
					if (unavailableStartTime && timeToMinutes(unavailableStartTime) < timeToMinutes(groupEndTime)) {
						groupedSlots.push({
							type: 'unavailable',
							startTime: unavailableStartTime,
							endTime: groupEndTime,
							isGrouped: true
						});
					}
					currentUnavailableStart = null;
				}

				const slotEndTime = calculateEndTime(timeStr, duration);
				lastAvailableEndTime = slotEndTime;
				groupedSlots.push({
					type: 'available',
					startTime: timeStr,
					endTime: slotEndTime
				});

				if (timeToMinutes(slotEndTime) >= endTotalMinutes) {
					break;
				}
			} else {
				if (currentUnavailableStart === null) {
					currentUnavailableStart = timeStr;
				}
			}
		}

		if (currentUnavailableStart !== null) {
			const lastAvailableSlot = groupedSlots.filter((s) => s.type === 'available').pop();
			const lastAvailableEnd = lastAvailableSlot ? timeToMinutes(lastAvailableSlot.endTime) : 0;

			const unavailableStartMinutes = timeToMinutes(currentUnavailableStart);

			if (unavailableStartMinutes < endTotalMinutes && lastAvailableEnd < endTotalMinutes) {
				// Use the end time of the last available slot for the final unavailable period
				const unavailableStartTime = lastAvailableSlot
					? lastAvailableSlot.endTime
					: currentUnavailableStart;
				groupedSlots.push({
					type: 'unavailable',
					startTime: unavailableStartTime,
					endTime: dayWorkingHours.endTime,
					isGrouped: true
				});
			}
		}

		return groupedSlots;
	}

	// =============== Date Availability Check ===============
	function isDateUnavailable(date: DateValue): boolean {
		if (!(date instanceof CalendarDate)) {
			return true;
		}

		if (date.compare(minDate) < 0 || date.compare(maxCalendarDate) > 0) {
			return true;
		}

		if (!workingHours) return true;

		const dateStr = date.toString();
		const dayHours = workingHours[dateStr];

		if (!dayHours) return true;
		if (!dayHours.isOpen) return true;

		// If no services selected, don't check availability slots
		// This allows calendar to show open/closed days
		if (selectedServices.length === 0) {
			return false; // Show all working days as available
		}

		const duration = getTotalDuration();
		const availableSlots = generateAvailableTimeSlots(duration, date);
		if (availableSlots.length === 0) return true;

		return false;
	}

	// =============== Helper Functions ===============
	function getTotalDuration() {
		return selectedServices.reduce(
			(total, service: Service) => total + service.duration_minutes,
			0
		);
	}

	function getTotalPrice() {
		return selectedServices.reduce((total, service: Service) => total + service.price, 0);
	}

	function toggleService(service: Service) {
		const index = selectedServices.findIndex((s) => s.id === service.id);
		const wasSelected = index >= 0;

		if (wasSelected) {
			selectedServices = selectedServices.filter((s) => s.id !== service.id);
		} else {
			selectedServices = [...selectedServices, service];
		}

		// Only clear if we're on the date/time selection step
		if (currentStep === 2 && selectedDate) {
			const monthKey = `${selectedDate.year}-${String(selectedDate.month).padStart(2, '0')}`;
			availableHoursCache.delete(monthKey);
			fetchHoursForMonth(selectedDate);
		}

		selectedTime = null;
	}

	// Select a time slot with server-side re-validation
	async function selectTimeWithValidation(time: string) {
		selectedTime = time;
		await refreshAndValidateSlot();
	}

	// Re-fetch available hours and check if selectedTime is still available
	async function refreshAndValidateSlot() {
		if (!selectedDate || !selectedTime) return;

		const monthKey = `${selectedDate.year}-${String(selectedDate.month).padStart(2, '0')}`;
		availableHoursCache.delete(monthKey);
		await fetchHoursForMonth(selectedDate);

		const dateStr = selectedDate.toString();
		const dayAvailable = availableHours?.[dateStr]?.slots;
		if (!dayAvailable || dayAvailable.length === 0) {
			toast.error('Sorry, this slot is no longer available. Please choose a different time.');
			selectedTime = null;
			return false;
		}

		const duration = getTotalDuration();
		const [selHour, selMinute] = selectedTime.split(':').map(Number);
		const selStart = selHour * 60 + selMinute;
		const selEnd = selStart + duration;

		const stillAvailable = dayAvailable.some(slot => {
			const [sH, sM] = slot.startTime.split(':').map(Number);
			const [eH, eM] = slot.endTime.split(':').map(Number);
			return selStart >= (sH * 60 + sM) && selEnd <= (eH * 60 + eM);
		});

		if (!stillAvailable) {
			toast.error('Sorry, this slot was just taken. Please choose a different time.');
			selectedTime = null;
			return false;
		}

		return true;
	}

	function formatDuration(minutes: number): string {
		const hours = Math.floor(minutes / 60);
		const remainingMinutes = minutes % 60;

		if (hours === 0) {
			return `${remainingMinutes} minutes`;
		} else if (remainingMinutes === 0) {
			return `${hours} ${hours === 1 ? 'hour' : 'hours'}`;
		} else {
			return `${hours} ${hours === 1 ? 'hour' : 'hours'} ${remainingMinutes} minutes`;
		}
	}

	function getDayWithOrdinal(date: CalendarDate): string {
		const monthName = new SvelteDate(date.year, date.month - 1, date.day).toLocaleDateString(
			'en-GB',
			{
				month: 'long'
			}
		);
		const day = date.day;
		if (day > 3 && day < 21) return monthName + ' ' + day + 'th';
		switch (day % 10) {
			case 1:
				return monthName + ' ' + day + 'st';
			case 2:
				return monthName + ' ' + day + 'nd';
			case 3:
				return monthName + ' ' + day + 'rd';
			default:
				return monthName + ' ' + day + 'th';
		}
	}

	// =============== Derived Values ===============
	const formattedTotalDuration = $derived(formatDuration(getTotalDuration()));
	const groupedTimeSlots = $derived(
		currentStep === 2 && selectedServices.length > 0 && selectedDate
			? generateGroupedTimeSlots(getTotalDuration(), selectedDate, lunchProtectionStatus())
			: []
	);
	const formattedSelectedDate = $derived(
		selectedDate ? getDayWithOrdinal(selectedDate) : undefined
	);

	// =============== Navigation ===============
	async function nextStep() {
		// Step 2 -> Step 3: Re-validate slot, then reserve
		if (currentStep === 2) {
			const slotStillFree = await refreshAndValidateSlot();
			if (!slotStillFree) return;
			const reserved = await reserveSlot();
			if (!reserved) return;
		}

		if (currentStep < 5) {
			currentStep++;
			setTimeout(() => {
				window.scrollTo({ top: 0, behavior: 'smooth' });
			}, 50);
		}
	}

	function prevStep() {
		if (currentStep > 1) {
			currentStep--;
			setTimeout(() => {
				window.scrollTo({ top: 0, behavior: 'smooth' });
			}, 50);
		}
	}

	// =============== Validation ===============
	const canProceedStep1 = $derived(selectedServices.length > 0);
	const canProceedStep2 = $derived(!!(selectedDate && selectedTime));
	const canProceedStep3 = $derived(
		(authStore.isAuthenticated
			? !!(
					authStore.currentUser?.firstName &&
					authStore.currentUser?.lastName &&
					authStore.currentUser?.email &&
					authStore.currentUser?.phone
				)
			: !!(
					customerInfo.firstName &&
					customerInfo.lastName &&
					customerInfo.email &&
					customerInfo.phone
				)) && !reservationExpired
	);
	const canProceedStep4 = $derived(true);

	// =============== Submission ===============
	async function submitBooking() {
		isSubmitting = true;
		try {
			// Generate idempotency key if not already set (reused on retry)
			if (!idempotencyKey) {
				idempotencyKey = crypto.randomUUID();
			}

			// Build the start_time in ISO format
			if (!selectedDate || !selectedTime) {
				toast.error('Please select a date and time');
				return;
			}

			// Convert CalendarDate to JavaScript Date, then to ISO string
			const [hours, minutes] = selectedTime.split(':').map(Number);
			const bookingDate = selectedDate.toDate(getLocalTimeZone());
			bookingDate.setHours(hours, minutes, 0, 0);
			const startTimeISO = bookingDate.toISOString();

			// Extract service IDs
			const serviceIds = selectedServices.map((s) => s.id);

			// For guest users, create a guest account first
			let guestUserId: string | null = null;
			if (!authStore.isAuthenticated) {
				const guestResponse = await fetch('/api/users/guest', {
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({
						firstName: customerInfo.firstName,
						lastName: customerInfo.lastName,
						email: customerInfo.email,
						phone: customerInfo.phone
					})
				});

				if (!guestResponse.ok) {
					const errorText = await guestResponse.text();
					if (guestResponse.status === 409) {
						toast.error('Email already registered — please log in to book.');
					} else {
						toast.error('Failed to create guest account. Please try again.');
					}
					isSubmitting = false;
					return;
				}

				const guestData = await guestResponse.json();
				guestUserId = guestData.id;
			}

			// Build request body
			const requestBody: Record<string, unknown> = {
				service_ids: serviceIds,
				start_time: startTimeISO,
				notes: customerInfo.specialRequests || null
			};

			if (guestUserId) {
				requestBody.user_id = guestUserId;
			}

			const headers: Record<string, string> = { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey };
			if (authStore.currentToken) {
				headers['Authorization'] = `Bearer ${authStore.currentToken}`;
			}

			const response = await fetch('/api/bookings', {
				method: 'POST',
				headers,
				body: JSON.stringify(requestBody)
			});

			if (response.ok) {
				const booking = await response.json();

				// Show success message with booking details
				const bookingDateStr = new Date(booking.start_time).toLocaleDateString('en-GB', {
					weekday: 'long',
					day: 'numeric',
					month: 'long'
				});
				const bookingTimeStr = new Date(booking.start_time).toLocaleTimeString('en-GB', {
					hour: 'numeric',
					minute: '2-digit'
				});
				
				toast.success(`Booking confirmed for ${bookingDateStr} at ${bookingTimeStr}!`);
				
				// Clear reservation state
				reservationId = null;
				reservationExpiresAt = null;
				
				// Reset form and redirect
				selectedServices = [];
				selectedDate = undefined;
				selectedTime = null;
				currentStep = 1;
				
				// Navigate to account page (logged-in) or home (guest)
				window.location.href = authStore.isAuthenticated ? '/account' : '/';
			} else {
				// Handle error response
				const errorText = await response.text();
				let errorMessage = 'Failed to submit booking. Please try again.';
				
				// Try to parse error message from backend
				try {
					const errorData = JSON.parse(errorText);
					if (errorData.error) {
						errorMessage = errorData.error;
					} else if (typeof errorData === 'string') {
						errorMessage = errorData;
					}
				} catch {
					// Use default message if parsing fails
				}
				
				// Handle specific error cases
				if (response.status === 409) {
					// Check if it's an active booking conflict or time slot conflict
					if (errorMessage.includes('active booking') || errorMessage.includes('already have')) {
						toast.error('You already have an active booking. Please complete or cancel it before creating a new one.');
					} else {
						toast.error('This time slot is no longer available. Please choose a different time.');
					}
				} else if (errorMessage.includes('patch test') || errorMessage.includes('Patch test')) {
					toast.error(errorMessage + ' Please complete a patch test first.');
				} else if (errorMessage.includes('48 hours') || errorMessage.includes('48h') || errorMessage.includes('advance')) {
					toast.error(errorMessage);
				} else if (response.status === 400) {
					toast.error(errorMessage);
				} else {
					toast.error('Failed to submit booking: ' + errorMessage);
				}
				console.error('Booking submission failed:', response.status, errorText);
			}
		} catch (error) {
			console.error('Booking submission error:', error);
			toast.error('Network error. Please check your connection and try again.');
		} finally {
			isSubmitting = false;
		}
	}
</script>

<div class="mx-auto max-w-4xl p-6">
	<div class="mb-8 text-center">
		<h1 class="mb-2 text-3xl font-bold">Book Your Appointment</h1>
		<p class="text-gray-600">Professional beauty treatments in a calm and friendly environment</p>
	</div>

	<StepIndicator {currentStep} steps={['Service', 'Date & Time', 'Details', 'Payment & Review', 'Confirm']} />

	<!-- Step 1: Service Selection -->
	{#if currentStep === 1}
		<Card.Root>
			<Card.Header>
				<Card.Title>Choose Your Services</Card.Title>
				<Card.Description>Select one or more treatments for your appointment</Card.Description>
			</Card.Header>
			<Card.Content class="space-y-4">
				<ServiceSelector
					{services}
					selected={selectedServices}
					loading={servicesLoading}
					ontoggle={toggleService}
				/>

				{#if selectedServices.length > 0}
					<div class="rounded-lg bg-gray-50 p-4">
						<h4 class="mb-2 font-semibold">Selected Services</h4>
						<div class="space-y-2">
							{#each selectedServices as service (service.id)}
								<div class="flex justify-between text-sm">
									<span>{service.name}</span>
									<span>{service.duration_minutes} mins • £{service.price}</span>
								</div>
							{/each}
							<Separator class="my-2" />
							<div class="flex justify-between text-sm font-semibold">
								<span>Estimated Duration:</span>
								<span>{formattedTotalDuration}</span>
							</div>
							<div class="flex justify-between text-sm font-semibold">
								<span>Total Cost:</span>
								<span>£{getTotalPrice()}</span>
							</div>
						</div>
					</div>
				{/if}
			</Card.Content>
			<Card.Footer class="flex justify-end">
				<BookingActions
					canBack={false}
					canNext={canProceedStep1}
					nextLabel="Next: Select Date & Time"
					on:next={nextStep}
				/>
			</Card.Footer>
		</Card.Root>
	{/if}

	<!-- Step 2: Date & Time Selection -->
	{#if currentStep === 2}
		<Card.Root>
			<Card.Header>
				<Card.Title>Choose Date & Time</Card.Title>
				<Card.Description>
					{selectedServices.map((s) => s.name).join(', ')} • {formattedTotalDuration} total • £{getTotalPrice()}
				</Card.Description>
			</Card.Header>
			<Card.Content class="p-0">
				<Card.Root class="gap-0 border-0 p-0">
					<Card.Content class="relative p-0 md:pr-56">
						{#if loadingWorkingHours}
							<div class="flex items-center justify-center p-6">
								<p>Loading available dates...</p>
							</div>
						{:else}
							<DatePicker
								date={selectedDate}
								{placeholder}
								minValue={minDate}
								maxValue={maxCalendarDate}
								{isDateUnavailable}
								onchange={(newDate) => {
									selectedDate = newDate;
									selectedTime = null;
								}}
								onPlaceholderChange={(newPlaceholder) => {
									placeholder = newPlaceholder;
								}}
							/>
						{/if}

						{#if loadingAvailableHours}
							<div
								class="absolute inset-y-0 right-0 flex w-56 items-center justify-center border-l p-6"
							>
								<p class="text-sm text-gray-500">Loading times...</p>
							</div>
						{:else}
							<TimeSlotPicker
								date={selectedDate}
								{groupedTimeSlots}
								{selectedTime}
								formattedDate={formattedSelectedDate}
								onselect={(time) => {
									selectTimeWithValidation(time);
								}}
								lunchProtectionStatus={lunchProtectionStatus()}
							/>
						{/if}
					</Card.Content>
				</Card.Root>
			</Card.Content>

			<!-- Mobile appointment summary -->
			<div class="border-t px-6 py-4 text-center text-sm md:hidden">
				{#if selectedDate && selectedTime}
					Appointment for
					<span class="font-medium">
						{selectedDate.toDate(getLocalTimeZone()).toLocaleDateString('en-US', {
							weekday: 'long',
							day: 'numeric',
							month: 'short'
						})}
					</span>
					<br />at <span class="font-medium">{selectedTime}</span>
				{:else}
					Select a date and time
				{/if}
			</div>

			<Card.Footer class="flex justify-between border-t px-6 !py-5">
				<Button variant="outline" onclick={prevStep}>Back</Button>
				<div class="flex items-center space-x-4">
					<!-- Desktop appointment summary -->
					<div class="hidden text-sm md:block">
						{#if selectedDate && selectedTime}
							Appointment for
							<span class="font-medium">
								{selectedDate.toDate(getLocalTimeZone()).toLocaleDateString('en-US', {
									weekday: 'long',
									day: 'numeric',
									month: 'short'
								})}
							</span>
							at <span class="font-medium">{selectedTime}</span>
						{:else}
							Select a date and time
						{/if}
					</div>
					<Button disabled={!canProceedStep2} onclick={nextStep}>Next: Your Details</Button>
				</div>
			</Card.Footer>
		</Card.Root>
	{/if}

	<!-- Step 3: Customer Details -->
	{#if currentStep === 3}
		<Card.Root>
			<Card.Header>
				<Card.Title>Your Details</Card.Title>
				<Card.Description>Please confirm your contact information</Card.Description>
			</Card.Header>
			<Card.Content class="space-y-6">
				{#if reservationExpired}
					<div class="rounded-lg bg-red-50 p-4 text-center">
						<p class="text-red-600">Reservation expired — please go back and select a new time</p>
					</div>
				{:else}
					<div class="rounded-lg bg-blue-50 p-4 text-center">
						<p class="text-blue-700">
							Your slot is reserved for {reservationCountdown} — complete your booking before time expires
						</p>
					</div>
				{/if}

				<BookingSummary
					services={selectedServices}
					date={selectedDate}
					time={selectedTime}
					showCustomer={false}
				/>

				{#if !authStore.isAuthenticated}
					<p class="mb-4 text-center text-sm text-yellow-600">
						You are checking out as a guest, so you will miss out on a loyalty stamp. Please login
						for full membership benefits.
					</p>

					<div class="grid gap-4 md:grid-cols-2">
						<div class="space-y-2">
							<Label for="firstName">First Name *</Label>
							<Input
								id="firstName"
								bind:value={customerInfo.firstName}
								placeholder="Enter your first name"
							/>
						</div>
						<div class="space-y-2">
							<Label for="lastName">Last Name *</Label>
							<Input
								id="lastName"
								bind:value={customerInfo.lastName}
								placeholder="Enter your last name"
							/>
						</div>
						<div class="space-y-2">
							<Label for="email">Email *</Label>
							<Input
								id="email"
								type="email"
								bind:value={customerInfo.email}
								placeholder="Enter your email"
							/>
						</div>
						<div class="space-y-2">
							<Label for="phone">Phone Number *</Label>
							<Input
								id="phone"
								type="tel"
								bind:value={customerInfo.phone}
								placeholder="Enter your phone number"
							/>
						</div>
					</div>
				{/if}

				<div class="space-y-2">
					<Label for="requests">Special Requests (Optional)</Label>
					<Textarea
						id="requests"
						bind:value={customerInfo.specialRequests}
						placeholder="Any allergies, preferences, or special requirements..."
						rows={3}
					/>
				</div>

				<div class="text-sm text-gray-600">
					{#if !authStore.isAuthenticated}
						<p>* Required fields</p>
					{/if}
					<p class="mt-2">
						By booking, you agree to our Terms & Conditions and Privacy Policy. We'll send you
						appointment reminders via email and/or SMS.
					</p>
					<p class="mt-2 text-xs text-gray-500">
						<strong>Cancellation Policy:</strong> Free cancellation up to 24 hours before your
						appointment. Cancellations within 24 hours may incur a deposit penalty.
					</p>
				</div>
			</Card.Content>
			<Card.Footer class="flex justify-between">
				<Button variant="outline" onclick={prevStep}>Back</Button>
				<Button
					disabled={!canProceedStep3}
					onclick={nextStep}
					class="bg-primary text-primary-foreground"
				>
					{reservationExpired ? 'Reservation Expired' : 'Next: Review & Payment'}
				</Button>
			</Card.Footer>
		</Card.Root>
	{/if}

	<!-- Step 4: Payment & Review -->
	{#if currentStep === 4}
		<Card.Root>
			<Card.Header>
				<Card.Title>Payment & Review</Card.Title>
				<Card.Description>Review your booking details</Card.Description>
			</Card.Header>
			<Card.Content class="space-y-6">
				<BookingSummary
					services={selectedServices}
					date={selectedDate}
					time={selectedTime}
					customer={authStore.isAuthenticated
						? {
								firstName: authStore.currentUser?.firstName ?? '',
								lastName: authStore.currentUser?.lastName ?? '',
								email: authStore.currentUser?.email ?? '',
								phone: authStore.currentUser?.phone ?? '',
								specialRequests: customerInfo.specialRequests
							}
						: customerInfo}
					showCustomer={true}
				/>

				<!-- TODO: Integrate payment processor (Stripe/Square) here. Current flow: logged-in users go straight to confirmation, guest users show payment step before confirmation. -->
				<div class="rounded-lg bg-white p-6">
					<h2 class="mb-4 text-2xl font-semibold">Payment</h2>
					<p class="text-gray-600">Square payment integration will be added here.</p>
				</div>
			</Card.Content>
			<Card.Footer class="flex justify-between">
				<Button variant="outline" onclick={prevStep}>Back</Button>
				<Button
					disabled={isSubmitting}
					onclick={nextStep}
					class="bg-primary text-primary-foreground"
				>
					Next: Confirm
				</Button>
			</Card.Footer>
		</Card.Root>
	{/if}

	<!-- Step 5: Confirm -->
	{#if currentStep === 5}
		<Card.Root>
			<Card.Header>
				<Card.Title>Confirm Your Booking</Card.Title>
				<Card.Description>Ready to confirm your appointment</Card.Description>
			</Card.Header>
			<Card.Content class="space-y-6">
				<div class="rounded-lg bg-gray-50 p-6 text-center">
					<p class="text-lg">
						Ready to confirm: {selectedServices.map((s) => s.name).join(', ')} on {selectedDate?.toDate(getLocalTimeZone()).toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' })} at {selectedTime}
					</p>
				</div>
			</Card.Content>
			<Card.Footer class="flex justify-between">
				<Button variant="outline" onclick={prevStep}>Back</Button>
				<Button
					disabled={isSubmitting}
					onclick={submitBooking}
					class="bg-primary text-primary-foreground"
				>
					{isSubmitting ? 'Processing...' : 'Confirm Booking'}
				</Button>
			</Card.Footer>
		</Card.Root>
	{/if}
</div>
