<script lang="ts">
	// ============================================================
	// ADMIN SCHEDULE PAGE — Google Calendar-style week view
	// ============================================================
	import { goto } from '$app/navigation';
	import { authStore } from '$lib/stores/auth.svelte';
	import { browser } from '$app/environment';
	import { Skeleton } from '$lib/components/ui/skeleton';
	import { Button } from '$lib/components/ui/button';
	import { formatDuration } from '$lib/utils/format';
	import { formatUserName } from '$lib/utils/nameDisplay';
	import { SvelteDate } from 'svelte/reactivity';
	import BookingModal from '$lib/components/admin/BookingModal.svelte';

	// -- Types --
	type BookingService = { service_name?: string };
	type ScheduleBooking = {
		id: string;
		start_time: string;
		status: string;
		duration_minutes: number;
		user?: {
			full_name: string;
			previous_first_name?: string | null;
			previous_last_name?: string | null;
		};
		services: BookingService[];
	};
	type TimeBlocker = {
		id: string;
		start_time: string;
		duration_minutes: number;
		description: string;
	};
	type WorkingDay = {
		date: string;
		isOpen: boolean;
		startTime: string;
		endTime: string;
	};

	// -- State --
	let pageState = $state<'loading' | 'authorized' | 'unauthorized'>('loading');
	let bookings = $state<ScheduleBooking[]>([]);
	let blockers = $state<TimeBlocker[]>([]);
	let workingHours = $state<WorkingDay[]>([]);
	let loading = $state(true);
	let prevBookingsJson = $state('');
	let prevBlockersJson = $state('');
	let prevHoursJson = $state('');
	let initialized = $state(false);
	let showBookingModal = $state(false);
	let selectedBookingId = $state<string | null>(null);
	let weekStart = $state<Date | undefined>(undefined);

	// -- Drag-scroll state --
	let scrollContainer = $state<HTMLElement | null>(null);
	let isDragging = $state(false);
	let dragStartX = $state(0);
	let dragStartY = $state(0);
	let dragScrollLeft = $state(0);
	let dragScrollTop = $state(0);

	// -- Auth & Init --
	$effect(() => {
		if (!browser) return;
		if (authStore.isLoading) {
			pageState = 'loading';
			return;
		}
		if (!authStore.isAuthenticated || authStore.currentUser?.role !== 'admin') {
			pageState = 'unauthorized';
			// eslint-disable-next-line svelte/no-navigation-without-resolve
			goto(authStore.isAuthenticated ? '/' : '/login', { replaceState: true });
			return;
		}
		pageState = 'authorized';
	});

	$effect(() => {
		if (pageState === 'authorized' && !weekStart) {
			const today = getLondonToday();
			const dayOfWeek = today.getDay();
			const diff = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
			const monday = new SvelteDate(today);
			monday.setDate(monday.getDate() - diff);
			monday.setHours(0, 0, 0, 0);
			weekStart = monday;
		}
	});

	// -- Helpers --
	function getWeekDays(start: Date): Date[] {
		return Array.from({ length: 7 }, (_, i) => {
			const d = new SvelteDate(start);
			d.setDate(d.getDate() + i);
			return d;
		});
	}

	function formatDate(d: Date): string {
		return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
	}

	/** Return the current London date as a Date set to midnight in the local timezone.
	 *  Uses Intl.DateTimeFormat with Europe/London to handle BST/GMT correctly. */
	function getLondonToday(): Date {
		const dateStr = new SvelteDate().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
		return new SvelteDate(dateStr + 'T00:00:00');
	}

	/** Return the current time-of-day in London as minutes since midnight, using
	 *  Intl.DateTimeFormat with Europe/London so the current-time blue line is
	 *  positioned correctly regardless of the browser's system timezone. */
	function getLondonNowMinutes(): number {
		const timeStr = new SvelteDate().toLocaleTimeString('en-GB', {
			timeZone: 'Europe/London',
			hour: '2-digit',
			minute: '2-digit',
			hour12: false
		});
		const [h, m] = timeStr.split(':').map(Number);
		return h * 60 + m;
	}

	/** Convert a UTC ISO timestamp to a London-date YYYY-MM-DD key.
	 *  Uses Intl.DateTimeFormat with Europe/London timezone so that bookings
	 *  at 23:30 UTC (00:30 BST next day) are grouped under the correct
	 *  London date column rather than the UTC date. */
	function getLondonDateKey(iso: string): string {
		return new SvelteDate(iso).toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
	}

	function formatWeekLabel(start: Date): string {
		const end = new SvelteDate(start);
		end.setDate(end.getDate() + 6);
		const opts: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
		const endOpts: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' };
		return `${start.toLocaleDateString('en-US', opts)} – ${end.toLocaleDateString('en-US', endOpts)}`;
	}

	function formatHour(h: number): string {
		const period = h >= 12 ? 'PM' : 'AM';
		const hour = h % 12 || 12;
		return `${hour} ${period}`;
	}

	function formatTimeShort(iso: string): string {
		return new SvelteDate(iso).toLocaleTimeString('en-US', {
			hour: 'numeric',
			minute: '2-digit',
			hour12: true
		});
	}

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

	// Calculates pixel offset from the top of the grid.
	// Each hour row is SLOT_HEIGHT (64px content + 1px border-b),
	// so fractional hours are multiplied by SLOT_HEIGHT.
	function gridPxFromTime(time: string, minStart: number): number {
		const fractionalHours = timeToMinutes(time) / 60 - minStart;
		return fractionalHours * SLOT_HEIGHT;
	}

	function getServiceNames(b: ScheduleBooking): string {
		return (b.services ?? [])
			.map((s) => s.service_name)
			.filter(Boolean)
			.join(', ');
	}

	function bookingStyle(status: string): string {
		switch (status) {
			case 'completed':
				return 'background:#d1fae5;color:#065f46;border-color:#6ee7b7;text-decoration:line-through';
			case 'confirmed':
				return 'background:#d1fae5;color:#065f46;border-color:#6ee7b7;';
			case 'in_progress':
				return 'background:#dbeafe;color:#1e40af;border-color:#93c5fd';
			case 'pending':
				return 'background:#fef9c3;color:#854d0e;border-color:#fde047';
			case 'client_cancelled':
				return 'background:#fecaca;color:#7f1d1d;border-color:#f87171;text-decoration:line-through';
			case 'we_cancelled':
				return 'background:#fecaca;color:#7f1d1d;border-color:#f87171;text-decoration:line-through';
			case 'no_show':
				return 'background:#e5e7eb;color:#1f2937;border-color:#9ca3af;text-decoration:line-through';
			default:
				return 'background:#f3f4f6;color:#1f2937;border-color:#d1d5db';
		}
	}

	function dotColor(status: string): string {
		switch (status) {
			case 'completed':
			case 'confirmed':
				return '#10b981';
			case 'in_progress':
				return '#3b82f6';
			case 'pending':
				return '#eab308';
			case 'client_cancelled':
			case 'we_cancelled':
				return '#dc2626';
			default:
				return '#6b7280';
		}
	}

	function bookingsOverlap(a: ScheduleBooking, b: ScheduleBooking): boolean {
		const aStart = new SvelteDate(a.start_time).getTime();
		const aEnd = aStart + a.duration_minutes * 60000;
		const bStart = new SvelteDate(b.start_time).getTime();
		const bEnd = bStart + b.duration_minutes * 60000;
		return aStart < bEnd && bStart < aEnd;
	}

	// -- Data Fetching --
	async function fetchWeekData() {
		if (!weekStart) return;
		if (!initialized) loading = true;
		try {
			const startStr = formatDate(weekStart);
			const end = new SvelteDate(weekStart);
			end.setDate(end.getDate() + 6);
			const endStr = formatDate(end);

			const [bookingsRes, whRes, blockersRes] = await Promise.all([
				fetch(`/api/admin/bookings?start_date=${startStr}&end_date=${endStr}&per_page=500`, {
					headers: { Authorization: `Bearer ${authStore.currentToken}` }
				}),
				fetch(`/api/scheduling/working-hours?start=${startStr}&end=${endStr}`, {
					headers: { Authorization: `Bearer ${authStore.currentToken}` }
				}),
				fetch(`/api/admin/time-blockers?start=${startStr}&end=${endStr}`, {
					headers: { Authorization: `Bearer ${authStore.currentToken}` }
				})
			]);

			if (bookingsRes.ok) {
				const data = await bookingsRes.json();
				const newBookings = data.bookings || [];
				const newJson = JSON.stringify(newBookings);
				if (newJson !== prevBookingsJson) {
					bookings = newBookings;
					prevBookingsJson = newJson;
				}
			}

			if (blockersRes.ok) {
				const data: TimeBlocker[] = await blockersRes.json();
				const newBlockers = (data || []).filter((b) => !b.description?.startsWith('RESERVATION:'));
				const newJson = JSON.stringify(newBlockers);
				if (newJson !== prevBlockersJson) {
					blockers = newBlockers;
					prevBlockersJson = newJson;
				}
			}

			if (whRes.ok) {
				const data: WorkingDay[] = await whRes.json();
				const newJson = JSON.stringify(data);
				if (newJson !== prevHoursJson) {
					workingHours = data;
					prevHoursJson = newJson;
				}
			}
		} catch (err) {
			console.error('Error fetching schedule data:', err);
		} finally {
			if (!initialized) {
				loading = false;
				initialized = true;
			}
		}
	}

	$effect(() => {
		if (weekStart) fetchWeekData();
	});

	$effect(() => {
		if (pageState !== 'authorized') return;
		const intervalId = setInterval(fetchWeekData, 600_000);
		const handleRefresh = () => fetchWeekData();
		window.addEventListener('bookingApproved', handleRefresh);
		return () => {
			clearInterval(intervalId);
			window.removeEventListener('bookingApproved', handleRefresh);
		};
	});

	// -- Interaction Handlers --
	function onMouseDown(e: MouseEvent) {
		if (!scrollContainer || e.button !== 0) return;
		isDragging = true;
		dragStartX = e.pageX;
		dragStartY = e.pageY;
		dragScrollLeft = scrollContainer.scrollLeft;
		dragScrollTop = scrollContainer.scrollTop;
	}

	function onMouseMove(e: MouseEvent) {
		if (!isDragging || !scrollContainer) return;
		e.preventDefault();
		scrollContainer.scrollLeft = dragScrollLeft - (e.pageX - dragStartX);
		scrollContainer.scrollTop = dragScrollTop - (e.pageY - dragStartY);
	}

	function onMouseUp() {
		isDragging = false;
	}

	function navigateWeek(delta: number) {
		if (!weekStart) return;
		const d = new SvelteDate(weekStart);
		d.setDate(d.getDate() + delta * 7);
		weekStart = d;
	}

	function goToday() {
		const today = getLondonToday();
		const dayOfWeek = today.getDay();
		const diff = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
		const monday = new SvelteDate(today);
		monday.setDate(monday.getDate() - diff);
		monday.setHours(0, 0, 0, 0);
		weekStart = monday;
	}

	function openBooking(id: string) {
		selectedBookingId = id;
		showBookingModal = true;
	}

	// -- Layout Constants --
	const HOUR_HEIGHT = 64;
	const SLOT_HEIGHT = HOUR_HEIGHT + 1; // each hour row has 1px border-b
	const TIME_LABEL_WIDTH = 56;
	const HEADER_HEIGHT = 48;
	const dayHeaders = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

	const today = getLondonToday();
	const todayStr = formatDate(today);

	// -- Derived State --

	// Group bookings by London-date key for O(1) lookup in the loop.
	// Using London date (not UTC date) ensures that bookings crossing midnight
	// during BST (e.g. 23:30 UTC → 00:30 BST next day) appear in the correct column.
	const bookingsByDate = $derived(
		bookings.reduce((acc, b) => {
			const dateKey = getLondonDateKey(b.start_time);
			if (!acc.has(dateKey)) acc.set(dateKey, []);
			acc.get(dateKey)!.push(b);
			return acc;
		}, new Map<string, ScheduleBooking[]>())
	);

	const blockersByDate = $derived(
		blockers.reduce((acc, b) => {
			const dateKey = getLondonDateKey(b.start_time);
			if (!acc.has(dateKey)) acc.set(dateKey, []);
			acc.get(dateKey)!.push(b);
			return acc;
		}, new Map<string, TimeBlocker[]>())
	);

	const timeRange = $derived.by(() => {
		if (!weekStart)
			return { minStart: 8, maxEnd: 18, hours: Array.from({ length: 11 }, (_, i) => i + 8) };

		// Find earliest start and latest end across ALL data (hours, bookings, blockers)
		let earliest = 24,
			latest = 0;

		for (const day of getWeekDays(weekStart)) {
			const dateStr = formatDate(day);
			const wh = workingHours.find((w) => w.date === dateStr);
			if (wh?.isOpen) {
				const [sh, sm] = wh.startTime.split(':').map(Number);
				const sf = sh + (sm || 0) / 60;
				if (sf < earliest) earliest = sf;
				const [eh, em] = wh.endTime.split(':').map(Number);
				const ef = eh + (em || 0) / 60;
				if (ef > latest) latest = ef;
			}
			for (const b of bookingsByDate.get(dateStr) || []) {
				const d = new SvelteDate(b.start_time);
				const sf = d.getHours() + d.getMinutes() / 60;
				if (sf < earliest) earliest = sf;
				const ef = sf + b.duration_minutes / 60;
				if (ef > latest) latest = ef;
			}
			for (const b of blockersByDate.get(dateStr) || []) {
				const d = new SvelteDate(b.start_time);
				const sf = d.getHours() + d.getMinutes() / 60;
				if (sf < earliest) earliest = sf;
				const ef = sf + b.duration_minutes / 60;
				if (ef > latest) latest = ef;
			}
		}

		// No data: fall back to 8-18
		if (earliest >= 24 || latest <= 0)
			return { minStart: 8, maxEnd: 18, hours: [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] };

		// Start: 30min buffer before earliest (to show the pre-booking gap)
		// End: floor of latest (the row that CONTAINS the latest item, not the next row)
		let minStart = Math.floor(earliest - 0.5);
		let maxEnd = Math.floor(latest);

		minStart = Math.max(0, minStart);
		maxEnd = Math.min(24, maxEnd);

		const hours: number[] = [];
		for (let h = minStart; h <= maxEnd; h++) hours.push(h);
		return { minStart, maxEnd, hours };
	});

	const isCurrentWeek = $derived(
		weekStart !== undefined &&
			(() => {
				const end = new SvelteDate(weekStart);
				end.setDate(end.getDate() + 6);
				end.setHours(23, 59, 59, 999);
				return today >= weekStart && today <= end;
			})()
	);

	let nowMinutes = $state(getLondonNowMinutes());
	$effect(() => {
		if (pageState !== 'authorized') return;
		const tickId = setInterval(() => {
			nowMinutes = getLondonNowMinutes();
		}, 30_000);
		return () => clearInterval(tickId);
	});
</script>

<svelte:head>
	<script>
		(function () {
			try {
				var token = localStorage.getItem('authToken');
				if (!token) {
					window.location.replace('/login');
					return;
				}
				var payload = JSON.parse(atob(token.split('.')[1]));
				if (payload.exp * 1000 <= Date.now()) {
					window.location.replace('/login');
					return;
				}
				if (payload.role !== 'admin') {
					window.location.replace('/');
				}
			} catch (e) {
				window.location.replace('/login');
			}
		})();
	</script>
</svelte:head>

{#if pageState === 'loading'}
	<div class="mx-auto max-w-7xl space-y-6 p-4 md:p-6">
		<Skeleton class="h-8 w-64" />
		<Skeleton class="h-[calc(100vh-12rem)] w-full" />
	</div>
{:else if pageState === 'authorized' && weekStart}
	<div class="mx-auto flex h-[calc(100vh-4rem)] max-w-7xl flex-col overflow-hidden p-4 md:p-6">
		<!-- NAVIGATION BAR -->
		<div class="mb-4 flex shrink-0 flex-wrap items-center justify-between gap-3">
			<h1 class="font-['Playfair_Display'] text-3xl font-bold">Schedule</h1>
			<div class="flex items-center gap-2">
				<Button variant="outline" size="sm" onclick={() => navigateWeek(-1)}>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<polyline points="15 18 9 12 15 6" />
					</svg>
					<span class="hidden sm:inline">Prev</span>
				</Button>
				<Button variant="outline" size="sm" onclick={goToday}>Today</Button>
				<Button variant="outline" size="sm" onclick={() => navigateWeek(1)}>
					<span class="hidden sm:inline">Next</span>
					<svg
						class="h-4 w-4"
						viewBox="0 0 24 24"
						fill="none"
						stroke="currentColor"
						stroke-width="2"
					>
						<polyline points="9 18 15 12 9 6" />
					</svg>
				</Button>
				<span class="ml-2 text-sm font-medium text-gray-600">{formatWeekLabel(weekStart)}</span>
			</div>
		</div>

		<!-- GRID CONTAINER -->
		{#if loading}
			<div class="flex-1 overflow-hidden rounded-lg border bg-white">
				<Skeleton class="h-full w-full" />
			</div>
		{:else}
			<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
			<div
				bind:this={scrollContainer}
				role="application"
				aria-label="Week schedule"
				class="schedule-wrapper flex-1 overflow-auto rounded-lg border bg-white select-none"
				class:cursor-grab={!isDragging}
				class:cursor-grabbing={isDragging}
				onmousedown={onMouseDown}
				onmousemove={onMouseMove}
				onmouseup={onMouseUp}
				onmouseleave={onMouseUp}
			>
				<div class="relative isolate flex min-w-fit flex-col">
					<!-- STICKY HEADER ROW -->
					<div
						class="sticky top-0 z-30 flex border-b bg-gray-50"
						style="height: {HEADER_HEIGHT}px;"
					>
						<!-- Time Column Spacer -->
						<div class="shrink-0 border-r bg-gray-50" style="width: {TIME_LABEL_WIDTH}px;"></div>

						{#each getWeekDays(weekStart) as day, dayIdx (formatDate(day))}
							{@const isToday = formatDate(day) === todayStr}
							<div
								class="flex min-w-35 flex-1 flex-col items-center justify-center border-l text-center
								       {isToday ? 'bg-blue-200' : ''}"
							>
								<div class="text-xs font-medium text-gray-500">{dayHeaders[dayIdx]}</div>
								<div class="text-sm font-bold {isToday ? 'text-blue-600' : ''}">
									{day.getDate()}
								</div>
							</div>
						{/each}
					</div>

					<!-- BODY: TIME COLUMN + GRID -->
					<div class="relative flex">
						<!-- 1. SOLID TIME PANE (Left Column) -->
						<div
							class="sticky left-0 z-20 shrink-0 border-r border-gray-100 bg-white"
							style="width: {TIME_LABEL_WIDTH}px;"
						>
							{#each timeRange.hours as h (h)}
								<div
									class="flex items-start justify-end border-b border-gray-100 pt-0.5 pr-1.5 text-right text-xs text-gray-400"
									style="height: {SLOT_HEIGHT}px;"
								>
									{formatHour(h)}
								</div>
							{/each}
						</div>

						<!-- 2. GRID CONTAINER (Day Columns) -->
						<div class="relative flex flex-1 flex-col">
							<!-- Grid Rows & Cells -->
							<div class="relative">
								{#each timeRange.hours as h (h)}
									<div class="flex border-b border-gray-100">
										{#each getWeekDays(weekStart) as day (formatDate(day))}
											{@const dateStr = formatDate(day)}
											{@const isToday = dateStr === todayStr}
											{@const dayBookings = bookingsByDate.get(dateStr) || []}
											{@const dayBlockers = blockersByDate.get(dateStr) || []}

											<div
												class="relative min-w-35 flex-1 border-r border-gray-100 {isToday
													? 'bg-blue-50'
													: ''}"
												style="height: {HOUR_HEIGHT}px;"
											>
												<!-- Half-hour dashed line -->
												<div class="half-hour-line absolute inset-x-0 h-px" style="top: 50%;"></div>

												{#each dayBlockers.filter((b) => new SvelteDate(b.start_time).getHours() === h) as b (b.id)}
													{@const startMinutes = new SvelteDate(b.start_time).getMinutes()}
													{@const topOffset = (startMinutes / 60) * HOUR_HEIGHT}
													{@const heightPx = Math.max((b.duration_minutes / 60) * HOUR_HEIGHT, 20)}

													<div
														class="absolute inset-x-0.5 z-[9] overflow-hidden rounded border border-amber-200 bg-amber-50/90 px-1.5 py-0.5 text-left text-xs text-amber-900 shadow-sm backdrop-blur-sm"
														style="top: {topOffset}px; height: {heightPx}px;"
													>
														<div class="flex items-center gap-1">
															<div class="h-2 w-2 shrink-0 rounded-full bg-amber-500"></div>
															<span class="truncate font-medium">{b.description || 'Blocker'}</span>
														</div>
														{#if heightPx > 36}
															<div class="mt-1 truncate text-[10px] opacity-75">
																{formatTimeShort(b.start_time)} · {formatDuration(
																	b.duration_minutes
																)}
															</div>
														{/if}
													</div>
												{/each}

												<!-- BOOKING BLOCKS -->
												{#each dayBookings.filter((b) => new SvelteDate(b.start_time).getHours() === h) as b (b.id)}
													{@const startMinutes = new SvelteDate(b.start_time).getMinutes()}
													{@const topOffset = (startMinutes / 60) * HOUR_HEIGHT}
													{@const heightPx = Math.max((b.duration_minutes / 60) * HOUR_HEIGHT, 20)}
													{@const isCancelled =
														b.status === 'client_cancelled' ||
														b.status === 'we_cancelled' ||
														b.status === 'no_show'}
													{@const hasOverlap =
														isCancelled &&
														dayBookings.some(
															(other) =>
																other.id !== b.id &&
																!(
																	other.status === 'client_cancelled' ||
																	other.status === 'we_cancelled' ||
																	other.status === 'no_show'
																) &&
																bookingsOverlap(b, other)
														)}

													{#if !hasOverlap}
														<button
															type="button"
															aria-label="View booking for {formatUserName(
																b.user?.full_name || 'Guest',
																b.user?.previous_first_name,
																b.user?.previous_last_name
															)}"
															class="absolute inset-x-0.5 z-10 cursor-pointer overflow-hidden rounded border px-1.5 py-0.5 text-left text-xs transition-shadow hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-500"
															style="top: {topOffset}px; height: {heightPx}px; {bookingStyle(
																b.status
															)}"
															onclick={(e) => {
																e.stopPropagation();
																openBooking(b.id);
															}}
														>
															<div class="flex items-center gap-1">
																<div
																	class="h-2 w-2 shrink-0 rounded-full"
																	style="background:{dotColor(b.status)}"
																></div>
																<span class="truncate font-medium"
																	>{formatUserName(
																		b.user?.full_name || 'Guest',
																		b.user?.previous_first_name,
																		b.user?.previous_last_name
																	)}</span
																>
															</div>
															{#if heightPx > 36}
																<div class="line-clamp-3 text-[10px] opacity-75">
																	{getServiceNames(b)}
																</div>
															{/if}
															{#if heightPx > 52}
																<div class="truncate text-[10px] opacity-75">
																	{formatTimeShort(b.start_time)} · {formatDuration(
																		b.duration_minutes
																	)}
																</div>
															{/if}
														</button>
													{/if}
												{/each}
											</div>
										{/each}
									</div>
								{/each}
							</div>

							<!-- 3. OVERLAY LAYER (Lines & Closed Stripes) -->
							<!-- z-5: below bookings (z-10) since grid no longer creates a stacking context -->
							<div class="pointer-events-none absolute inset-0 z-5">
								{#each getWeekDays(weekStart) as day, dayIdx (formatDate(day))}
									{@const dateStr = formatDate(day)}
									{@const wh = workingHours.find((w) => w.date === dateStr)}
									{@const colLeft = `calc(${dayIdx} * (100% / 7))`}
									{@const colWidth = `calc(100% / 7)`}

									{#if !wh?.isOpen}
										<!-- Closed Day Overlay -->
										<div
											class="stripe-bg absolute flex items-center justify-center"
											style="left:{colLeft}; top:0; width:{colWidth}; height:100%;"
										>
											<span
												class="pointer-events-auto rounded bg-black/40 px-3 py-1 text-sm font-semibold text-white"
												>Closed</span
											>
										</div>
									{:else}
										{@const openPx = gridPxFromTime(wh.startTime, timeRange.minStart)}
										{@const closePx = gridPxFromTime(wh.endTime, timeRange.minStart)}

										<!-- Pre-opening gray stripe -->
										{#if openPx > 0}
											<div
												class="stripe-bg absolute"
												style="left:{colLeft}; top:0; width:{colWidth}; height:{openPx}px;"
											></div>
										{/if}

										<!-- Post-closing gray stripe -->
										{#if closePx < timeRange.hours.length * SLOT_HEIGHT}
											<div
												class="stripe-bg absolute"
												style="left:{colLeft}; top:{closePx}px; width:{colWidth}; bottom:0;"
											></div>
										{/if}

										<!-- Opening/Closing Red Lines -->
										<div
											class="absolute border-t-2 border-red-500"
											style="left:{colLeft}; top:{openPx}px; width:{colWidth};"
										></div>
										<div
											class="absolute border-t-2 border-red-500"
											style="left:{colLeft}; top:{closePx}px; width:{colWidth};"
										></div>
									{/if}
								{/each}

								<!-- CURRENT TIME INDICATOR -->
								{#if isCurrentWeek}
									{@const nowTime = `${String(Math.floor(nowMinutes / 60)).padStart(2, '0')}:${String(nowMinutes % 60).padStart(2, '0')}`}
									{@const nowPx = gridPxFromTime(nowTime, timeRange.minStart)}
									{@const todayIdx = getWeekDays(weekStart!).findIndex(
										(d) => formatDate(d) === todayStr
									)}
									{@const colLeft = `calc(${todayIdx} * (100% / 7))`}
									{@const colWidth = `calc(100% / 7)`}

									{#if nowPx >= 0 && nowPx <= timeRange.hours.length * SLOT_HEIGHT && todayIdx !== -1}
										<!-- Dot -->
										<div
											class="absolute h-3 w-3 rounded-full bg-blue-500"
											style="left: calc({colLeft} - 6px); top: {nowPx - 6}px;"
										></div>

										<!-- Line -->
										<!-- FIXED: Restricted width to current column using colLeft/colWidth -->
										<div
											class="time-indicator-line absolute h-0.5"
											style="left: {colLeft}; width: {colWidth}; top: {nowPx}px;"
										></div>
									{/if}
								{/if}
							</div>
						</div>
					</div>
				</div>
			</div>
		{/if}
	</div>

	<BookingModal bind:open={showBookingModal} bookingId={selectedBookingId ?? ''} />
{/if}

<style>
	/* Diagonal stripe pattern for closed hours/days */
	.stripe-bg {
		background: repeating-linear-gradient(
			45deg,
			rgba(156, 163, 175, 0.4),
			rgba(156, 163, 175, 0.4) 10px,
			rgba(209, 213, 219, 0.15) 10px,
			rgba(209, 213, 219, 0.15) 20px
		);
	}

	/* Half-hour dashed line */
	.half-hour-line {
		background: repeating-linear-gradient(
			to right,
			#e5e7eb 0,
			#e5e7eb 4px,
			transparent 4px,
			transparent 16px
		);
	}

	/* Current time indicator line */
	.time-indicator-line {
		background: repeating-linear-gradient(
			to right,
			#3b82f6 0,
			#3b82f6 8px,
			transparent 8px,
			transparent 16px
		);
	}
</style>
