<script lang="ts">
	import PolicyPopover from '$lib/components/ui/policyPopover.svelte';
	import { POLICY } from '$lib/constants/policy';
	import { authStore } from '$lib/stores/auth.svelte';
	import { apiFetch } from '$lib/utils/api';
	import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
	import { SvelteDate } from 'svelte/reactivity';
	import { toast } from 'svelte-sonner';
	import { extractErrorMessage } from '$lib/utils/toast-safe';
	import * as Modal from '$lib/components/ui/dialog';
	import { Button } from '$lib/components/ui/button';
	import { Input } from '$lib/components/ui/input';
	import UserPaymentModal from '$lib/components/payments/UserPaymentModal.svelte';
	import EditRequestModal from '$lib/components/account/EditRequestModal.svelte';
	import { computeBalanceDue } from '$lib/utils/booking';
	import { parseWallClockDate } from '$lib/utils/timeSlots';
	import type { Booking, BookingDiscount, Payment } from '$lib/types/booking';
	import CardInput from '$lib/components/payments/CardInput.svelte';
import CardBrandIcon from '$lib/components/payments/CardBrandIcon.svelte';
	import { savedCardsStore, type SavedCard } from '$lib/stores/savedCards.svelte';
	interface Props {
		open: boolean;
		bookingId: string;
	}

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

	let selectedBooking = $state<Booking | null>(null);
	const businessSettings = $derived(getBusinessInfo());
	let loading = $state(false);
	let hasPendingEditRequest = $state(false);
	let pendingEditRequest = $state<{
		id: string;
		notes: string | null;
		requested_at: string;
		original: {
			start_time: string | null;
			services: Array<{ name: string; price: number; duration_minutes: number }>;
		};
		proposed: {
			start_time: string | null;
			services: Array<{ name: string; price: number; duration_minutes: number }>;
		};
	} | null>(null);

	let showEditModal = $state(false);
	let showCancelConfirm = $state(false);
	let cancelling = $state(false);

	// IMPORTANT: Use override_duration_minutes when present — services may have been
	// customised at booking time. Showing base values misleads users about what was booked.
	const totalDuration = $derived(
		selectedBooking?.services?.reduce(
			(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
			0
		) || 0
	);

	const isFutureBooking = $derived(
		selectedBooking ? new SvelteDate(selectedBooking.start_time) > new SvelteDate() : false
	);

	const hasPayments = $derived(
		selectedBooking && selectedBooking.payments && selectedBooking.payments.length > 0
	);

	const isCancellable = $derived(
		selectedBooking && isFutureBooking && ['pending', 'confirmed'].includes(selectedBooking.status)
	);

	const canEditBooking = $derived(isCancellable);

	const totalPaid = $derived(
		selectedBooking?.payments
			?.filter((p) => p.status === 'completed')
			.reduce((sum, p) => sum + p.amount, 0) || 0
	);

	const totalRefunds = $derived(
		(selectedBooking?.refunds ?? [])
			.filter((r) => r.status === 'completed')
			.reduce((sum, r) => sum + r.amount, 0)
	);

	const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);

	const totalVAT = $derived(
		selectedBooking?.payments
			?.filter((p) => p.status === 'completed' && p.vat_amount)
			.reduce((sum, p) => sum + (p.vat_amount ?? 0), 0) || 0
	);

	const totalNet = $derived(
		selectedBooking?.payments
			?.filter((p) => p.status === 'completed' && p.net_amount)
			.reduce((sum, p) => sum + (p.net_amount ?? 0), 0) || 0
	);

	const hasVAT = $derived(totalVAT > 0);

	const depositOutstanding = $derived(
		selectedBooking?.deposit_required && !selectedBooking?.deposit_paid
	);

	const canPayEarly = $derived(
		selectedBooking &&
			!depositOutstanding &&
			totalPaid < selectedBooking.total_amount &&
			['confirmed', 'in_progress'].includes(selectedBooking.status)
	);

	const isCompleted = $derived(selectedBooking?.status === 'completed');

	const hoursUntilAppointment = $derived(
		selectedBooking
			? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
					(1000 * 60 * 60)
			: Infinity
	);
	const protectedDeposit = $derived(
		selectedBooking
			? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
			: 0
	);
	const estimatedRefund = $derived(
		hoursUntilAppointment > POLICY.FULL_REFUND_THRESHOLD_HOURS
			? totalPaid
			: hoursUntilAppointment >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS
				? Math.max(0, totalPaid - protectedDeposit)
				: 0
	);

	let showPaymentModal = $state(false);

	let showTipModal = $state(false);
	let tipAmount = $state<number>(0);
	let selectedTipPreset = $state<number | null>(null);
	let customTipInput = $state('');
	let tipProcessing = $state(false);

	// Card selection state for tips
	let tipSavedCards = $state<SavedCard[]>([]);
	let tipLoadingCards = $state(false);
	let tipSelectedCardId = $state<string | null>(null);
	let tipShowNewCard = $state(false);

	// New card form state for tips
	let tipNewCardNumber = $state('');
	let tipNewCardExpiry = $state('');
	let tipNewCardCVC = $state('');
	let tipSaveCardFuture = $state(false);
	let tipCardNumberTouched = $state(false);
	let tipCardExpiryTouched = $state(false);
	let tipCVCTouched = $state(false);

	const canSaveCards = $derived(
		authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
	);

	// Card validation (matching UserPaymentModal pattern)
	function isValidLuhn(cardNumber: string): boolean {
		const s = cardNumber.replace(/\D/g, '');
		let sum = 0;
		let alternate = false;
		for (let i = s.length - 1; i >= 0; i--) {
			let n = parseInt(s[i], 10);
			if (alternate) {
				n *= 2;
				if (n > 9) n -= 9;
			}
			sum += n;
			alternate = !alternate;
		}
		return sum % 10 === 0 && s.length >= 13 && s.length <= 19;
	}

	function handleTipFieldBlur(field: string) {
		if (field === 'cardNumber') tipCardNumberTouched = true;
		else if (field === 'cardExpiry') tipCardExpiryTouched = true;
		else if (field === 'cardCVC') tipCVCTouched = true;
	}

	function handleTipFieldInput(field: string) {
		if (field === 'cardNumber') tipCardNumberTouched = false;
		else if (field === 'cardExpiry') tipCardExpiryTouched = false;
		else if (field === 'cardCVC') tipCVCTouched = false;
	}

	function parseExpiryParts(value: string): { month: number; year: number } | null {
		if (!/^\d{2}\/\d{2}$/.test(value)) return null;
		const [monthStr, yearStr] = value.split('/');
		const month = parseInt(monthStr, 10);
		const year = 2000 + parseInt(yearStr, 10);
		if (month < 1 || month > 12) return null;
		return { month, year };
	}

	const tipNewCardExpiryParts = $derived(parseExpiryParts(tipNewCardExpiry));
	const isTipNewCardExpiryPast = $derived(
		tipNewCardExpiryParts !== null &&
			(() => {
				const expiryDate = new SvelteDate(tipNewCardExpiryParts.year, tipNewCardExpiryParts.month);
				return expiryDate < new SvelteDate();
			})()
	);
	const hasTipNewCardInvalidMonth = $derived(/^\d{2}\/\d{2}$/.test(tipNewCardExpiry) && tipNewCardExpiryParts === null);

	const tipNewCardError = $derived(
		tipShowNewCard || tipSavedCards.length === 0
			? tipCardNumberTouched && !isValidLuhn(tipNewCardNumber) && tipNewCardNumber.length > 0
				? 'Invalid card number'
				: tipCardExpiryTouched && hasTipNewCardInvalidMonth
					? 'Invalid expiry month'
					: tipCardExpiryTouched && isTipNewCardExpiryPast
						? 'This card has expired'
						: tipCardExpiryTouched && tipNewCardExpiry.length > 0 && !/^\d{2}\/\d{2}$/.test(tipNewCardExpiry)
							? 'Enter expiry as MM/YY'
							: tipCVCTouched && tipNewCardCVC.length < 3 && tipNewCardCVC.length > 0
								? 'Enter your CVC number'
								: isValidLuhn(tipNewCardNumber) && /^\d{2}\/\d{2}$/.test(tipNewCardExpiry) && tipNewCardCVC.length >= 3
									? null
									: tipNewCardNumber.length === 0 && tipNewCardExpiry.length === 0 && tipNewCardCVC.length === 0
										? null
										: 'Please complete all card fields'
			: null
	);

	const isTipCardValid = $derived(
		tipSelectedCardId !== null ||
			(isValidLuhn(tipNewCardNumber) &&
				tipNewCardExpiryParts !== null &&
				!isTipNewCardExpiryPast &&
				tipNewCardCVC.length >= 3)
	);

	const tipPresets = $derived(
		selectedBooking
			? [
					{ pct: 10, amount: Math.round(selectedBooking.total_amount * 0.1 * 100) / 100 },
					{ pct: 15, amount: Math.round(selectedBooking.total_amount * 0.15 * 100) / 100 },
					{ pct: 20, amount: Math.round(selectedBooking.total_amount * 0.2 * 100) / 100 }
				]
			: []
	);

	function selectTipPreset(amount: number) {
		selectedTipPreset = amount;
		customTipInput = '';
		tipAmount = amount;
	}

	function handleCustomTip(e: Event) {
		const input = e.target as HTMLInputElement;
		const cleaned = input.value.replace(/[^0-9.]/g, '');
		const firstDot = cleaned.indexOf('.');
		let sanitized: string;
		if (firstDot !== -1) {
			const integerPart = cleaned.substring(0, firstDot);
			const decimalPart = cleaned.substring(firstDot + 1).replace(/\./g, '');
			sanitized = integerPart + '.' + decimalPart;
		} else {
			sanitized = cleaned;
		}
		if (/^\d+(\.\d{0,2})?$/.test(sanitized) || sanitized === '') {
			customTipInput = sanitized;
			selectedTipPreset = null;
			tipAmount = parseFloat(sanitized) || 0;
		}
	}

	async function loadTipSavedCards() {
		if (savedCardsStore.loaded) {
			tipSavedCards = savedCardsStore.cards;
			if (tipSavedCards.length > 0 && !tipSelectedCardId) {
				tipSelectedCardId = tipSavedCards.find((c) => c.is_default)?.id || tipSavedCards[0].id;
			}
			return;
		}
		tipLoadingCards = true;
		try {
			await savedCardsStore.fetch();
			tipSavedCards = savedCardsStore.cards;
			if (tipSavedCards.length > 0 && !tipSelectedCardId) {
				tipSelectedCardId = tipSavedCards.find((c) => c.is_default)?.id || tipSavedCards[0].id;
			}
		} catch {
			// ignore
		} finally {
			tipLoadingCards = false;
		}
	}

	async function submitTip() {
		if (!selectedBooking) return;
		if (tipAmount <= 0) {
			toast.error('Please select a tip amount');
			return;
		}

		if (tipSavedCards.length > 0 && !tipSelectedCardId && !tipShowNewCard) {
			toast.error('Please select a payment method');
			return;
		}
		if ((tipShowNewCard || tipSavedCards.length === 0) && !tipNewCardNumber.replace(/\s/g, '')) {
			toast.error('Please enter your card number');
			return;
		}

		tipProcessing = true;

		// Validate card details for new card payments
		if (tipShowNewCard || tipSavedCards.length === 0) {
			if (!isValidLuhn(tipNewCardNumber) || !/^\d{2}\/\d{2}$/.test(tipNewCardExpiry) || isTipNewCardExpiryPast || tipNewCardCVC.length < 3) {
				tipProcessing = false;
				toast.error(tipNewCardError || 'Please enter valid credit card details');
				return;
			}
		}

		try {
			const body: Record<string, unknown> = { amount: Math.round(tipAmount * 100) };

			if (tipShowNewCard || tipSavedCards.length === 0) {
				body.new_card_token = tipNewCardNumber.replace(/\s/g, '');
				body.card_expiry = tipNewCardExpiry;
				body.card_cvc = tipNewCardCVC;
				body.save_card = tipSaveCardFuture;
			} else {
				body.card_id = tipSelectedCardId;
			}

			const response = await apiFetch(`/api/bookings/${selectedBooking.id}/tip`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify(body)
			});
			if (!response.ok) {
				const errorText = await response.text();
				throw new Error(errorText || 'Tip payment failed');
			}
			toast.success('Thank you for your tip!');
			showTipModal = false;
			fetchBookingDetails();
		} catch (err) {
			toast.error(err instanceof Error ? err.message : 'Tip payment failed');
		} finally {
			tipProcessing = false;
		}
	}

	function handlePaymentComplete() {
		toast.success('Payment completed');
		showPaymentModal = false;
		fetchBookingDetails();
	}

	async function fetchBookingDetails() {
		if (!bookingId) return;

		try {
			const bookingResp = await apiFetch(`/api/bookings/${bookingId}`, {
				method: 'GET',
				headers: { 'Content-Type': 'application/json' }
			});

			if (bookingResp.ok) {
				const data = await bookingResp.json();
				selectedBooking = data as Booking;

				// Ensure business info is loaded (cached by shared store)
				ensureBusinessInfo();

				const editResp = await apiFetch(`/api/bookings/${bookingId}/edit-request`);
				const editData = await editResp.json();
				hasPendingEditRequest = editData.edit_request != null;
				pendingEditRequest = editData.edit_request || null;
			} else {
				const text = await bookingResp.text();
				toast.error('Failed to load booking: ' + extractErrorMessage(text));
				open = false;
			}
		} catch (err) {
			console.error('Error fetching booking:', err);
			toast.error('Network error');
			open = false;
		} finally {
			loading = false;
		}
	}

	$effect(() => {
		if (!open) {
			setTimeout(() => {
				selectedBooking = null;
				hasPendingEditRequest = false;
				pendingEditRequest = null;
				showCancelConfirm = false;
				showEditModal = false;
			}, 200);
		} else if (bookingId && !selectedBooking) {
			fetchBookingDetails();
		}
	});

	$effect(() => {
		if (showTipModal) {
			loadTipSavedCards();
		}
	});

	function printReceipt() {
		if (!selectedBooking) {
			toast.error('No booking data to print');
			return;
		}
		const pw = window.open('', '_blank');
		if (!pw) {
			toast.error('Please allow pop-ups to print the receipt');
			return;
		}

		const biz = businessSettings;
		const paidPayments =
			selectedBooking.payments?.filter(
				(p) => p.status === 'completed' && p.payment_method !== 'discount'
			) ?? [];
		const discountPayments =
			selectedBooking.payments?.filter(
				(p) => p.payment_method === 'discount' && p.status === 'completed'
			) ?? [];
		const refunds = selectedBooking.refunds?.filter((r) => r.status === 'completed') ?? [];
		const grossTotal = paidPayments.reduce((s, p) => s + p.amount, 0);

		const esc = (str: string | null | undefined): string => {
			if (!str) return '';
			return String(str)
				.replace(/&/g, '&amp;')
				.replace(/</g, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/"/g, '&quot;');
		};

		const fmt = (val: number | null | undefined, fallback = '\u2014'): string => {
			return val != null ? '\u00a3' + val.toFixed(2) : fallback;
		};

		pw.document
			.write(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Receipt</title>
<style>
  @page { margin: 12mm; }
  * { color: #000 !important; background: transparent !important; }
  body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; line-height: 1.5; max-width: 700px; margin: 0 auto; padding: 20px; }
  h1 { font-size: 18px; margin: 0 0 2px 0; }
  .header { border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 14px; }
  .header p { margin: 1px 0; font-size: 11px; }
  table { width: 100%; border-collapse: collapse; margin: 10px 0; }
  th, td { padding: 5px 8px; text-align: left; border-bottom: 1px solid #000; font-size: 11px; }
  th { font-weight: 600; }
  .total td { font-weight: 700; border-top: 2px solid #000; }
  .warning { font-size: 10px; margin-top: 4px; }
  .footer { margin-top: 20px; padding-top: 10px; border-top: 1px solid #000; font-size: 10px; text-align: center; }
  @media print { body { padding: 0; } }
</style></head><body>
<div class="header">
  <h1>${esc(biz?.business_name ?? 'Crussell Nail Art Studio')}</h1>
  <p>${esc(biz?.business_address ?? '')}</p>
  ${biz?.is_vat_registered && biz?.vat_registration_number ? `<p>VAT Reg: ${esc(biz.vat_registration_number)}</p>` : ''}
  ${biz?.business_phone ? `<p>Tel: ${esc(biz.business_phone)}</p>` : ''}
  ${biz?.business_email ? `<p>Email: ${esc(biz.business_email)}</p>` : ''}
</div>
<h2>Receipt</h2>
<table>
  <tr><td style="width:110px;font-weight:600">Booking Ref</td><td>${esc(selectedBooking.id)}</td></tr>
  <tr><td style="font-weight:600">Date</td><td>${parseWallClockDate(selectedBooking.start_time).toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}</td></tr>
  <tr><td style="font-weight:600">Status</td><td style="text-transform:capitalize">${selectedBooking.status.replace('_', ' ')}</td></tr>
</table>
<h2>Services</h2>
<table>
  <tr><th>Service</th><th style="text-align:right">Price</th></tr>
  ${(selectedBooking.services ?? []).map((s) => `<tr><td>${esc(s.service_name)}${s.duration_minutes ? ' (' + s.duration_minutes + ' min)' : ''}</td><td style="text-align:right">\u00a3${(s.price ?? 0).toFixed(2)}</td></tr>`).join('')}
</table>
<h2>Payments</h2>
<table>
  <tr><th>Type</th><th>Method</th><th style="text-align:right">Net</th><th style="text-align:right">VAT</th><th style="text-align:right">Gross</th></tr>
  ${paidPayments.map((p) => `<tr><td style="text-transform:capitalize">${p.payment_type}</td><td style="text-transform:capitalize">${p.payment_method ?? '\u2014'}</td><td style="text-align:right">${fmt(p.net_amount)}</td><td style="text-align:right">${p.is_vat_applicable && p.vat_amount != null ? fmt(p.vat_amount) : '\u2014'}</td><td style="text-align:right">\u00a3${p.amount.toFixed(2)}</td></tr>`).join('')}
  ${discountPayments.map((d) => `<tr><td>Discount</td><td>\u2014</td><td style="text-align:right;color:#059669">-\u00a3${Math.abs(d.amount).toFixed(2)}</td><td style="text-align:right;color:#059669">\u2014</td><td style="text-align:right;color:#059669">\u2014</td></tr>`).join('')}
  ${refunds.map((r) => `<tr><td>Refund</td><td>\u2014</td><td style="text-align:right;color:#dc2626">-\u00a3${r.amount.toFixed(2)}</td><td style="text-align:right;color:#dc2626">\u2014</td><td style="text-align:right;color:#dc2626">\u2014</td></tr>`).join('')}
  ${hasVAT ? `<tr class="total"><td colspan="2"></td><td style="text-align:right">\u00a3${totalNet.toFixed(2)}</td><td style="text-align:right">\u00a3${totalVAT.toFixed(2)}</td><td style="text-align:right">\u00a3${(totalNet + totalVAT).toFixed(2)}</td></tr>` : ''}
  <tr class="total"><td colspan="4" style="text-align:right">Total Paid</td><td style="text-align:right">\u00a3${grossTotal.toFixed(2)}</td></tr>
</table>
${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}% where shown above.</p>` : '<p class="warning">VAT is not applicable for this transaction.</p>'}
<div class="footer"><p>Thank you for visiting ${esc(biz?.business_name ?? 'Crussell')}.</p></div>
</body></html>`);
		pw.document.close();
		pw.onload = () => pw.print();
	}

	async function cancelBooking() {
		if (!selectedBooking) return;
		cancelling = true;
		try {
			const body = hasPayments ? { reason: 'client_cancelled' } : undefined;
			const response = await apiFetch(`/api/bookings/${selectedBooking.id}`, {
				method: 'DELETE',
				headers: { 'Content-Type': 'application/json' },
				body: body ? JSON.stringify(body) : undefined
			});

			if (response.ok) {
				toast.success('Booking cancelled');
				showCancelConfirm = false;
				open = false;
			} else {
				const text = await response.text();
				toast.error('Failed to cancel: ' + extractErrorMessage(text));
			}
		} catch {
			toast.error('Network error');
		} finally {
			cancelling = false;
		}
	}

	function formatPaymentMethod(method: string): string {
		switch (method) {
			case 'in_person_card':
				return 'Card, In-person';
			case 'online_square':
				return 'Card, Online';
			case 'cash':
				return 'Cash';
			case 'giftcard':
				return 'Gift Card';
			case 'discount':
				return 'Discount';
			default:
				return method.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
		}
	}

	function getPaymentName(
		payment: Payment,
		index: number,
		payments: Payment[] | undefined,
		discounts: BookingDiscount[] | undefined
	): string {
		if (payment.payment_method === 'online_square') return 'Online Card';
		if (payment.payment_method === 'in_person_card') return 'Card Machine';
		if (payment.payment_method === 'cash') return 'Cash';
		if (payment.payment_method === 'giftcard') return 'Gift Card';
		if (payment.payment_method === 'discount') {
			const discountPaymentsBefore = (payments ?? [])
				.slice(0, index)
				.filter((p) => p.payment_method === 'discount').length;
			const discountList = (discounts ?? []).filter((d) => d.discount_amount > 0.01);
			if (discountList[discountPaymentsBefore]) {
				const d = discountList[discountPaymentsBefore];
				if (d.discount_source === 'loyalty') return 'Loyalty Stamp Card (10% Off)';
				if (d.campaign_name) return `${d.campaign_name}`;
				return 'Promo Campaign Discount';
			}
			return 'Discount';
		}
		return formatPaymentMethod(payment.payment_method);
	}
</script>

<Modal.Root bind:open>
	<Modal.Content
		class="!z-[60] max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-md md:max-w-3xl"
	>
		<Modal.Header>
			<div class="flex items-center justify-between">
				<div>
					<Modal.Title class="text-lg font-semibold">Booking Details</Modal.Title>
					{#if selectedBooking}
						<div class="mt-1 text-sm text-gray-500">ID: {selectedBooking.id}</div>
					{/if}
				</div>

				{#if selectedBooking}
					{@const isPastBooking = new SvelteDate(selectedBooking.start_time) < new SvelteDate()}
					{@const isUnpaid = selectedBooking.amount_due > 0}
					{@const showChip = !isPastBooking || isUnpaid}
					{@const isConfirmedOrLater = ['confirmed', 'in_progress', 'completed'].includes(
						selectedBooking.status
					)}

					{#if showChip}
						<div class="flex items-center gap-2">
							<span
								class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium
								{isPastBooking
									? 'bg-red-100 text-red-800'
									: selectedBooking.status === 'confirmed' || selectedBooking.status === 'completed'
										? 'bg-emerald-100 text-emerald-800'
										: selectedBooking.status === 'pending'
											? 'bg-amber-100 text-amber-800'
											: 'bg-gray-100 text-gray-800'}"
							>
								{isPastBooking ? 'Unpaid' : selectedBooking.status.replace('_', ' ')}
							</span>

							{#if selectedBooking.deposit_required}
								{#if selectedBooking.status === 'pending'}
									<span
										class="inline-flex items-center rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800"
									>
										Will Require Deposit
									</span>
								{:else if isConfirmedOrLater}
									<span
										class="inline-flex items-center rounded-full px-3 py-1 text-sm font-medium
										{selectedBooking.deposit_paid ? 'bg-green-100 text-green-800' : 'bg-orange-100 text-orange-800'}"
									>
										{selectedBooking.deposit_paid ? 'Deposit Paid' : 'Deposit Due'}
									</span>
								{/if}
							{/if}
						</div>
					{/if}
				{/if}
			</div>
		</Modal.Header>

		{#if loading}
			<div class="flex items-center justify-center p-8 text-gray-500">Loading...</div>
		{:else if selectedBooking}
			<div class="space-y-6 px-4 pb-4">
				<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
					<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
						Appointment Details
					</h3>
					{#if selectedBooking.user?.previous_first_name && selectedBooking.user?.previous_last_name}
						{@const fullName = `${selectedBooking.user.first_name} ${selectedBooking.user.last_name}`}
						{@const formerName = `${selectedBooking.user.previous_first_name} ${selectedBooking.user.previous_last_name}`}
						<div class="mb-3 text-sm text-gray-500">
							{fullName}, (formerly {formerName})
						</div>
					{/if}
					<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
						<div>
							<div class="text-xs text-gray-500">Scheduled Date & Time</div>
							<div class="font-medium">
								{(() => {
									const date = parseWallClockDate(selectedBooking.start_time);
									const dateStr = date.toLocaleDateString('en-US', {
										weekday: 'long',
										day: 'numeric',
										month: 'short'
									});
									const timeStr = date.toLocaleTimeString('en-US', {
										hour: 'numeric',
										minute: '2-digit',
										hour12: true
									});
									return `${dateStr} at ${timeStr}`;
								})()}
							</div>
						</div>
						<div>
							<div class="text-xs text-gray-500">Duration</div>
							<div class="font-medium">{totalDuration} minutes</div>
						</div>
						{#if selectedBooking.notes}
							<div class="md:col-span-2">
								<div class="text-xs text-gray-500">Notes</div>
								<div class="mt-1 rounded-md border border-gray-300 bg-white p-2 text-sm">
									{selectedBooking.notes}
								</div>
							</div>
						{/if}
					</div>
				</div>

				{#if selectedBooking.services && selectedBooking.services.length > 0}
					<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
						<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
							Services
						</h3>
						<div class="space-y-3">
							{#each selectedBooking.services as service, index (index)}
								<div class="rounded-md border border-gray-300 bg-white p-3">
									<div class="font-medium">{service.service_name || '—'}</div>
									{#if service.service_description}
										<div class="mt-1 text-sm text-gray-600">{service.service_description}</div>
									{/if}
									<div class="mt-2 flex items-center justify-between text-sm">
										<span class="text-gray-600"
											>{service.override_duration_minutes ?? service.duration_minutes} min</span
										>
										<span class="font-semibold"
											>£{(service.override_price ?? service.price ?? 0).toFixed(2)}</span
										>
									</div>
								</div>
							{/each}
						</div>
					</div>
				{/if}

				<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
					<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
						Financial Summary
					</h3>
					<div class="space-y-2">
						{#if ['confirmed', 'in_progress', 'completed'].includes(selectedBooking.status) && selectedBooking.deposit_required}
							<div class="flex items-center justify-between border-b border-gray-200 pb-2">
								<span class="text-sm text-gray-600">Deposit Required</span>
								<div class="text-right">
									<div class="font-semibold">
										£{selectedBooking.deposit_amount?.toFixed(2) || '0.00'}
									</div>
									<div class="text-xs">
										<span
											class={selectedBooking.deposit_paid ? 'text-green-600' : 'text-orange-600'}
										>
											{selectedBooking.deposit_paid ? 'Paid' : 'Outstanding'}
										</span>
										{#if !selectedBooking.deposit_paid && selectedBooking.deposit_deadline}
											<span class="text-gray-500">
												• Due: {parseWallClockDate(
													selectedBooking.deposit_deadline
												).toLocaleDateString('en-GB', {
													weekday: 'short',
													day: 'numeric',
													month: 'short',
													year: 'numeric'
												})} at {parseWallClockDate(
													selectedBooking.deposit_deadline
												).toLocaleTimeString('en-GB', {
													hour: 'numeric',
													minute: '2-digit',
													hour12: true
												})}
											</span>
										{/if}
									</div>
								</div>
							</div>
						{/if}

						<div class="flex items-center justify-between">
							<span class="text-sm text-gray-600">Subtotal (Services)</span>
							<span class="font-semibold">£{selectedBooking.total_amount.toFixed(2)}</span>
						</div>

						{#if selectedBooking.discounts && selectedBooking.discounts.length > 0}
							<div
								class="my-2 space-y-1 rounded-md border-y border-fuchsia-100 bg-fuchsia-50/20 px-2 py-2"
							>
								{#each selectedBooking.discounts as d (d.id)}
									<div class="flex items-center justify-between text-xs text-fuchsia-800">
										<span class="flex items-center gap-1.5">
											<span class="h-1.5 w-1.5 rounded-full bg-fuchsia-400"></span>
											{#if d.discount_source === 'loyalty'}
												Loyalty Stamp Card (10% Off)
											{:else if d.discount_source === 'referral'}
												Referral Discount ({d.discount_percent}% Off)
											{:else if d.campaign_name}
												{d.campaign_name} ({d.discount_percent}% Off)
											{:else}
												Promo Campaign ({d.discount_percent}% Off)
											{/if}
										</span>
										<span class="font-medium">-£{d.discount_amount.toFixed(2)}</span>
									</div>
								{/each}
							</div>
							<div class="flex items-center justify-between font-medium text-gray-900">
								<span class="text-sm">Net Total</span>
								<span
									>£{(
										selectedBooking.total_amount -
										selectedBooking.discounts.reduce((sum, d) => sum + d.discount_amount, 0)
									).toFixed(2)}</span
								>
							</div>
						{/if}

						{#if hasVAT}
							<div class="mt-2 border-t border-gray-200 pt-2">
								<div class="flex items-center justify-between text-xs text-gray-500">
									<span>Net amount (excl. VAT)</span>
									<span class="font-medium text-gray-700">£{totalNet.toFixed(2)}</span>
								</div>
								<div class="flex items-center justify-between text-xs text-gray-500">
									<span>VAT ({businessSettings?.default_vat_rate ?? 20}%)</span>
									<span class="font-medium text-gray-700">£{totalVAT.toFixed(2)}</span>
								</div>
							</div>
						{/if}

						<div class="flex items-center justify-between">
							<span class="text-sm text-gray-600">Amount Paid (Card/Cash)</span>
							<span class="font-semibold text-green-700">
								£{(selectedBooking.payments ?? [])
									.filter((p) => p.payment_method !== 'discount' && p.status === 'completed')
									.reduce((sum, p) => sum + p.amount, 0)
									.toFixed(2)}
							</span>
						</div>

						{#if totalRefunds > 0}
							<div class="flex items-center justify-between">
								<span class="text-sm text-red-600">Refunds</span>
								<span class="font-semibold text-red-600">-£{totalRefunds.toFixed(2)}</span>
							</div>
						{/if}

						{#if balanceDue > 0.01}
							<div class="flex items-center justify-between border-t border-gray-300 pt-2">
								<span class="font-medium text-gray-900">
									{isFutureBooking ? 'Estimated Subtotal' : 'Amount Due'}
								</span>
								<span class="text-lg font-bold text-red-600">
									£{balanceDue.toFixed(2)}
								</span>
							</div>
						{/if}
					</div>
				</div>

				{#if (selectedBooking.payments && selectedBooking.payments.length > 0) || (selectedBooking.refunds && selectedBooking.refunds.length > 0)}
					<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
						<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
							Payment History
						</h3>
						<div class="space-y-3">
							{#each selectedBooking.payments as payment, index (index)}
								<div class="rounded-md border border-gray-300 bg-white p-3">
									<div class="flex items-start justify-between">
										<div class="flex-1">
											<div class="flex items-center gap-2">
												<span class="font-medium text-gray-900"
													>{getPaymentName(
														payment,
														index,
														selectedBooking.payments,
														selectedBooking.discounts
													)}</span
												>
												<span
													class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium
                                                    {payment.status === 'completed'
														? 'bg-green-100 text-green-800'
														: payment.status === 'pending'
															? 'bg-yellow-100 text-yellow-800'
															: 'bg-gray-100 text-gray-800'}"
												>
													{payment.status}
												</span>
											</div>
											<div class="mt-1 text-xs text-gray-500">
												{#if payment.payment_method === 'discount'}
													Applied automatically on completion
												{:else}
													{payment.payment_type.charAt(0).toUpperCase() +
														payment.payment_type.slice(1)} payment
													{#if payment.card_last4}
														, Card ending in {payment.card_last4}
													{/if}
												{/if}
											</div>
											{#if payment.is_vat_applicable}
												<div class="mt-2 text-xs text-gray-600">
													<div>Net: £{payment.net_amount?.toFixed(2) || '0.00'}</div>
													{#if payment.vat_amount}
														<div>
															VAT ({payment.vat_rate || 0}%): £{payment.vat_amount.toFixed(2)}
														</div>
													{/if}
												</div>
											{/if}
											<div class="mt-1 text-xs text-gray-400">
												{new SvelteDate(payment.created_at).toLocaleString()}
											</div>
										</div>
										<div class="text-right font-semibold">
											{payment.payment_method === 'discount' ? '-' : ''}£{payment.amount.toFixed(2)}
										</div>
									</div>
								</div>
							{/each}

							{#each selectedBooking.refunds ?? [] as refund (refund.id)}
								<div class="rounded-md border border-red-200 bg-red-50 p-3">
									<div class="flex items-start justify-between">
										<div class="flex-1">
											<div class="flex items-center gap-2">
												<span class="font-medium text-red-700">Refund</span>
												<span
													class="inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800"
												>
													{refund.status}
												</span>
											</div>
											<div class="mt-1 text-xs text-red-600">
												{refund.reason || 'Refund processed'}
											</div>
											<div class="mt-1 text-xs text-gray-400">
												{new SvelteDate(refund.created_at).toLocaleString()}
											</div>
										</div>
										<div class="text-right font-semibold text-red-600">
											-£{refund.amount.toFixed(2)}
										</div>
									</div>
								</div>
							{/each}
						</div>
					</div>
				{/if}
			</div>
		{/if}

		<div class="flex flex-col gap-2 border-t px-4 py-3">
			<div class="flex gap-2">
				{#if isCancellable}
					<Button
						variant="destructive"
						size="sm"
						class="flex-1"
						onclick={() => (showCancelConfirm = true)}
					>
						Cancel Booking
					</Button>
					{#if canEditBooking}
						<Button
							variant="outline"
							size="sm"
							class="flex-1"
							onclick={() => {
								showEditModal = true;
							}}
						>
							Edit/Reschedule
						</Button>
					{/if}
				{/if}
			</div>

			{#if pendingEditRequest}
				{@const timeChanged =
					pendingEditRequest.original.start_time &&
					pendingEditRequest.proposed?.start_time &&
					pendingEditRequest.original.start_time !== pendingEditRequest.proposed.start_time}
				{@const servicesChanged =
					pendingEditRequest.proposed?.services?.length &&
					JSON.stringify(pendingEditRequest.original.services?.map((s) => s.name)) !==
						JSON.stringify(pendingEditRequest.proposed.services?.map((s) => s.name))}
				{@const originalNames = (pendingEditRequest.original.services ?? []).map((s) => s.name)}
				{@const proposedNames = (pendingEditRequest.proposed.services ?? []).map((s) => s.name)}
				{@const addedServices = proposedNames.filter((n) => !originalNames.includes(n))}
				{@const removedServices = originalNames.filter((n) => !proposedNames.includes(n))}
				{#if timeChanged || servicesChanged}
					<div class="rounded-md border border-amber-200 bg-amber-50/60 px-4 py-3 text-sm">
						<div class="flex items-start gap-2.5">
							<svg
								class="mt-0.5 h-4 w-4 shrink-0 text-amber-500"
								viewBox="0 0 24 24"
								fill="none"
								stroke="currentColor"
								stroke-width="2"
							>
								<path d="M12 16v-4M12 8h.01" />
								<circle cx="12" cy="12" r="10" />
							</svg>
							<div class="text-amber-900">
								<p class="font-medium">Awaiting admin approval</p>
								<p class="mt-0.5 text-amber-700">
									{#if timeChanged}
										Reschedule requested from {parseWallClockDate(
											pendingEditRequest.original.start_time!
										).toLocaleString('en-GB', {
											day: 'numeric',
											month: 'short',
											hour: '2-digit',
											minute: '2-digit'
										})} to {parseWallClockDate(
											pendingEditRequest.proposed.start_time!
										).toLocaleString('en-GB', {
											day: 'numeric',
											month: 'short',
											hour: '2-digit',
											minute: '2-digit'
										})}
									{/if}
								</p>
								{#if addedServices.length > 0}
									<p class="mt-1 text-amber-700">
										<span class="font-medium text-green-700">Services added:</span>
										{addedServices.join(', ')}
									</p>
								{/if}
								{#if removedServices.length > 0}
									<p class="mt-0.5 text-amber-700">
										<span class="font-medium text-red-600">Services removed:</span>
										{removedServices.join(', ')}
									</p>
								{/if}
								<p class="mt-1 text-xs text-amber-500">
									We'll let you know once it's been reviewed
								</p>
							</div>
						</div>
					</div>
				{/if}
			{/if}

			<div class="flex gap-2">
				{#if selectedBooking}
					<Button
						size="sm"
						class="flex-1 hover:bg-gray-50"
						variant="outline"
						onclick={printReceipt}
					>
						Print Receipt
					</Button>
				{/if}
				{#if isCompleted}
					<Button
						size="sm"
						class="flex-1 hover:bg-fuchsia-50"
						variant="outline"
						onclick={() => (showTipModal = true)}
					>
						Leave a Tip
					</Button>
				{/if}
				{#if depositOutstanding && selectedBooking?.status !== 'pending'}
					<Button
						size="sm"
						class="flex-1 bg-amber-600 text-white hover:bg-amber-700"
						onclick={() => (showPaymentModal = true)}
						disabled={hasPendingEditRequest}
					>
						Pay Deposit
					</Button>
				{:else if canPayEarly && !hasPendingEditRequest}
					<Button
						size="sm"
						class="flex-1 bg-emerald-600 text-white hover:bg-emerald-700"
						onclick={() => (showPaymentModal = true)}
					>
						Pay Early
					</Button>
				{/if}
				{#if hasPendingEditRequest && !depositOutstanding}
					<div
						class="flex-1 rounded-md border border-dashed border-gray-200 bg-gray-50/50 px-3 py-2 text-center text-xs text-gray-400"
					>
						Payments paused while awaiting approval
					</div>
				{/if}
			</div>
			<Button size="sm" class="w-full" variant="ghost" onclick={() => (open = false)}>Close</Button>
		</div>
	</Modal.Content>
</Modal.Root>

{#if showEditModal && selectedBooking}
	<EditRequestModal
		bind:open={showEditModal}
		booking={selectedBooking}
		onSubmitted={() => {
			showEditModal = false;
			fetchBookingDetails();
		}}
	/>
{/if}

{#if showPaymentModal && selectedBooking}
	<UserPaymentModal
		booking={selectedBooking}
		onClose={() => (showPaymentModal = false)}
		onComplete={handlePaymentComplete}
		{canSaveCards}
		defaultPaymentType={depositOutstanding ? 'deposit' : undefined}
	/>
{/if}

<Modal.Root open={showCancelConfirm} onOpenChange={(v) => (showCancelConfirm = v)}>
	<Modal.Content class="!z-[70] max-w-[calc(100%-2rem)]">
		<Modal.Header>
			<Modal.Title>Cancel Booking</Modal.Title>
			<Modal.Description>
				{@const hoursUntilAppt = Math.round(hoursUntilAppointment)}
				{#if hasPayments}
					<p>
						Are you sure you want to cancel this booking? You have already made payments totalling
						<span class="font-semibold">£{totalPaid.toFixed(2)}</span>.
					</p>
					<div
						class="mt-2 rounded-md border p-3 text-sm {hoursUntilAppt <
						POLICY.PARTIAL_REFUND_THRESHOLD_HOURS
							? 'border-red-200 bg-red-50 text-red-800'
							: 'border-amber-200 bg-amber-50 text-amber-800'}"
					>
						{#if hoursUntilAppt > POLICY.FULL_REFUND_THRESHOLD_HOURS}
							<p class="font-medium text-green-800">Full Refund</p>
							<p class="mt-1">
								You have given over {POLICY.FULL_REFUND_THRESHOLD_HOURS} hours' notice. You will receive
								a
								<strong>full refund</strong> of <strong>£{totalPaid.toFixed(2)}</strong>. Nothing
								will be deducted.
							</p>
						{:else if hoursUntilAppt >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS}
							<p class="font-medium">Partial Refund</p>
							<p class="mt-1">
								Based on your notice period ({hoursUntilAppt}h), up to {POLICY.PROTECTED_DEPOSIT_MAX_PCT *
									100}% of the total (£{protectedDeposit.toFixed(2)}) is treated as a protected
								deposit and will be retained. The remaining
								<strong>£{estimatedRefund.toFixed(2)}</strong> will be refunded.
							</p>
						{:else}
							<p class="font-semibold text-red-900">Cancelling With No Refund</p>
							<p class="mt-1">
								This booking is under {POLICY.PARTIAL_REFUND_THRESHOLD_HOURS} hours' notice. The full
								amount you have paid (<strong>£{totalPaid.toFixed(2)}</strong>) will be retained to
								cover the lost slot. It will also count as a <strong>no-show</strong> toward your booking
								history (2 no-shows within 6 months would require deposits on future bookings).
							</p>
						{/if}
						<p class="mt-2 text-xs">
							Refunds are processed via Square and may take 3–5 business days. See our
							<PolicyPopover>
								{#snippet trigger()}
									<span class="underline">cancellation policy</span>
								{/snippet}
							</PolicyPopover>
							for full details.
						</p>
					</div>
				{:else if hoursUntilAppt >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS}
					<p>Are you sure you want to cancel this booking?</p>
					<div class="mt-2 rounded-md border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800">
						<p class="font-medium">Clean Cancellation</p>
						<p class="mt-1">
							This booking has no payments and is being cancelled with
							{hoursUntilAppt > POLICY.FULL_REFUND_THRESHOLD_HOURS ? 'plenty of' : 'sufficient'} notice.
							It will be removed completely and will not appear in your booking history.
						</p>
					</div>
				{:else}
					<p>Are you sure you want to cancel this booking?</p>
					<div class="mt-2 rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-800">
						<p class="font-semibold text-red-900">Short Notice Cancellation</p>
						<p class="mt-1">
							This booking is under {POLICY.PARTIAL_REFUND_THRESHOLD_HOURS} hours' notice. Cancelling
							now counts as a
							<strong>no-show</strong> (2 no-shows within 6 months will require deposits on future bookings).
						</p>
					</div>
				{/if}
			</Modal.Description>
		</Modal.Header>
		<Modal.Footer>
			<Button variant="outline" onclick={() => (showCancelConfirm = false)}>Keep Booking</Button>
			<Button variant="destructive" onclick={cancelBooking} disabled={cancelling}>
				{cancelling ? 'Cancelling...' : 'Yes, Cancel'}
			</Button>
		</Modal.Footer>
	</Modal.Content>
</Modal.Root>

<Modal.Root
	open={showTipModal}
	onOpenChange={(v) => {
		if (!v) {
			showTipModal = false;
			tipAmount = 0;
			selectedTipPreset = null;
			customTipInput = '';
		}
	}}
>
	<Modal.Content class="!z-[70] max-w-[calc(100%-2rem)] max-h-[90vh] overflow-y-auto">
		<Modal.Header>
			<Modal.Title>Leave a Tip</Modal.Title>
			<Modal.Description>Show your appreciation for great service</Modal.Description>
		</Modal.Header>

		<div class="space-y-4 px-4 pb-4">
			<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
				{#each tipPresets as preset (preset.pct)}
					<button
						class="rounded-lg border border-input bg-background py-3 text-center font-semibold transition-colors hover:bg-fuchsia-50 {selectedTipPreset ===
						preset.amount
							? 'bg-fuchsia-100'
							: ''}"
						onclick={() => selectTipPreset(preset.amount)}
						type="button"
					>
						<div>£{preset.amount.toFixed(2)}</div>
						<div class="text-xs font-normal text-gray-500">{preset.pct}%</div>
					</button>
				{/each}
			</div>

			<div>
				<label for="custom-tip" class="text-sm font-medium text-gray-700"
					>Or enter custom amount</label
				>
				<div class="relative mt-1">
					<span class="absolute top-1/2 left-3 -translate-y-1/2 text-gray-500">£</span>
					<Input
						id="custom-tip"
						type="text"
						inputmode="decimal"
						step="0.01"
						min="0"
						placeholder="0.00"
						class="pl-7"
						value={customTipInput}
						oninput={handleCustomTip}
					/>
				</div>
			</div>

			<!-- Card Selection for Tip -->
			<div class="space-y-3">
				<span class="block text-xs font-semibold tracking-wider text-gray-500 uppercase">Payment Method</span>

				{#if tipLoadingCards}
					<div class="py-2 text-center text-sm text-gray-500">Loading payment methods...</div>
				{:else if tipSavedCards.length > 0}
					<div class="space-y-2">
						{#each tipSavedCards as card (card.id)}
							<button
								type="button"
								class="flex w-full items-center justify-between rounded-lg border p-3 text-left {tipSelectedCardId === card.id && !tipShowNewCard ? 'border-input bg-accent' : 'border-gray-200 hover:bg-gray-50'}"
								onclick={() => { tipSelectedCardId = card.id; tipShowNewCard = false; }}
							>
	<div class="flex items-center gap-3">
		<CardBrandIcon brand={card.brand} />
									<div class="text-sm">
										<span class="font-mono">**** {card.last_4}</span>
										<span class="ml-2 text-xs text-gray-400"
											>Exp {String(card.exp_month).padStart(2, '0')}/{card.exp_year}</span
										>
									</div>
								</div>
								{#if tipSelectedCardId === card.id && !tipShowNewCard}
									<span class="text-xs font-semibold text-primary">Selected</span>
								{/if}
							</button>
						{/each}
						<button
							type="button"
							class="flex w-full items-center justify-between rounded-lg border p-3 text-left {tipShowNewCard ? 'border-input bg-accent' : 'border-gray-200 hover:bg-gray-50'}"
							onclick={() => { tipSelectedCardId = null; tipShowNewCard = true; }}
						>
							<div class="flex items-center gap-3">
								<div
									class="flex h-8 min-w-12 items-center justify-center rounded border border-dashed border-gray-300 text-xs font-medium text-gray-400"
								>
									NEW
								</div>
								<span class="animate-pulse text-sm font-medium text-gray-700">Use a new card</span>
							</div>
							{#if tipShowNewCard}
								<span class="text-xs font-semibold text-primary">Selected</span>
							{/if}
						</button>
					</div>

					{#if tipShowNewCard}
						<div class="space-y-3 rounded-lg border bg-gray-50 p-3">
							<CardInput
								bind:cardNumber={tipNewCardNumber}
								bind:cardExpiry={tipNewCardExpiry}
								bind:cardCVC={tipNewCardCVC}
								bind:saveCard={tipSaveCardFuture}
								showSaveCard={canSaveCards}
								onfieldblur={handleTipFieldBlur}
								onfieldinput={handleTipFieldInput}
							/>
							{#if tipNewCardError}
								<div class="mt-1 text-xs font-semibold text-red-500">{tipNewCardError}</div>
							{/if}
						</div>
					{/if}
				{:else}
					<div class="space-y-3 rounded-lg border bg-gray-50 p-3">
						<CardInput
							bind:cardNumber={tipNewCardNumber}
							bind:cardExpiry={tipNewCardExpiry}
							bind:cardCVC={tipNewCardCVC}
							bind:saveCard={tipSaveCardFuture}
							showSaveCard={canSaveCards}
							onfieldblur={handleTipFieldBlur}
							onfieldinput={handleTipFieldInput}
						/>
						{#if tipNewCardError}
							<div class="mt-1 text-xs font-semibold text-red-500">{tipNewCardError}</div>
						{/if}
					</div>
				{/if}
			</div>
		</div>

		<Modal.Footer>
			<Button variant="outline" onclick={() => (showTipModal = false)}>Cancel</Button>
			<Button
				class="hover:bg-fuchsia-50"
				onclick={submitTip}
				disabled={tipAmount <= 0 || !isTipCardValid || tipProcessing}
				loading={tipProcessing}
			>
				{tipProcessing ? 'Processing...' : `Pay Tip £${tipAmount.toFixed(2)}`}
			</Button>
		</Modal.Footer>
	</Modal.Content>
</Modal.Root>
