import { SvelteDate } from 'svelte/reactivity';
import { CalendarDate } from '@internationalized/date';
import {
	extractBookedSlots,
	getLunchProtectionForSlots,
	type LunchProtectionResult
} from '$lib/lunchProtection';

/**
 * Returns the UTC datetime string with explicit +00:00 offset, suitable for
 * sending to the backend for scheduling/booking purposes.
 * This is functionally equivalent to `Date.toISOString()` except the suffix
 * is `+00:00` instead of `Z`. Both are valid RFC3339, but Go's `time.Time`
 * JSON unmarshalling prefers the explicit offset form when constructing
 * wall-clock timestamps that should not be shifted by the backend's timezone.
 * The DST correctness of the resulting datetime comes from the CalendarDate
 * / getLocalTimeZone() path used to construct the Date object — not from
 * this formatting function.
 */
export function formatLocalDateTime(date: Date): string {
	const y = date.getUTCFullYear();
	const m = String(date.getUTCMonth() + 1).padStart(2, '0');
	const d = String(date.getUTCDate()).padStart(2, '0');
	const h = String(date.getUTCHours()).padStart(2, '0');
	const min = String(date.getUTCMinutes()).padStart(2, '0');
	const s = String(date.getUTCSeconds()).padStart(2, '0');
	return `${y}-${m}-${d}T${h}:${min}:${s}+00:00`;
}

/**
 * Parses a UTC ISO datetime string (e.g. "2026-06-15T09:00:00Z" or
 * "2026-06-15T09:00:00+00:00") from the backend and returns a Date
 * whose getHours()/getMinutes() in the browser's local timezone
 * reflect the local wall-clock time.
 *
 * The backend stores all times as UTC wall-clock values. This function
 * uses the standard Date parser which correctly interprets the ISO
 * string as UTC and applies the browser timezone for get*() accessors.
 * Example: "2026-06-15T09:00:00Z" → getHours() returns 10 in BST.
 */
export function parseWallClockDate(iso: string): Date {
	return new Date(iso);
}

/**
 * Formats a UTC ISO datetime string as a human-readable time (e.g. "10:00").
 * Always shows the wall-clock time, NOT shifted by timezone.
 */
export function formatWallClockTime(iso: string): string {
	const d = parseWallClockDate(iso);
	return d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false });
}

/**
 * Formats a UTC ISO datetime string as a human-readable date (e.g. "Monday, 15 June 2026").
 * Always shows the wall-clock date, NOT shifted by timezone.
 */
export function formatWallClockDate(iso: string): string {
	const d = parseWallClockDate(iso);
	return d.toLocaleDateString('en-GB', {
		weekday: 'long',
		day: 'numeric',
		month: 'long',
		year: 'numeric'
	});
}

/**
 * Returns a CalendarDate representing today's date in the Europe/London timezone.
 * This ensures "is today" comparisons are correct even during BST (British Summer Time)
 * when there is a window between 00:00-01:00 BST where the UTC date differs from the
 * London date. Avoids using browser-local time which can be wrong in that window.
 */
export function getLondonTodayCalendarDate(): CalendarDate {
	const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
	const [y, m, d] = londonDateStr.split('-').map(Number);
	return new CalendarDate(y, m, d);
}

export interface TimeSlotGroup {
	type: 'available' | 'unavailable';
	startTime: string;
	endTime: string;
	isGrouped?: boolean;
	outOfHours?: boolean;
}

export interface DayHours {
	isOpen: boolean;
	startTime: string;
	endTime: string;
}

export interface DayAvailability {
	isOpen: boolean;
	slots: Array<{ startTime: string; endTime: string }>;
}

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

export function calculateEndTime(startTime: string, durationMinutes: number): string {
	const [hours, minutes] = startTime.split(':').map(Number);
	const totalMinutes = hours * 60 + minutes + durationMinutes;
	return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
}

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

export function calculatePreviousTime(time: string): string {
	const [hours, minutes] = time.split(':').map(Number);
	const totalMinutes = hours * 60 + minutes - 15;
	return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
}

export function normalizeTime(time: string): string {
	const parts = time.split(':');
	if (parts.length < 2) return time.padStart(5, '0');
	return `${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}`;
}

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

export function buildLunchProtection(
	date: CalendarDate,
	workingHours: Record<string, DayHours> | null,
	availableHours: Record<string, DayAvailability> | null,
	duration: number,
	isAdmin: boolean
): Map<string, LunchProtectionResult> {
	if (!workingHours || !availableHours) return new Map();
	const dateStr = date.toString();
	const dayWH = workingHours[dateStr];
	const dayAH = availableHours[dateStr];
	if (!dayWH?.isOpen || !dayAH?.slots) return new Map();

	const existingBookings = extractBookedSlots(dayWH.startTime, dayWH.endTime, dayAH.slots);
	return getLunchProtectionForSlots(
		dayWH.startTime,
		dayWH.endTime,
		existingBookings,
		duration,
		15,
		isAdmin
	);
}

export function generateAvailableTimeSlots(
	date: CalendarDate,
	workingHours: Record<string, DayHours> | null,
	availableHours: Record<string, DayAvailability> | null,
	duration: number,
	protection: Map<string, LunchProtectionResult>
): string[] {
	if (!workingHours || !availableHours) return [];
	const dateStr = date.toString();
	const dayWH = workingHours[dateStr];
	const dayAH = availableHours[dateStr];
	if (!dayWH?.isOpen || !dayAH?.slots) {
		return [];
	}

	const slots: string[] = [];
	const todayCal = getLondonTodayCalendarDate();
	const isToday = date.compare(todayCal) === 0;

	for (const slot of dayAH.slots) {
		const [startHour, startMinute] = slot.startTime.split(':').map(Number);
		const [endHour, endMinute] = slot.endTime.split(':').map(Number);
		let startTotalMinutes = Math.ceil((startHour * 60 + startMinute) / 15) * 15;
		const endTotalMinutes = endHour * 60 + endMinute;

		if (isToday) {
			const now = new Date();
			const londonTimeStr = now.toLocaleTimeString('en-GB', {
				timeZone: 'Europe/London',
				hour: '2-digit',
				minute: '2-digit',
				hour12: false
			});
			const [londonHours, londonMinutes] = londonTimeStr.split(':').map(Number);
			const currentMinutes = londonHours * 60 + londonMinutes;
			const minimumStart = Math.ceil((currentMinutes + 60) / 15) * 15;
			startTotalMinutes = Math.max(startTotalMinutes, minimumStart);
		}

		for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 15) {
			if (minutes + duration <= endTotalMinutes) {
				const timeStr = `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}`;
				const p = protection.get(timeStr);
				if (!p || !p.isBlocked) {
					slots.push(timeStr);
				}
			}
		}
	}
	return slots;
}

export function generateGroupedTimeSlots(
	date: CalendarDate,
	workingHours: Record<string, DayHours> | null,
	availableHours: Record<string, DayAvailability> | null,
	duration: number,
	protection: Map<string, LunchProtectionResult>
): TimeSlotGroup[] {
	if (!workingHours) {
		return [];
	}
	const dateStr = date.toString();
	const dayWH = workingHours[dateStr];
	if (!dayWH || !dayWH.isOpen) {
		return [];
	}

	const grouped: TimeSlotGroup[] = [];
	const [startHour, startMinute] = dayWH.startTime.split(':').map(Number);
	const [endHour, endMinute] = dayWH.endTime.split(':').map(Number);
	let startTotalMinutes = startHour * 60 + startMinute;
	const endTotalMinutes = endHour * 60 + endMinute;

	const todayCal = getLondonTodayCalendarDate();
	if (date.compare(todayCal) === 0) {
		const now = new Date();
		const londonTimeStr = now.toLocaleTimeString('en-GB', {
			timeZone: 'Europe/London',
			hour: '2-digit',
			minute: '2-digit',
			hour12: false
		});
		const [londonHours, londonMinutes] = londonTimeStr.split(':').map(Number);
		const currentMinutes = londonHours * 60 + londonMinutes;
		startTotalMinutes = Math.max(startTotalMinutes, Math.ceil((currentMinutes + 15) / 15) * 15);
	}

	const availableSlots = generateAvailableTimeSlots(
		date,
		workingHours,
		availableHours,
		duration,
		protection
	);

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

	for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 15) {
		const timeStr = `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}`;
		const p = protection.get(timeStr);
		const isAvailable = availableSlots.includes(timeStr) && !p?.isBlocked;

		if (isAvailable) {
			if (currentUnavailableStart !== null) {
				const unavailStart = lastAvailableEndTime || currentUnavailableStart;
				const groupEnd = calculatePreviousTime(timeStr);
				if (timeToMinutes(unavailStart) < timeToMinutes(groupEnd)) {
					grouped.push({
						type: 'unavailable',
						startTime: unavailStart,
						endTime: groupEnd,
						isGrouped: true
					});
				}
				currentUnavailableStart = null;
			}
			const slotEnd = calculateEndTime(timeStr, duration);
			lastAvailableEndTime = slotEnd;
			grouped.push({ type: 'available', startTime: timeStr, endTime: slotEnd });
			if (timeToMinutes(slotEnd) >= endTotalMinutes) break;
		} else {
			if (currentUnavailableStart === null) {
				currentUnavailableStart = timeStr;
			}
		}
	}

	if (currentUnavailableStart !== null) {
		const lastAvail = grouped.filter((s) => s.type === 'available').pop();
		const lastAvailEnd = lastAvail ? timeToMinutes(lastAvail.endTime) : 0;
		const unavailStartMin = timeToMinutes(currentUnavailableStart);
		if (unavailStartMin < endTotalMinutes && lastAvailEnd < endTotalMinutes) {
			grouped.push({
				type: 'unavailable',
				startTime: lastAvail ? lastAvail.endTime : currentUnavailableStart,
				endTime: normalizeTime(dayWH.endTime),
				isGrouped: true
			});
		}
	}

	return grouped;
}
