<script lang="ts">
	import { apiFetch } from '$lib/utils/api';
	import { toast } from 'svelte-sonner';
	import { SvelteDate, SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
	import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
	import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
	import { formatUserName } from '$lib/utils/nameDisplay';

	// UI Components
	import * as Modal from '$lib/components/ui/dialog';
	import { Button } from '$lib/components/ui/button';
	import * as Card from '$lib/components/ui/card';
	// Note: We are using native inputs for Steps 1 and 3 to fix reactivity bugs
	// but keeping the Label and other components.
	import { Checkbox } from '$lib/components/ui/checkbox';
	import { Label } from '$lib/components/ui/label';
	import { Input } from '$lib/components/ui/input';
	import { Separator } from '$lib/components/ui/separator';
	import { Skeleton } from '$lib/components/ui/skeleton';
	import CharCounter from '$lib/components/ui/CharCounter.svelte';
	import { PhoneInput } from '$lib/components/ui/phone-input/index.js';

	// Booking Components
	import BookingActions from '$lib/components/booking/BookingActions.svelte';
	import ServiceSelector from '$lib/components/booking/ServiceSelector.svelte';
	import DatePicker from '$lib/components/booking/DatePicker.svelte';
	import TimeSlotList from '$lib/components/booking/TimeSlotList.svelte';
	import SelectedTimeSummary from '$lib/components/booking/SelectedTimeSummary.svelte';

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

	import {
		buildLunchProtection,
		generateAvailableTimeSlots,
		generateGroupedTimeSlots,
		formatTime,
		formatLocalDateTime,
		calculateEndTime,
		timeToMinutes,
		getDayWithOrdinal,
		parseWallClockDate,
		getLondonTodayCalendarDate,
		type DayHours,
		type DayAvailability
	} from '$lib/utils/timeSlots';

	// =============== Props ===============
	interface Props {
		open: boolean;
		onBookingCreated?: () => void;
	}

	let { open = $bindable(), onBookingCreated }: Props = $props();

	// =============== State ===============
	let currentStep = $state(1);

	// Step 1: Customer Selection
	let userType = $state<'member' | 'guest'>('member');
	let userQuery = $state('');
	// Updated type to include account_role for filtering
	let users = $state<
		Array<{
			id: string;
			fullName: string;
			email?: string;
			phone?: string;
			account_role: string;
			previousFirstName?: string | null;
			previousLastName?: string | null;
		}>
	>([]);
	let selectedUserId = $state<string | null>(null);
	let guestName = $state('');
	let guestPhone = $state('');
	let guestPhoneError = $state('');
	let loadingUsers = $state(false);

	// Step 2: Services
	let services = $state<Service[]>([]);
	let selectedServices = $state<Service[]>([]);
	let loadingServices = $state(true);

	// Custom Services
	let customServices = $state<Array<CustomService & { is_custom: boolean }>>([]);
	let customSearchQuery = $state('');
	let loadingCustomServices = $state(false);
	let showCustomCreateForm = $state(false);
	let newCustomService = $state({
		name: '',
		description: '',
		price: '',
		duration_minutes: '',
		minimum_age_required: ''
	});
	let creatingCustomService = $state(false);
	let customServiceErrors = $state<Record<string, string>>({});

	const durationOptions = Array.from({ length: 32 }, (_, i) => (i + 1) * 15);

	function validateCsName(name: unknown): string {
		const n = name === null || name === undefined ? '' : String(name);
		if (!n.trim()) return 'Name is required';
		return '';
	}
	function validateCsPrice(price: unknown): string {
		const p = price === null || price === undefined ? '' : String(price);
		if (!p.trim()) return 'Price is required';
		const num = parseFloat(p);
		if (isNaN(num) || num <= 0) return 'Must be greater than 0';
		return '';
	}
	function validateCsDuration(value: unknown): string {
		const v = value === null || value === undefined ? '' : String(value);
		if (!v.trim()) return 'Duration is required';
		const num = parseInt(v);
		if (isNaN(num) || num <= 0) return 'Must be greater than 0';
		return '';
	}
	function validateCsMinimumAge(value: unknown): string {
		const v = value === null || value === undefined ? '' : String(value);
		if (!v.trim()) return 'Required';
		const num = parseInt(v);
		if (isNaN(num) || num < 0 || num > 100) return 'Must be between 0 and 100';
		return '';
	}
	function validateCsAll() {
		customServiceErrors = {
			name: validateCsName(newCustomService.name),
			price: validateCsPrice(newCustomService.price),
			duration_minutes: validateCsDuration(newCustomService.duration_minutes),
			minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
		};
	}
	const isCustomFormValid = $derived(
		(newCustomService.name ?? '').trim() !== '' &&
			!customServiceErrors.name &&
			!customServiceErrors.price &&
			!customServiceErrors.duration_minutes &&
			!customServiceErrors.minimum_age_required
	);
	// eslint-disable-next-line @typescript-eslint/no-unused-vars
	function toggleCustomForm(show: boolean) {
		showCustomCreateForm = show;
		if (show) {
			newCustomService = {
				name: '',
				description: '',
				price: '',
				duration_minutes: '',
				minimum_age_required: ''
			};
			customServiceErrors = { name: '', price: '', duration_minutes: '', minimum_age_required: '' };
			requestAnimationFrame(() => {
				const modalContent = document.querySelector('[data-custom-form-container]');
				if (modalContent) {
					modalContent.scrollTop = modalContent.scrollHeight;
				}
			});
		}
	}

	// Step 3: Service Overrides & Notes
	let notes = $state('');
	let serviceOverrides = $state<
		Record<
			string,
			{ price: string; duration: string; originalPrice: number; originalDuration: number }
		>
	>({});

	// Step 4: Date & Time
	let placeholder = $state<CalendarDate>(getLondonTodayCalendarDate());
	let selectedDate = $state<CalendarDate | undefined>(undefined);
	let selectedTime = $state<string | null>(null);
	let workingHours = $state<Record<string, DayHours> | null>(null);
	let availableHours = $state<Record<string, DayAvailability> | null>(null);
	let _loadingWorkingHours = $state(false);
	let loadingAvailableHours = $state(false);
	let hoursRangeGeneration = $state(0);
	let hoursMonthGeneration = $state(0);
	let outOfHours = $state(false);
	// Keep the last-known normal (non-out-of-hours) working hours for per-slot styling
	let normalWorkingHours = $state<Record<string, DayHours> | null>(null);
	// Clear caches and force re-fetch when out-of-hours toggled
	let prevOutOfHours = $state(false);
	$effect(() => {
		if (outOfHours !== prevOutOfHours) {
			prevOutOfHours = outOfHours;
			// Save normal hours BEFORE clearing, so we can show which slots are genuinely out-of-hours
			if (outOfHours && workingHours) {
				normalWorkingHours = { ...workingHours };
			} else if (!outOfHours) {
				normalWorkingHours = null;
			}
			// Clear data and caches
			workingHoursCache = {};
			availableHoursCache = {};
			workingHours = null;
			availableHours = null;
			selectedTime = null;
			// Directly re-fetch since caches aren't reactive (plain `let` not `$state`)
			// so the safety net effect won't detect the cache deletion
			if (currentStep === 4 && placeholder) {
				fetchHoursRange(placeholder, 2);
			}
		}
	});

	// Date Boundaries
	const today = getLondonTodayCalendarDate();
	const minDate = today;
	const maxDate = new SvelteDate(today.year, today.month - 1, today.day);
	maxDate.setMonth(today.month - 1 + 6);
	const maxCalendarDate = new CalendarDate(
		maxDate.getFullYear(),
		maxDate.getMonth() + 1,
		maxDate.getDate()
	);

	let submitting = $state(false);

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

	// =============== Cache ===============
	let workingHoursCache: Record<string, Record<string, DayHours>> = {};
	let availableHoursCache: Record<string, Record<string, DayAvailability>> = {};
	let loadingMonthKeys: Set<string> = new SvelteSet();

	// =============== Derived Helpers ===============
	function getTotalDuration() {
		return selectedServices.reduce((total, service) => {
			const override = serviceOverrides[service.id];
			const duration =
				override && override.duration ? parseInt(override.duration) : service.duration_minutes;
			return total + (isNaN(duration) ? 0 : duration);
		}, 0);
	}

	function getTotalPrice() {
		return selectedServices.reduce((total, service) => {
			const override = serviceOverrides[service.id];
			const price = override && override.price ? parseFloat(override.price) : service.price;
			return total + (isNaN(price) ? 0 : price);
		}, 0);
	}

	// =============== Lunch Protection ===============
	const lunchProtection = $derived(
		selectedDate && selectedServices.length > 0 && !outOfHours
			? buildLunchProtection(selectedDate, workingHours, availableHours, getTotalDuration(), true)
			: new Map()
	);

	// =============== Debug: Slot generation ===============
	/** Check if a slot time falls outside the normal (non-out-of-hours) business hours */
	function isSlotOutOfHours(
		dateStr: string,
		timeStr: string,
		duration: number,
		normalWH: Record<string, DayHours> | null
	): boolean {
		if (!normalWH) return false;
		const normalDay = normalWH[dateStr];
		if (!normalDay) return false;
		// Day is normally closed → ALL slots are out-of-hours
		if (!normalDay.isOpen) return true;
		// Slot starts before normal opening
		if (timeToMinutes(timeStr) < timeToMinutes(normalDay.startTime)) return true;
		// Slot ends after normal closing
		if (timeToMinutes(timeStr) + duration > timeToMinutes(normalDay.endTime)) return true;
		return false;
	}

	const groupedTimeSlots = $derived.by(() => {
		const base =
			currentStep === 4 && selectedServices.length > 0 && selectedDate
				? generateGroupedTimeSlots(
						selectedDate,
						workingHours,
						availableHours,
						getTotalDuration(),
						lunchProtection
					)
				: [];
		if (!outOfHours || !normalWorkingHours || !selectedDate) return base;
		const dateStr = selectedDate.toString();
		const duration = getTotalDuration();
		return base.map((slot) => {
			if (slot.type === 'available') {
				return {
					...slot,
					outOfHours: isSlotOutOfHours(dateStr, slot.startTime, duration, normalWorkingHours)
				};
			}
			return slot;
		});
	});

	function formatDuration(minutes: number): string {
		const hours = Math.floor(minutes / 60);
		const remainingMinutes = minutes % 60;
		if (hours === 0) return `${remainingMinutes} minutes`;
		if (remainingMinutes === 0) return `${hours} ${hours === 1 ? 'hour' : 'hours'}`;
		return `${hours} ${hours === 1 ? 'hour' : 'hours'} ${remainingMinutes} minutes`;
	}

	const formattedTotalDuration = $derived(formatDuration(getTotalDuration()));
	const formattedSelectedDate = $derived(
		selectedDate ? getDayWithOrdinal(selectedDate) : undefined
	);

	const canProceedStep1 = $derived(
		userType === 'member'
			? !!selectedUserId
			: !!(guestName.trim() && guestPhone.trim() && isValidUKPhone(guestPhone))
	);
	const canProceedStep2 = $derived(selectedServices.length > 0);
	// eslint-disable-next-line @typescript-eslint/no-unused-vars
	const canProceedStep3 = $derived(true); // Overrides are optional
	const canProceedStep4 = $derived(!!(selectedDate && selectedTime));

	/** Whether the currently selected time slot is out-of-hours */
	const selectedTimeOutOfHours = $derived(
		outOfHours && selectedDate && selectedTime && normalWorkingHours
			? isSlotOutOfHours(
					selectedDate.toString(),
					selectedTime,
					getTotalDuration(),
					normalWorkingHours
				)
			: false
	);

	// =============== Effects ===============
	let wasOpen = false;
	let userNavigatedCalendar = $state(false);

	$effect(() => {
		if (open && !wasOpen) {
			resetState();
			fetchServices();
			fetchUsers();
		}

		wasOpen = open;
	});

	// Refetch services when selected user changes (for eligibility)
	$effect(() => {
		if (open && selectedUserId) {
			fetchServices();
		}
	});

	// Preload current + next month on entering step 4; individual months fetched on navigation
	let bookingCreateInitialLoadDone = $state(false);
	$effect(() => {
		if (open && currentStep === 4 && !bookingCreateInitialLoadDone) {
			fetchHoursRange(placeholder, 2);
			bookingCreateInitialLoadDone = true;
		}
	});

	// Safety net: fetch when navigating to an uncached month
	$effect(() => {
		if (open && currentStep === 4 && bookingCreateInitialLoadDone) {
			const monthKey = `${placeholder.year}-${String(placeholder.month).padStart(2, '0')}`;
			if (!(monthKey in workingHoursCache) && !loadingMonthKeys.has(monthKey)) {
				fetchHoursForMonth(placeholder);
			}
		}
	});

	// Auto-select first available date once data loads (timing-safe, data-driven)
	let bookingCreateAutoSelectDone = $state(false);
	$effect(() => {
		if (
			open &&
			currentStep === 4 &&
			workingHours &&
			availableHours &&
			!selectedDate &&
			selectedServices.length > 0 &&
			!userNavigatedCalendar &&
			!bookingCreateAutoSelectDone
		) {
			bookingCreateAutoSelectDone = true;
			const now = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
			const maxDateJs = new SvelteDate(
				maxCalendarDate.year,
				maxCalendarDate.month - 1,
				maxCalendarDate.day
			);
			const daysDifference = Math.floor(
				(maxDateJs.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
			);
			const daysToCheck = Math.min(daysDifference, 180);
			for (let i = 0; i <= daysToCheck; i++) {
				const checkDate = new SvelteDate(now);
				checkDate.setDate(now.getDate() + i);
				const dateStr = checkDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
				const calDate = new CalendarDate(
					checkDate.getFullYear(),
					checkDate.getMonth() + 1,
					checkDate.getDate()
				);
				if (outOfHours) {
					// Out-of-hours: just check available hours exist with slots
					const dayAH = availableHours?.[dateStr];
					if (dayAH?.slots?.length > 0 && !isDateUnavailable(calDate)) {
						selectedDate = calDate;
						if (!userNavigatedCalendar) {
							placeholder = new CalendarDate(checkDate.getFullYear(), checkDate.getMonth() + 1, 1);
						}
						break;
					}
				} else if (workingHours[dateStr]?.isOpen && !isDateUnavailable(calDate)) {
					selectedDate = calDate;
					if (!userNavigatedCalendar) {
						placeholder = new CalendarDate(checkDate.getFullYear(), checkDate.getMonth() + 1, 1);
					}
					break;
				}
			}
		}
	});

	// =============== Reset State ===============
	function resetState() {
		currentStep = 1;
		userType = 'member';
		userQuery = '';
		users = [];
		selectedUserId = null;
		guestName = '';
		guestPhone = '';
		selectedServices = [];
		selectedDate = undefined;
		selectedTime = null;
		notes = '';
		serviceOverrides = {};
		workingHoursCache = {};
		availableHoursCache = {};
		workingHours = null;
		availableHours = null;
		bookingCreateInitialLoadDone = false;
		userNavigatedCalendar = false;
		bookingCreateAutoSelectDone = false;
		loadingMonthKeys = new SvelteSet();
		outOfHours = false;
		normalWorkingHours = null;
		// Clear reservation state
		reservationId = null;
		reservationExpiresAt = null;
		reservationCountdown = '';
		if (window.__bookingCreateCountdownInterval) {
			clearInterval(window.__bookingCreateCountdownInterval);
		}
	}

	// =============== Data Fetching ===============
	async function fetchUsers() {
		loadingUsers = true;
		try {
			const response = await apiFetch(
				`/api/admin/users?page=1&per_page=4&q=${encodeURIComponent(userQuery)}`
			);
			if (response.ok) {
				const data = await response.json();

				// Filter out specific roles
				const excludedRoles = ['admin', 'guest', 'affiliate'];
				users = (data.users || []).filter(
					(user: { account_role: string }) => !excludedRoles.includes(user.account_role)
				);
			}
		} catch (_err) {
			toast.error('Failed to load users');
		} finally {
			loadingUsers = false;
		}
	}

	async function fetchServices() {
		loadingServices = true;
		try {
			let url = '/api/services';
			// If a user is selected, get eligibility for that user
			if (selectedUserId) {
				url = `/api/services/eligible-for/${selectedUserId}`;
			}
			const response = await apiFetch(url);
			if (response.ok) {
				services = await response.json();
			}
		} catch (_err) {
			toast.error('Failed to load services');
		} finally {
			loadingServices = false;
		}
	}

	async function fetchHoursRange(startDate: CalendarDate, months: number) {
		// Calculate end month manually (CalendarDate is immutable)
		let endYear = startDate.year;
		let endMonth = startDate.month + months - 1;
		while (endMonth > 12) {
			endMonth -= 12;
			endYear++;
		}
		const endMonthDate = new CalendarDate(endYear, endMonth, 1);
		const daysInEndMonth = endMonthDate.calendar.getDaysInMonth(endMonthDate);

		const startStr = startDate.toString();
		const endStr = `${endYear}-${String(endMonth).padStart(2, '0')}-${String(daysInEndMonth).padStart(2, '0')}`;

		hoursRangeGeneration++;
		const gen = hoursRangeGeneration;

		_loadingWorkingHours = true;
		loadingAvailableHours = true;

		try {
			const [whRes, ahRes] = await Promise.all([
				apiFetch(
					`/api/scheduling/working-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`
				),
				apiFetch(
					`/api/scheduling/available-hours?start=${startStr}&end=${endStr}${outOfHours ? '&out_of_hours=true' : ''}`
				)
			]);

			if (whRes.ok && ahRes.ok) {
				const whData: WorkingHoursDay[] = await whRes.json();
				const ahData: AvailableHoursDay[] = await ahRes.json();

				const whMap: Record<string, DayHours> = {};
				const ahMap: Record<string, DayAvailability> = {};

				whData.forEach(
					(d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime })
				);
				ahData.forEach((d) => (ahMap[d.date] = { isOpen: d.isOpen, slots: d.slots }));

				if (gen !== hoursRangeGeneration) return; // Stale response, discard

				// Cache by month key
				for (let i = 0; i < months; i++) {
					let mYear = startDate.year;
					let mMonth = startDate.month + i;
					while (mMonth > 12) {
						mMonth -= 12;
						mYear++;
					}
					const key = `${mYear}-${String(mMonth).padStart(2, '0')}`;
					workingHoursCache[key] = whMap;
					availableHoursCache[key] = ahMap;
				}

				// MERGE instead of replace — preserves data from previously loaded months
				workingHours = { ...workingHours, ...whMap };
				availableHours = { ...availableHours, ...ahMap };
			}
		} catch (_err) {
			toast.error('Failed to load availability');
		} finally {
			_loadingWorkingHours = false;
			loadingAvailableHours = false;
		}
	}

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

		if (monthKey in workingHoursCache && monthKey in availableHoursCache) {
			// MERGE instead of replace — preserves data from other loaded months
			workingHours = { ...workingHours, ...workingHoursCache[monthKey] };
			availableHours = { ...availableHours, ...availableHoursCache[monthKey] };
			return;
		}

		// Prevent re-entrant calls for the same month
		if (loadingMonthKeys.has(monthKey)) return;
		loadingMonthKeys = new SvelteSet(loadingMonthKeys).add(monthKey);

		hoursMonthGeneration++;
		const gen = hoursMonthGeneration;

		_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 [whRes, ahRes] = await Promise.all([
				apiFetch(
					`/api/scheduling/working-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`
				),
				apiFetch(
					`/api/scheduling/available-hours?start=${startOfMonth}&end=${endOfMonth}${outOfHours ? '&out_of_hours=true' : ''}`
				)
			]);

			if (whRes.ok && ahRes.ok) {
				const whData: WorkingHoursDay[] = await whRes.json();
				const ahData: AvailableHoursDay[] = await ahRes.json();

				const whMap: Record<string, DayHours> = {};
				const ahMap: Record<string, DayAvailability> = {};

				whData.forEach(
					(d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime })
				);
				ahData.forEach((d) => (ahMap[d.date] = { isOpen: d.isOpen, slots: d.slots }));

				if (gen !== hoursMonthGeneration) return; // Stale response, discard

				workingHoursCache[monthKey] = whMap;
				availableHoursCache[monthKey] = ahMap;
				workingHours = { ...workingHours, ...whMap };
				availableHours = { ...availableHours, ...ahMap };
			}
		} catch (_err) {
			toast.error('Failed to load availability');
		} finally {
			_loadingWorkingHours = false;
			loadingAvailableHours = false;
			loadingMonthKeys = new SvelteSet([...loadingMonthKeys].filter((k) => k !== monthKey));
		}
	}

	// =============== Reservation ===============
	async function reserveSlot(): Promise<boolean> {
		if (!selectedDate || !selectedTime) {
			return false;
		}

		isReserving = true;

		try {
			const localDate = selectedDate.toDate(getLocalTimeZone());
			const [hours, minutes] = selectedTime.split(':').map(Number);
			localDate.setHours(hours, minutes, 0, 0);
			const startTimeISO = formatLocalDateTime(localDate);

			const serviceIds = selectedServices.filter((s) => !s.is_custom).map((s) => s.id);
			const customServiceIds = selectedServices.filter((s) => s.is_custom).map((s) => s.id);

			// Build service overrides payload
			const overrides = [];
			for (const [serviceId, data] of Object.entries(serviceOverrides)) {
				const durationChanged = parseInt(data.duration) !== data.originalDuration;
				if (durationChanged) {
					overrides.push({
						service_id: serviceId,
						override_duration_minutes: parseInt(data.duration)
					});
				}
			}

			const payload: {
				user_id: string | null;
				start_time: string;
				service_ids: string[];
				service_overrides: Array<{ service_id: string; override_duration_minutes: number }>;
				ttl_minutes: number;
				reservation_type: string;
				out_of_hours: boolean;
				custom_service_ids?: string[];
			} = {
				user_id: selectedUserId || null,
				start_time: startTimeISO,
				service_ids: serviceIds,
				service_overrides: overrides.length > 0 ? overrides : [],
				ttl_minutes: 15,
				reservation_type: 'callin',
				out_of_hours: outOfHours
			};
			if (customServiceIds.length > 0) {
				payload.custom_service_ids = customServiceIds;
			}

			const response = await apiFetch('/api/admin/bookings/reserve', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(payload)
			});

			if (response.ok) {
				const data = await response.json();
				reservationId = data.id;
				reservationExpiresAt = new Date(data.expires_at);
				startCountdown();
				return true;
			} else if (response.status === 409) {
				toast.error('Slot no longer available, refreshing...');
				// Refresh available hours
				if (selectedDate) {
					fetchHoursForMonth(selectedDate);
				}
				reservationId = null;
				reservationExpiresAt = null;
				reservationCountdown = '';
				return false;
			} else {
				const errorText = await response.text();
				toast.error(`Failed to reserve slot: ${errorText}`);
				return false;
			}
		} catch (_err) {
			toast.error('Failed to reserve slot');
			return false;
		} finally {
			isReserving = false;
		}
	}

	function startCountdown() {
		// Clear any existing interval
		if (window.__bookingCreateCountdownInterval) {
			clearInterval(window.__bookingCreateCountdownInterval);
		}

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

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

			if (diff <= 0) {
				reservationCountdown = 'Expired';
				reservationId = null;
				reservationExpiresAt = null;
				if (window.__bookingCreateCountdownInterval) {
					clearInterval(window.__bookingCreateCountdownInterval);
				}
				return;
			}

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

		updateCountdown();
		window.__bookingCreateCountdownInterval = setInterval(updateCountdown, 1000);
	}

	// =============== Logic ===============
	function toggleService(service: Service) {
		const index = selectedServices.findIndex((s) => s.id === service.id);
		if (index >= 0) {
			selectedServices = selectedServices.filter((s) => s.id !== service.id);
			const newOverrides = { ...serviceOverrides };
			delete newOverrides[service.id];
			serviceOverrides = newOverrides;
		} else {
			selectedServices = [...selectedServices, service];
			serviceOverrides = {
				...serviceOverrides,
				[service.id]: {
					price: service.price.toFixed(2),
					duration: service.duration_minutes.toString(),
					originalPrice: service.price,
					originalDuration: service.duration_minutes
				}
			};
		}

		selectedTime = null;
	}

	async function fetchCustomServices() {
		loadingCustomServices = true;
		try {
			const params = new SvelteURLSearchParams();
			if (customSearchQuery.trim()) {
				params.set('q', customSearchQuery.trim());
			} else {
				params.set('popular', '3');
			}
			const response = await apiFetch(`/api/admin/custom-services?${params}`);
			if (response.ok) {
				const data = await response.json();
				const list = data.services || data;
				customServices = list.map((cs: CustomService) => ({ ...cs, is_custom: true }));
			}
		} catch {
			console.error('Failed to fetch custom services');
		} finally {
			loadingCustomServices = false;
		}
	}

	async function createCustomService() {
		validateCsAll();
		if (!isCustomFormValid) {
			toast.error('Please fix the validation errors');
			return;
		}
		creatingCustomService = true;
		try {
			const response = await apiFetch('/api/admin/custom-services', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					name: (newCustomService.name ?? '').trim(),
					description: (newCustomService.description ?? '').trim() || undefined,
					price: parseFloat(newCustomService.price ?? '0'),
					duration_minutes: parseInt(newCustomService.duration_minutes ?? '0'),
					minimum_age_required: parseInt(newCustomService.minimum_age_required ?? '0') || 0
				})
			});
			if (response.ok) {
				const cs = await response.json();
				const customService = { ...cs, is_custom: true };
				selectedServices = [...selectedServices, customService];
				serviceOverrides = {
					...serviceOverrides,
					[cs.id]: {
						price: cs.price.toFixed(2),
						duration: cs.duration_minutes.toString(),
						originalPrice: cs.price,
						originalDuration: cs.duration_minutes
					}
				};
				showCustomCreateForm = false;
				newCustomService = {
					name: '',
					description: '',
					price: '',
					duration_minutes: '',
					minimum_age_required: ''
				};
				customServiceErrors = { name: '', price: '', duration_minutes: '' };
				toast.success('Custom service created and added');
			} else {
				const err = await response.text();
				toast.error(`Failed: ${err}`);
			}
		} catch {
			toast.error('Network error');
		} finally {
			creatingCustomService = false;
		}
	}

	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();

		// Out-of-hours: only check if available hours exist with slots
		if (outOfHours) {
			const ahDay = availableHours?.[dateStr];
			return !ahDay?.slots || ahDay.slots.length === 0;
		}

		const dayHours = workingHours[dateStr];
		if (!dayHours?.isOpen) return true;

		// No available hours data for this date = data not loaded = unavailable
		if (!availableHours?.[dateStr]) return true;
		// API returned empty slots = no availability at all
		if (!availableHours[dateStr].slots || availableHours[dateStr].slots.length === 0) return true;

		// Determine the duration to check: if services are selected use their total,
		// otherwise use a minimum of 15 minutes (any meaningful booking needs at least this)
		const duration = selectedServices.length > 0 ? getTotalDuration() : 15;
		if (duration <= 0) return true;

		// Build lunch protection specifically for the date being checked
		const dayProtection = buildLunchProtection(
			date as CalendarDate,
			workingHours,
			availableHours,
			duration,
			true
		);
		const slots = generateAvailableTimeSlots(
			date as CalendarDate,
			workingHours,
			availableHours,
			duration,
			dayProtection
		);
		if (slots.length === 0) return true;

		return false;
	}

	// =============== Submission ===============
	async function submitBooking() {
		// First, reserve the slot
		const reserved = await reserveSlot();
		if (!reserved) {
			submitting = false;
			return;
		}

		submitting = true;

		try {
			let finalUserId = selectedUserId;

			if (userType === 'guest') {
				if (!isValidUKPhone(guestPhone)) {
					toast.error('Please enter a valid UK phone number for the guest');
					return;
				}
				const phone = toE164UK(guestPhone)!;
				const createRes = await apiFetch('/api/users/guest', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json'
					},
					body: JSON.stringify({
						firstName: guestName.trim().split(' ')[0] || 'Guest',
						lastName: guestName.trim().split(' ').slice(1).join(' ') || 'Customer',
						phone: phone,
						email: `callin-${Date.now()}@guest.invalid`
					})
				});

				if (!createRes.ok) {
					toast.error('Failed to create guest user');
					submitting = false;
					return;
				}

				const guestUser = await createRes.json();
				finalUserId = guestUser.id;
			}

			if (!finalUserId) throw new Error('User ID required');
			if (!selectedDate || !selectedTime) throw new Error('Date and time required');

			const localDate = selectedDate.toDate(getLocalTimeZone());
			const [hours, minutes] = selectedTime.split(':').map(Number);
			localDate.setHours(hours, minutes, 0, 0);
			const dateTimeStr = formatLocalDateTime(localDate);

			const overrides = [];
			for (const [serviceId, data] of Object.entries(serviceOverrides)) {
				const priceChanged = Math.abs(parseFloat(data.price) - data.originalPrice) > 0.01;
				const durationChanged = parseInt(data.duration) !== data.originalDuration;

				if (priceChanged || durationChanged) {
					overrides.push({
						service_id: serviceId,
						override_price: priceChanged ? parseFloat(data.price) : null,
						override_duration_minutes: durationChanged ? parseInt(data.duration) : null
					});
				}
			}

			const payload: {
				user_id: string;
				start_time: string;
				service_ids: string[];
				custom_service_ids: string[];
				service_overrides:
					| Array<{
							service_id: string;
							override_price: number | null;
							override_duration_minutes: number | null;
					  }>
					| undefined;
				notes: string | null;
				out_of_hours: boolean;
			} = {
				user_id: finalUserId,
				start_time: dateTimeStr,
				service_ids: selectedServices.filter((s) => !s.is_custom).map((s) => s.id),
				custom_service_ids: selectedServices.filter((s) => s.is_custom).map((s) => s.id),
				service_overrides: overrides.length > 0 ? overrides : undefined,
				notes: notes.trim() || null,
				out_of_hours: outOfHours
			};

			const res = await apiFetch('/api/admin/bookings', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify(payload)
			});

			if (res.ok) {
				toast.success('Booking created successfully!');
				open = false;
				window.dispatchEvent(new CustomEvent('bookingApproved'));
				onBookingCreated?.();
			} else {
				const errorText = await res.text();
				toast.error(`Failed to create booking: ${errorText}`);
			}
		} catch (_err) {
			toast.error('An error occurred while creating booking');
		} finally {
			submitting = false;
		}
	}

	// Input handlers
	function handlePriceInput(serviceId: string, value: string) {
		const override = serviceOverrides[serviceId];
		if (!override) return;

		let cleaned = value.replace(/[^\d.]/g, '');
		const parts = cleaned.split('.');
		if (parts.length > 2) cleaned = parts[0] + '.' + parts.slice(1).join('');
		if (cleaned.includes('.')) {
			const [int, dec] = cleaned.split('.');
			cleaned = int + '.' + dec.substring(0, 2);
		}

		serviceOverrides = { ...serviceOverrides, [serviceId]: { ...override, price: cleaned } };
	}

	function handleDurationInput(serviceId: string, value: string) {
		const override = serviceOverrides[serviceId];
		if (!override) return;

		const cleaned = value.replace(/\D/g, '');
		serviceOverrides = { ...serviceOverrides, [serviceId]: { ...override, duration: cleaned } };

		// Clear date/time and cache when duration changes
		selectedDate = undefined;
		selectedTime = null;
		availableHoursCache = {};
	}
</script>

<Modal.Root bind:open>
	<Modal.Content
		class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl md:max-w-4xl"
	>
		<Modal.Header>
			<Modal.Title>Create Admin Booking</Modal.Title>
			<Modal.Description>
				Book an appointment for a member or guest with flexible pricing and timing
			</Modal.Description>
		</Modal.Header>

		<div class="px-6 pb-4">
			<!-- Step 1: Customer Selection -->
			{#if currentStep === 1}
				<Card.Root>
					<Card.Header>
						<Card.Title>Select Customer</Card.Title>
						<Card.Description>Choose an existing member or create a guest booking</Card.Description>
					</Card.Header>
					<Card.Content class="space-y-4">
						<!-- Tabs -->
						<div class="flex gap-6 border-b border-gray-200">
							<button
								class="pb-2 text-sm font-medium transition-colors {userType === 'member'
									? 'border-b-2 border-primary text-primary'
									: 'text-gray-500 hover:text-gray-700'}"
								onclick={() => {
									userType = 'member';
									selectedUserId = null;
								}}
							>
								Member
							</button>
							<button
								class="pb-2 text-sm font-medium transition-colors {userType === 'guest'
									? 'border-b-2 border-primary text-primary'
									: 'text-gray-500 hover:text-gray-700'}"
								onclick={() => {
									userType = 'guest';
									guestName = '';
									guestPhone = '';
								}}
							>
								Guest / Non-Member
							</button>
						</div>

						{#if userType === 'member'}
							<!-- Native Input using oninput to prevent reactivity bugs -->
							<div class="flex items-center space-x-2">
								<div class="relative flex-1">
									<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
										<svg
											class="h-4 w-4 text-gray-400"
											fill="none"
											stroke="currentColor"
											viewBox="0 0 24 24"
										>
											<path
												stroke-linecap="round"
												stroke-linejoin="round"
												stroke-width="2"
												d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
											></path>
										</svg>
									</div>
									<input
										type="text"
										class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pl-9 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
										placeholder="Search by name, email or phone..."
										value={userQuery}
										oninput={(e) => {
											userQuery = e.currentTarget.value;
										}}
										onkeydown={(e) => {
											if (e.key === 'Enter') {
												e.preventDefault();
												fetchUsers();
											}
										}}
									/>
								</div>
								<Button onclick={fetchUsers} disabled={loadingUsers}>
									{loadingUsers ? '...' : 'Search'}
								</Button>
							</div>

							<!-- Compact Results List -->
							<div class="max-h-[300px] overflow-y-auto rounded-md border border-gray-200">
								{#if loadingUsers}
									<div class="space-y-2 p-2">
										{#each Array(3) as _, i (i)}
											<Skeleton class="h-10 w-full" />
										{/each}
									</div>
								{:else if users.length === 0}
									<div class="flex items-center justify-center p-8 text-sm text-gray-500">
										{userQuery
											? 'No users found. Try a different search.'
											: 'Search for a user above to get started.'}
									</div>
								{:else}
									<ul class="divide-y divide-gray-200">
										{#each users.slice(0, 4) as user (user.id)}
											<li>
												<button
													type="button"
													class="flex w-full cursor-pointer items-center justify-between px-4 py-3 text-left transition-colors hover:bg-fuchsia-50 {selectedUserId ===
													user.id
														? 'bg-fuchsia-100 font-medium'
														: ''}"
													onclick={() => (selectedUserId = user.id)}
												>
													<div>
														<div class="text-base font-medium">
															{formatUserName(
																user.fullName,
																user.previousFirstName,
																user.previousLastName
															)}
														</div>
														<div class="text-xs text-gray-500">
															{#if user.email && user.phone}
																{user.email} • {user.phone}
															{:else if user.email}
																{user.email}
															{:else if user.phone}
																{user.phone}
															{:else}
																No contact info
															{/if}
														</div>
													</div>
													{#if selectedUserId === user.id}
														<svg
															class="h-5 w-5 text-primary"
															fill="currentColor"
															viewBox="0 0 20 20"
														>
															<path
																fill-rule="evenodd"
																d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
																clip-rule="evenodd"
															></path>
														</svg>
													{/if}
												</button>
											</li>
										{/each}
									</ul>
								{/if}
							</div>
						{:else}
							<!-- Guest Form - Using Native Input -->
							<div class="space-y-4">
								<div class="space-y-2">
									<Label for="guest-name">Guest Name *</Label>
									<input
										id="guest-name"
										type="text"
										class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
										placeholder="Jane Doe"
										value={guestName}
										oninput={(e) => (guestName = e.currentTarget.value)}
									/>
								</div>
								<div class="space-y-2">
									<Label for="guest-phone">Phone Number *</Label>
									<PhoneInput
										id="guest-phone"
										bind:value={guestPhone}
										bind:error={guestPhoneError}
										placeholder="07700 900000"
									/>
								</div>
								<p class="rounded-lg bg-yellow-50 p-3 text-sm text-yellow-800">
									Booking as a guest creates a temporary record. Encourage them to sign up for
									loyalty benefits.
								</p>
							</div>
						{/if}
					</Card.Content>
					<Card.Footer class="flex justify-end">
						<BookingActions
							canBack={false}
							canNext={canProceedStep1}
							nextLabel="Next: Choose Services"
							on:next={() => currentStep++}
						/>
					</Card.Footer>
				</Card.Root>
			{/if}

			<!-- Step 2: Service Selection -->
			{#if currentStep === 2}
				<Card.Root>
					<Card.Header>
						<Card.Title>Choose Services</Card.Title>
						<Card.Description>Select one or more treatments for this appointment</Card.Description>
					</Card.Header>
					<Card.Content class="space-y-4">
						{#if loadingServices}
							<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
								{#each Array(4) as _, i (i)}
									<Skeleton class="h-28 w-full" />
								{/each}
							</div>
						{:else if services.length === 0}
							<p class="py-8 text-center text-gray-500">No services available.</p>
						{:else}
							<ServiceSelector
								{services}
								selected={selectedServices}
								loading={loadingServices}
								ontoggle={toggleService}
								showContactLink={false}
							/>
						{/if}

						<Separator />
						<div class="space-y-3">
							<h4 class="text-sm font-medium text-gray-600">Or book a custom service</h4>
							<div class={showCustomCreateForm ? 'hidden' : ''}>
								<div class="flex gap-2">
									<Input
										placeholder="Search existing custom services..."
										bind:value={customSearchQuery}
										onkeydown={(e) => {
											if (e.key === 'Enter') fetchCustomServices();
										}}
										class="flex-1"
									/>
									<Button variant="outline" size="sm" onclick={fetchCustomServices}>Search</Button>
								</div>
								{#if loadingCustomServices}
									<div class="space-y-2">
										{#each Array(3) as _, i (i)}
											<Skeleton class="h-10 w-full" />
										{/each}
									</div>
								{:else if customServices.length > 0}
									<div class="space-y-2">
										{#each customServices as cs (cs.id)}
											<button
												class="flex w-full items-center justify-between rounded-lg border px-3 py-2 text-sm hover:bg-gray-50"
												onclick={() => {
													if (!selectedServices.some((s) => s.id === cs.id)) {
														selectedServices = [...selectedServices, cs];
														serviceOverrides = {
															...serviceOverrides,
															[cs.id]: {
																price: cs.price.toFixed(2),
																duration: cs.duration_minutes.toString(),
																originalPrice: cs.price,
																originalDuration: cs.duration_minutes
															}
														};
														toast.success(`Added "${cs.name}"`);
													}
												}}
											>
												<span class="font-medium">{cs.name}</span>
												<span class="text-gray-500"
													>{cs.duration_minutes} min • £{cs.price.toFixed(2)}{cs.usage_count > 0
														? ` (${cs.usage_count}×)`
														: ''}</span
												>
											</button>
										{/each}
									</div>
								{/if}
								<Button
									variant="ghost"
									size="sm"
									onclick={() => {
										showCustomCreateForm = true;
									}}
									class="w-full"
								>
									+ Create new custom service
								</Button>
							</div>
							<div class={showCustomCreateForm ? '' : 'hidden'}>
								<div class="space-y-3 rounded-lg border p-4">
									<div class="space-y-1">
										<label for="booking-cs-name" class="text-sm font-medium">Name *</label>
										<Input
											id="booking-cs-name"
											bind:value={newCustomService.name}
											oninput={() =>
												(customServiceErrors.name = validateCsName(newCustomService.name))}
											onblur={() =>
												(customServiceErrors.name = validateCsName(newCustomService.name))}
											placeholder="e.g., Bridal Party French Tips"
											class={customServiceErrors.name ? 'border-red-500' : ''}
										/>
										{#if customServiceErrors.name}
											<p class="text-xs text-red-600">{customServiceErrors.name}</p>
										{/if}
									</div>
									<div class="space-y-1">
										<label for="booking-cs-desc" class="text-sm font-medium">Description</label>
										<Input
											id="booking-cs-desc"
											bind:value={newCustomService.description}
											placeholder="Brief description"
											class="w-full"
										/>
									</div>
									<div class="grid grid-cols-2 gap-2">
										<div class="space-y-1">
											<label for="booking-cs-price" class="text-sm font-medium">Price (£) *</label>
											<Input
												id="booking-cs-price"
												type="number"
												step="0.01"
												min="0"
												bind:value={newCustomService.price}
												oninput={() =>
													(customServiceErrors.price = validateCsPrice(newCustomService.price))}
												onblur={() =>
													(customServiceErrors.price = validateCsPrice(newCustomService.price))}
												placeholder="0.00"
												class={customServiceErrors.price ? 'border-red-500' : ''}
											/>
											{#if customServiceErrors.price}
												<p class="text-xs text-red-600">{customServiceErrors.price}</p>
											{/if}
										</div>
										<div class="space-y-1">
											<label for="booking-cs-dur" class="text-sm font-medium">Duration *</label>
											<select
												id="booking-cs-dur"
												bind:value={newCustomService.duration_minutes}
												onchange={() =>
													(customServiceErrors.duration_minutes = validateCsDuration(
														newCustomService.duration_minutes
													))}
												class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-xs ring-offset-background transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 {customServiceErrors.duration_minutes
													? 'border-red-500'
													: ''}"
											>
												<option value="">Select...</option>
												{#each durationOptions as mins (mins)}
													<option value={mins}
														>{mins} min{mins >= 60
															? ` (${Math.floor(mins / 60)}h${mins % 60 > 0 ? ` ${mins % 60}m` : ''})`
															: ''}</option
													>
												{/each}
											</select>
											{#if customServiceErrors.duration_minutes}
												<p class="text-xs text-red-600">{customServiceErrors.duration_minutes}</p>
											{/if}
										</div>
									</div>
									<div class="space-y-1">
										<label for="booking-cs-age" class="text-sm font-medium">Minimum Age</label>
										<Input
											id="booking-cs-age"
											type="number"
											inputmode="numeric"
											min="0"
											max="100"
											placeholder="0"
											bind:value={newCustomService.minimum_age_required}
											oninput={() =>
												(customServiceErrors.minimum_age_required = validateCsMinimumAge(
													newCustomService.minimum_age_required
												))}
											class="w-full {customServiceErrors.minimum_age_required
												? 'border-red-500'
												: ''}"
										/>
										{#if customServiceErrors.minimum_age_required}
											<p class="text-xs text-red-600">{customServiceErrors.minimum_age_required}</p>
										{/if}
										<p class="text-xs text-gray-500">0 for no age restriction</p>
									</div>
									<div class="flex gap-2">
										<Button
											size="sm"
											onclick={createCustomService}
											disabled={creatingCustomService || !isCustomFormValid}
										>
											{creatingCustomService ? 'Creating...' : 'Save & Add'}
										</Button>
										<Button
											variant="outline"
											size="sm"
											onclick={() => {
												showCustomCreateForm = false;
												newCustomService = {
													name: '',
													description: '',
													price: '',
													duration_minutes: '',
													minimum_age_required: ''
												};
												customServiceErrors = {
													name: '',
													price: '',
													duration_minutes: '',
													minimum_age_required: ''
												};
											}}
										>
											Cancel
										</Button>
									</div>
								</div>
							</div>
						</div>

						{#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-between">
						<Button variant="outline" onclick={() => currentStep--}>Back</Button>
						<Button disabled={!canProceedStep2} onclick={() => currentStep++}>
							Next: Customize Services
						</Button>
					</Card.Footer>
				</Card.Root>
			{/if}

			<!-- Step 3: Service Overrides & Notes -->
			{#if currentStep === 3}
				<Card.Root>
					<Card.Header>
						<Card.Title>Customize Services</Card.Title>
						<Card.Description>
							Adjust pricing or duration if needed, and add appointment notes
						</Card.Description>
					</Card.Header>
					<Card.Content class="space-y-6">
						<div>
							<h4 class="mb-3 font-semibold">Service Details</h4>
							<p class="mb-4 text-sm text-gray-600">
								Override default pricing or duration for special cases (discounts, extended
								sessions, etc.)
							</p>
							<div class="space-y-3">
								{#each selectedServices as service (service.id)}
									<!-- Safety check to ensure override exists -->
									{#if serviceOverrides[service.id]}
										<div class="rounded-lg border bg-white p-4">
											<div class="mb-3 font-medium">{service.name}</div>
											<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
												<div class="space-y-2">
													<Label for="price-{service.id}" class="text-xs text-gray-600"
														>Price (£)</Label
													>
													<!-- Native Input with oninput -->
													<input
														id="price-{service.id}"
														type="text"
														inputmode="decimal"
														class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
														value={serviceOverrides[service.id]?.price || service.price.toFixed(2)}
														oninput={(e) => handlePriceInput(service.id, e.currentTarget.value)}
													/>
												</div>
												<div class="space-y-2">
													<Label for="duration-{service.id}" class="text-xs text-gray-600"
														>Duration (min)</Label
													>
													<!-- Native Input with oninput -->
													<input
														id="duration-{service.id}"
														type="number"
														class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none"
														value={serviceOverrides[service.id]?.duration ||
															service.duration_minutes}
														oninput={(e) => handleDurationInput(service.id, e.currentTarget.value)}
													/>
												</div>
											</div>
											{#if serviceOverrides[service.id] && (Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01 || parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration)}
												<div class="mt-2 text-xs text-amber-600">
													{#if Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01}
														Price modified from £{serviceOverrides[
															service.id
														].originalPrice.toFixed(2)}
													{/if}

													{#if Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01 && parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration}
														•
													{/if}

													{#if parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration}
														Duration modified from {serviceOverrides[service.id].originalDuration} mins
													{/if}
												</div>
											{/if}
										</div>
									{/if}
								{/each}
							</div>
						</div>

						<div class="rounded-lg bg-gray-50 p-4">
							<div class="flex justify-between text-sm font-semibold">
								<span>Total Duration:</span>
								<span>{formattedTotalDuration}</span>
							</div>
							<div class="mt-1 flex justify-between text-sm font-semibold">
								<span>Total Cost:</span>
								<span>£{getTotalPrice().toFixed(2)}</span>
							</div>
						</div>

						<div class="space-y-2">
							<Label for="notes">Appointment Notes (extras only, client will see this)</Label>
							<!-- Native Textarea -->
							<textarea
								id="notes"
								class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
								bind:value={notes}
								placeholder="Any special requirements, preferences, or notes about this booking..."
							></textarea>
							<CharCounter text={notes} />
						</div>
					</Card.Content>
					<Card.Footer class="flex justify-between">
						<Button variant="outline" onclick={() => currentStep--}>Back</Button>
						<Button onclick={() => currentStep++}>Next: Select Date & Time</Button>
					</Card.Footer>
				</Card.Root>
			{/if}

			<!-- Step 4: Date & Time Selection -->
			{#if currentStep === 4}
				<Card.Root>
					<Card.Header>
						<Card.Title>Choose Date & Time</Card.Title>
						<Card.Description>
							{selectedServices.map((s) => s.name).join(', ')} • {formattedTotalDuration} total • £{getTotalPrice().toFixed(
								2
							)}
						</Card.Description>
					</Card.Header>
					<Card.Content class="space-y-4 p-0">
						<!-- Reservation Countdown Banner -->
						{#if reservationId && reservationExpiresAt}
							<div class="mx-6 mt-4 rounded-lg border border-green-200 bg-green-50 p-3">
								<div class="flex items-center justify-between">
									<div class="flex items-center gap-2">
										<svg
											class="h-5 w-5 text-green-600"
											fill="none"
											stroke="currentColor"
											viewBox="0 0 24 24"
										>
											<path
												stroke-linecap="round"
												stroke-linejoin="round"
												stroke-width="2"
												d="M5 13l4 4L19 7"
											></path>
										</svg>
										<span class="text-sm font-medium text-green-800">
											Slot reserved until {parseWallClockDate(
												reservationExpiresAt.toISOString()
											).toLocaleTimeString([], {
												hour: '2-digit',
												minute: '2-digit'
											})}
										</span>
									</div>
									<span class="font-mono text-sm font-semibold text-green-700">
										({reservationCountdown} remaining)
									</span>
								</div>
							</div>
						{/if}
						<div class="flex items-center justify-center p-6">
							<DatePicker
								date={selectedDate}
								{placeholder}
								minValue={minDate}
								maxValue={maxCalendarDate}
								{isDateUnavailable}
								onchange={(newDate) => {
									selectedDate = newDate;
									selectedTime = null;
								}}
								onPlaceholderChange={(newPlaceholder) => {
									placeholder = newPlaceholder;
									userNavigatedCalendar = true;
									fetchHoursForMonth(newPlaceholder);
								}}
							/>
						</div>

						<!-- Out-of-hours toggle -->
						<div class="mx-6 flex items-center gap-2">
							<Checkbox id="out-of-hours" bind:checked={outOfHours} />
							<Label for="out-of-hours" class="cursor-pointer text-sm font-medium text-amber-600">
								Out-of-hours booking
							</Label>
						</div>

						{#if loadingAvailableHours && selectedDate}
							<div class="flex items-center justify-center border-t p-6">
								<p class="text-sm text-gray-500">Loading times...</p>
							</div>
						{:else if selectedDate}
							<div class="border-t">
								<div class="max-h-64 overflow-y-auto p-6">
									{#if formattedSelectedDate}
										<div class="mb-3 grid justify-center gap-2 text-sm font-medium">
											{formattedSelectedDate}
										</div>
									{/if}
									<TimeSlotList
										slots={groupedTimeSlots}
										{selectedTime}
										duration={getTotalDuration()}
										protection={lunchProtection}
										onSelect={(time) => {
											selectedTime = time;
										}}
									/>
								</div>
							</div>
							{#if selectedDate && selectedTime}
								<div class="px-6 pb-4">
									<SelectedTimeSummary
										selectedDate={formattedSelectedDate || ''}
										selectedTime={formatTime(selectedTime)}
										endTime={formatTime(calculateEndTime(selectedTime, getTotalDuration()))}
										duration={getTotalDuration()}
										protection={lunchProtection.get(selectedTime)}
										outOfHours={selectedTimeOutOfHours}
									/>
								</div>
							{/if}
						{:else}
							<div class="flex items-center justify-center border-t p-6">
								<p class="text-center text-sm text-gray-500">
									Select a date to see available times
								</p>
							</div>
						{/if}
					</Card.Content>

					<Card.Footer class="flex justify-between">
						<Button variant="outline" onclick={() => currentStep--}>Back</Button>
						<Button
							disabled={!canProceedStep4 || submitting || isReserving}
							onclick={submitBooking}
							class="bg-primary text-primary-foreground"
						>
							{isReserving
								? 'Reserving Slot...'
								: submitting
									? 'Creating Booking...'
									: 'Create Booking'}
						</Button>
					</Card.Footer>
				</Card.Root>
			{/if}
		</div>
	</Modal.Content>
</Modal.Root>
