<script lang="ts">
	import { Button } from '$lib/components/ui/button';
	import WalkInCreateModal from '$lib/components/admin/WalkInCreateModal.svelte';
	import { CalendarDate } from '@internationalized/date';
	import { apiFetch } from '$lib/utils/api';
	import { SvelteDate } from 'svelte/reactivity';
	import { onMount, onDestroy } from 'svelte';
	import { toast } from 'svelte-sonner';
	import { extractErrorMessage } from '$lib/utils/toast-safe';

	import type { AvailableHoursDay, Service } from '$lib/types/booking';
	import {
		minutesToTime,
		calculateMiddleWindow,
		shouldApplyLunchProtection
	} from '$lib/lunchProtection';
	import { formatLocalDateTime } from '$lib/utils/timeSlots';

	const RESERVATION_TTL = 15;

	let showCreateModal = $state(false);
	let slotInfo = $state<{
		isAvailableNow: boolean;
		waitMinutes?: number;
		durationMinutes: number;
		startTime?: string;
		slotEndMinutes?: number;
	} | null>(null);
	let loading = $state(true);
	let noSlotsToday = $state(false);
	let currentTime = new SvelteDate();

	let _reservationId = $state<string | null>(null);
	let reservationExpiresAt = $state<Date | null>(null);
	let isReserving = $state(false);
	let reservedDuration = $state(0);
	let reservedStartTime = $state<string | null>(null);

	let shortestServiceMinutes = $state<number | null>(null);

	async function fetchShortestService() {
		try {
			const response = await apiFetch('/api/services');
			if (response.ok) {
				const services: Service[] = await response.json();
				const durations = services.map((s) => s.duration_minutes).filter((d) => d > 0);
				if (durations.length > 0) {
					shortestServiceMinutes = Math.min(...durations);
				}
			}
		} catch {
			// Silently handled - services list remains empty
		}
	}

	onMount(() => {
		calculateSlotAvailability();
		fetchShortestService();

		const interval = setInterval(() => {
			currentTime = new SvelteDate();
		}, 60000);

		return () => clearInterval(interval);
	});

	// Release any held reservation if the component unmounts while a slot
	// is still reserved (e.g. admin navigates away from the page). Best-effort
	// — TTL cleanup will eventually run if this fails.
	onDestroy(() => {
		if (typeof window !== 'undefined' && window.__walkInCountdownInterval) {
			clearInterval(window.__walkInCountdownInterval);
		}
		if (_reservationId) {
			releaseWalkInReservation();
		}
	});

	async function releaseWalkInReservation() {
		if (!_reservationId) return;
		const idToRelease = _reservationId;
		// Clear local state first so a slow DELETE doesn't block the UI.
		_reservationId = null;
		reservationExpiresAt = null;
		reservedDuration = 0;
		reservedStartTime = null;
		if (window.__walkInCountdownInterval) {
			clearInterval(window.__walkInCountdownInterval);
		}
		try {
			const res = await apiFetch('/api/admin/bookings/reserve', {
				method: 'DELETE'
			});
			if (!res.ok && res.status !== 404) {
				console.warn('Failed to release walk-in reservation', idToRelease, res.status);
			}
		} catch (e) {
			console.warn('Error releasing walk-in reservation', idToRelease, e);
		}
	}

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

	function formatDuration(minutes: number): string {
		const hours = Math.floor(minutes / 60);
		const mins = minutes % 60;
		if (hours > 0 && mins > 0) {
			return `${hours} hour${hours !== 1 ? 's' : ''}, ${mins} minute${mins !== 1 ? 's' : ''}`;
		} else if (hours > 0) {
			return `${hours} hour${hours !== 1 ? 's' : ''}`;
		} else {
			return `${mins} minute${mins !== 1 ? 's' : ''}`;
		}
	}

	/**
	 * Subtract time gaps from available slots, returning remaining (split) slots.
	 * E.g., subtract [{10:00, 11:00}] from [{09:00, 17:00}] → [{09:00, 10:00}, {11:00, 17:00}]
	 */
	function subtractTimeSlots(
		slots: Array<{ startTime: string; endTime: string }>,
		gaps: Array<{ startTime: string; endTime: string }>
	): Array<{ startTime: string; endTime: string }> {
		if (gaps.length === 0) return slots;

		let result = slots.map((s) => ({ ...s }));

		for (const gap of gaps) {
			const gapStart = timeToMinutes(gap.startTime);
			const gapEnd = timeToMinutes(gap.endTime);
			const newResult: Array<{ startTime: string; endTime: string }> = [];

			for (const slot of result) {
				const slotStart = timeToMinutes(slot.startTime);
				const slotEnd = timeToMinutes(slot.endTime);

				if (gapEnd <= slotStart || gapStart >= slotEnd) {
					// No overlap, keep the slot as is
					newResult.push(slot);
				} else {
					// Overlap exists, split the slot
					if (slotStart < gapStart) {
						newResult.push({ startTime: slot.startTime, endTime: minutesToTime(gapStart) });
					}
					if (gapEnd < slotEnd) {
						newResult.push({ startTime: minutesToTime(gapEnd), endTime: slot.endTime });
					}
				}
			}
			result = newResult;
		}

		return result;
	}

	function getLiveRemainingMinutes(): number | null {
		if (!slotInfo?.isAvailableNow || !slotInfo.slotEndMinutes) return null;
		const now = currentTime.getHours() * 60 + currentTime.getMinutes();
		const remaining = slotInfo.slotEndMinutes - now;
		return Math.max(0, remaining);
	}

	function getLiveWaitMinutes(): number | null {
		if (slotInfo?.isAvailableNow || !slotInfo?.startTime) return null;
		const now = currentTime.getHours() * 60 + currentTime.getMinutes();
		const slotStartMinutes = timeToMinutes(slotInfo.startTime);
		const wait = slotStartMinutes - now;
		return Math.max(0, wait);
	}

	async function calculateSlotAvailability() {
		loading = true;
		noSlotsToday = false;

		try {
			const now = new SvelteDate();
			const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
			const [y, m, d] = londonDateStr.split('-').map(Number);
			const today = new CalendarDate(y, m, d);

			const response = await apiFetch(
				`/api/scheduling/available-hours?start=${today}&end=${today}`
			);

			if (!response.ok) {
				noSlotsToday = true;
				return;
			}

			const data: AvailableHoursDay[] = await response.json();
			const todayData = data[0];

			if (!todayData || !todayData.isOpen || !todayData.slots || todayData.slots.length === 0) {
				noSlotsToday = true;
				return;
			}

			// --- Walk-in lunch protection ---
			// Always block the first 60 minutes of the suggested lunch window from walk-in availability
			const dayStartMinutesVal = Math.min(
				...todayData.slots.map((s) => timeToMinutes(s.startTime))
			);
			const dayEndMinutesVal = Math.max(...todayData.slots.map((s) => timeToMinutes(s.endTime)));
			const dayStartTimeVal = minutesToTime(dayStartMinutesVal);
			const dayEndTimeVal = minutesToTime(dayEndMinutesVal);

			if (shouldApplyLunchProtection(dayStartTimeVal, dayEndTimeVal)) {
				const { windowStart } = calculateMiddleWindow(dayStartTimeVal, dayEndTimeVal);
				const lunchWalkerBlocker = {
					startTime: minutesToTime(windowStart),
					endTime: minutesToTime(windowStart + 60)
				};

				todayData.slots = subtractTimeSlots(todayData.slots, [lunchWalkerBlocker]);

				if (todayData.slots.length === 0) {
					noSlotsToday = true;
					return;
				}
			}
			// --- End walk-in lunch protection ---

			const currentMinutes = now.getHours() * 60 + now.getMinutes();

			for (const slot of todayData.slots) {
				const slotStartMinutes = timeToMinutes(slot.startTime);
				const slotEndMinutes = timeToMinutes(slot.endTime);

				if (currentMinutes >= slotStartMinutes && currentMinutes < slotEndMinutes) {
					const remainingMinutes = slotEndMinutes - currentMinutes;
					slotInfo = {
						isAvailableNow: true,
						durationMinutes: remainingMinutes,
						slotEndMinutes: slotEndMinutes
					};
					return;
				}

				if (slotStartMinutes > currentMinutes) {
					const waitMinutes = slotStartMinutes - currentMinutes;
					const durationMinutes = slotEndMinutes - slotStartMinutes;
					slotInfo = {
						isAvailableNow: false,
						waitMinutes,
						durationMinutes,
						startTime: slot.startTime
					};
					return;
				}
			}

			noSlotsToday = true;
		} catch (_err) {
			console.error('Failed to calculate slot availability', _err);
			noSlotsToday = true;
		} finally {
			loading = false;
		}
	}

	function formatTime(time: string): string {
		const [hours, minutes] = time.split(':').map(Number);
		const period = hours >= 12 ? 'PM' : 'AM';
		const displayHours = hours % 12 || 12;
		return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
	}

	async function reserveWalkInSlot(startTime: string, durationMinutes: number): Promise<boolean> {
		isReserving = true;

		try {
			const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
			const [y, m, d] = londonDateStr.split('-').map(Number);
			const [hours, minutes] = startTime.split(':').map(Number);
			const start = new SvelteDate(y, m - 1, d, hours, minutes, 0, 0);
			const startTimeISO = formatLocalDateTime(start);

			const response = await apiFetch('/api/admin/bookings/reserve', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					user_id: null,
					start_time: startTimeISO,
					service_ids: [],
					service_overrides: [],
					ttl_minutes: RESERVATION_TTL,
					reservation_type: 'walkin',
					duration_minutes: durationMinutes
				})
			});

			if (response.ok) {
				const data = await response.json();
				_reservationId = data.id;
				reservedDuration = data.duration_minutes;
				reservationExpiresAt = new Date(data.expires_at);
				startWalkInCountdown();
				return true;
			} else if (response.status === 409) {
				toast.error('Slot no longer available, please try again');
				calculateSlotAvailability();
				return false;
			} else {
				const errorText = await response.text();
				toast.error(`Failed to reserve slot: ${extractErrorMessage(errorText)}`);
				return false;
			}
		} catch (_err) {
			console.error('Reservation error:', _err);
			toast.error('Failed to reserve slot');
			return false;
		} finally {
			isReserving = false;
		}
	}

	function startWalkInCountdown() {
		if (window.__walkInCountdownInterval) {
			clearInterval(window.__walkInCountdownInterval);
		}

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

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

			if (diff <= 0) {
				_reservationId = null;
				reservationExpiresAt = null;
				toast.error('Slot released — please re-check availability');
				if (window.__walkInCountdownInterval) {
					clearInterval(window.__walkInCountdownInterval);
				}
				showCreateModal = false;
				return;
			}
		};

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

	const availableMinutes = $derived.by(() => {
		if (!slotInfo) return null;
		if (slotInfo.isAvailableNow) return getLiveRemainingMinutes();
		return slotInfo.durationMinutes;
	});

	const tooShortForService = $derived.by(() => {
		if (shortestServiceMinutes === null || availableMinutes === null) return false;
		return availableMinutes < shortestServiceMinutes;
	});

	async function handleStartWalkIn() {
		if (!slotInfo) return;

		let reserveTime: string;
		let reserveDuration: number;

		if (slotInfo.isAvailableNow) {
			const liveRemaining = getLiveRemainingMinutes() ?? 0;
			if (liveRemaining > RESERVATION_TTL) {
				// Available now with >15min remaining — reserve from now to slot end
				const now = new SvelteDate();
				const currentMin = now.getHours() * 60 + now.getMinutes();
				reserveTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
				reserveDuration = slotInfo.slotEndMinutes! - currentMin;
			} else {
				// Available now but ≤15min — reserve next slot instead
				await calculateSlotAvailability();
				if (!slotInfo || slotInfo.isAvailableNow || !slotInfo.startTime) {
					toast.error('No suitable slot available');
					return;
				}
				reserveTime = slotInfo.startTime;
				reserveDuration = slotInfo.durationMinutes;
			}
		} else {
			// Not available now — reserve the next slot
			reserveTime = slotInfo.startTime!;
			reserveDuration = slotInfo.durationMinutes;
		}

		const reserved = await reserveWalkInSlot(reserveTime, reserveDuration);
		if (reserved) {
			reservedStartTime = reserveTime;
			showCreateModal = true;
		}
	}
</script>

<div class="rounded-lg border border-gray-200 bg-white p-4">
	<h3 class="mb-2 text-sm font-semibold tracking-wide text-gray-600 uppercase">Walk-In Booking</h3>

	{#if loading}
		<div class="mb-4 h-12 animate-pulse rounded bg-gray-100"></div>
	{:else if noSlotsToday}
		<p class="mb-4 text-sm text-gray-500">No slots available for walk-in today</p>
	{:else if slotInfo?.isAvailableNow}
		{@const liveRemaining = getLiveRemainingMinutes()}
		{#if liveRemaining !== null && liveRemaining > 0}
			<p class="mb-4 text-sm text-gray-500">
				Available now for <span class="font-semibold text-gray-700"
					>{formatDuration(liveRemaining)}</span
				>
			</p>
		{:else}
			<p class="mb-4 text-sm text-gray-500">No slots available for walk-in today</p>
		{/if}
	{:else if slotInfo && !slotInfo.isAvailableNow}
		{@const liveWait = getLiveWaitMinutes()}
		{#if liveWait !== null && liveWait > 0}
			<p class="mb-4 text-sm text-gray-500">
				Next slot available in <span class="font-semibold text-gray-700"
					>{formatDuration(liveWait)}</span
				>
				at {formatTime(slotInfo.startTime!)}, for
				<span class="font-semibold text-gray-700">{formatDuration(slotInfo.durationMinutes)}</span>
			</p>
		{:else}
			<p class="mb-4 text-sm text-gray-500">
				Available now for <span class="font-semibold text-gray-700"
					>{formatDuration(slotInfo.durationMinutes)}</span
				>
			</p>
		{/if}
	{/if}

	{#if tooShortForService}
		<p class="mb-4 text-sm text-amber-600">
			Not enough time for any service{shortestServiceMinutes !== null
				? ` (shortest service is ${formatDuration(shortestServiceMinutes)})`
				: ''}
		</p>
	{/if}

	<div class="flex flex-wrap items-center gap-3">
		<Button
			onclick={handleStartWalkIn}
			disabled={noSlotsToday ||
				isReserving ||
				(slotInfo?.isAvailableNow && (getLiveRemainingMinutes() ?? 0) <= 0) ||
				tooShortForService}
		>
			{isReserving ? 'Reserving...' : 'Start Walk-In Session'}
		</Button>
	</div>
</div>

{#if showCreateModal}
	<WalkInCreateModal
		bind:open={showCreateModal}
		maxSlotDuration={reservedDuration}
		availableStartTime={reservedStartTime ?? undefined}
		{reservationExpiresAt}
	/>
{/if}
