<script lang="ts">
	// @ts-nocheck
	import type {
		Collection,
		CollectionItineraryItem,
		CollectionItineraryDay,
		Location,
		Transportation,
		Lodging,
		Note,
		Checklist
	} from '$lib/types';
	// @ts-ignore
	import { DateTime } from 'luxon';
	import { dndzone, TRIGGERS, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
	import { flip } from 'svelte/animate';
	import CalendarBlank from '~icons/mdi/calendar-blank';
	import Bed from '~icons/mdi/bed';
	import Info from '~icons/mdi/information';
	import Plus from '~icons/mdi/plus';
	import LocationCard from '$lib/components/cards/LocationCard.svelte';
	import TransportationCard from '$lib/components/cards/TransportationCard.svelte';
	import LodgingCard from '$lib/components/cards/LodgingCard.svelte';
	import NoteCard from '$lib/components/cards/NoteCard.svelte';
	import ChecklistCard from '$lib/components/cards/ChecklistCard.svelte';
	import NewLocationModal from '$lib/components/locations/LocationModal.svelte';
	import LodgingModal from '../lodging/LodgingModal.svelte';
	import TransportationModal from '../transportation/TransportationModal.svelte';
	import NoteModal from '$lib/components/NoteModal.svelte';
	import ChecklistModal from '$lib/components/ChecklistModal.svelte';
	import ItineraryLinkModal from '$lib/components/collections/ItineraryLinkModal.svelte';
	import ItineraryDayPickModal from '$lib/components/collections/ItineraryDayPickModal.svelte';
	import Car from '~icons/mdi/car';
	import Walk from '~icons/mdi/walk';
	import LocationMarker from '~icons/mdi/map-marker';
	import { t } from 'svelte-i18n';
	import { addToast } from '$lib/toasts';
	import Globe from '~icons/mdi/globe';
	import { TRANSPORTATION_TYPES_ICONS } from '$lib';

	export let collection: Collection;
	export let user: any;
	// Whether the current user can modify this collection (owner or shared user)
	export let canModify: boolean = false;

	const flipDurationMs = 200;

	// Extended itinerary item with resolved object
	type ResolvedItineraryItem = CollectionItineraryItem & {
		resolvedObject: Location | Transportation | Lodging | Note | Checklist | null;
	};

	// Group itinerary items by day
	type DayGroup = {
		date: string;
		displayDate: string;
		items: ResolvedItineraryItem[];
		preTimelineLodging: ResolvedItineraryItem | null; // Checkout-side lodging shown before timeline
		postTimelineLodging: ResolvedItineraryItem | null; // Checkin-side lodging shown after timeline
		overnightLodging: Lodging[]; // Lodging where guest is staying overnight (not check-in day)
		globalDatedItems: ResolvedItineraryItem[]; // Trip-wide items that still carry a date
		dayMetadata: CollectionItineraryDay | null; // Day name and description
	};

	type DayTemperature = {
		available: boolean;
		temperature_c: number | null;
	};

	$: days = groupItemsByDay(collection);
	$: unscheduledItems = getUnscheduledItems(collection);
	// Trip-wide (global) itinerary items
	$: globalItems = (collection.itinerary || [])
		.filter((it) => it.is_global)
		.map((it) => resolveItineraryItem(it, collection))
		.sort((a, b) => a.order - b.order);

	// Auto-generate state
	let isAutoGenerating = false;

	// Saving state for itinerary reorders. When true, disable drag interactions.
	let isSavingOrder = false;
	// Which day (ISO date string) is currently being saved. Used to show per-day spinner.
	let savingDay: string | null = null;
	let dayTemperatures: Record<string, DayTemperature> = {};
	let activeTemperatureFetchVersion = 0;

	// Check if auto-generate is available (only for users with modify permission)
	$: canAutoGenerate =
		canModify && collection.itinerary?.length === 0 && hasDatedRecords(collection);

	function hasDatedRecords(collection: Collection): boolean {
		// Check if collection has any dated records
		const hasVisits =
			collection.locations?.some((loc) => loc.visits?.some((v) => v.start_date)) || false;
		const hasLodging = collection.lodging?.some((l) => l.check_in) || false;
		const hasTransportation = collection.transportations?.some((t) => t.date) || false;
		const hasNotes = collection.notes?.some((n) => n.date) || false;
		const hasChecklists = collection.checklists?.some((c) => c.date) || false;

		return hasVisits || hasLodging || hasTransportation || hasNotes || hasChecklists;
	}

	async function handleAutoGenerate() {
		if (!canAutoGenerate || isAutoGenerating) return;

		isAutoGenerating = true;

		try {
			const response = await fetch('/api/itineraries/auto-generate/', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					collection_id: collection.id
				})
			});

			if (!response.ok) {
				const error = await response.json();
				throw new Error(error.detail || error.error || 'Failed to auto-generate itinerary');
			}

			const data = await response.json();

			// Refresh the page to load the updated itinerary
			window.location.reload();
		} catch (error) {
			console.error('Auto-generate error:', error);
			alert(error.message || 'Failed to auto-generate itinerary');
			isAutoGenerating = false;
		}
	}

	function handleRemoveItineraryItem(event: CustomEvent<CollectionItineraryItem>) {
		const itemToRemove = event.detail;
		collection.itinerary = collection.itinerary?.filter((it) => it.id !== itemToRemove.id);
		days = groupItemsByDay(collection);
	}

	let locationToEdit: Location | null = null;
	let isLocationModalOpen: boolean = false;
	function handleEditLocation(event: CustomEvent<Location>) {
		locationToEdit = event.detail;
		isLocationModalOpen = true;
	}

	function handleDuplicateLocation(event: CustomEvent<Location>) {
		const duplicated = event.detail;
		if (!duplicated || !duplicated.id) return;

		const collectionId = collection?.id ? String(collection.id) : null;
		if (collectionId) {
			const existingCollections = Array.isArray((duplicated as any).collections)
				? (duplicated as any).collections.map((id: string) => String(id))
				: [];
			if (!existingCollections.includes(collectionId)) {
				(duplicated as any).collections = [...existingCollections, collectionId];
			}
		}

		collection = {
			...collection,
			locations: [
				duplicated,
				...(collection.locations || []).filter((loc) => String(loc.id) !== String(duplicated.id))
			]
		};

		days = groupItemsByDay(collection);
		unscheduledItems = getUnscheduledItems(collection);
	}

	let lodgingToEdit: Lodging | null = null;
	let isLodgingModalOpen: boolean = false;
	function handleEditLodging(event: CustomEvent<Lodging>) {
		lodgingToEdit = event.detail;
		isLodgingModalOpen = true;
	}

	let transportationToEdit: Transportation | null = null;
	let isTransportationModalOpen: boolean = false;
	function handleEditTransportation(event: CustomEvent<Transportation>) {
		transportationToEdit = event.detail;
		isTransportationModalOpen = true;
	}

	function handleEditNote(event: CustomEvent<Note>) {
		noteToEdit = event.detail;
		isNoteModalOpen = true;
		pendingAddDate = null;
	}

	function handleEditChecklist(event: CustomEvent<Checklist>) {
		checklistToEdit = event.detail;
		isChecklistModalOpen = true;
		pendingAddDate = null;
	}

	/**
	 * Move an item to the global (trip-wide) itinerary.
	 * Removes all dated entries for this item and adds it to the global view instead.
	 */
	async function moveItemToGlobal(objectType: string, objectId: string) {
		if (!collection.id) return;

		try {
			// Remove all dated itinerary entries for this item
			const entriesToRemove = (collection.itinerary || [])
				.filter((it) => it.object_id === objectId && it.date && !it.is_global)
				.map((it) => it.id);

			// Delete the dated entries, but preserve visits
			for (const entryId of entriesToRemove) {
				await fetch(`/api/itineraries/${entryId}?preserve_visits=true`, { method: 'DELETE' });
			}

			// Add as global if not already there
			const alreadyGlobal = (collection.itinerary || []).some(
				(it) => it.object_id === objectId && it.is_global
			);

			if (!alreadyGlobal) {
				const order = globalItems.length;
				const res = await fetch('/api/itineraries/', {
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({
						collection: collection.id,
						content_type: objectType,
						object_id: objectId,
						is_global: true,
						order
					})
				});

				if (!res.ok) throw new Error('Failed to add to global itinerary');
				const created = await res.json();
				collection.itinerary = [...(collection.itinerary || []), created];
			}

			// Remove dated entries from local state
			collection.itinerary = (collection.itinerary || []).filter(
				(it) => !entriesToRemove.includes(it.id)
			);

			// Refresh reactive variables
			days = groupItemsByDay(collection);
			globalItems = (collection.itinerary || [])
				.filter((it) => it.is_global)
				.map((it) => resolveItineraryItem(it, collection))
				.sort((a, b) => a.order - b.order);

			addToast('success', $t('itinerary.moved_to_trip_context'));
		} catch (error) {
			console.error('Error moving item to context:', error);
			addToast('error', $t('itinerary.failed_to_move_to_trip_context'));
		}
	}

	/**
	 * Add an unscheduled item directly to the global (trip-wide) itinerary.
	 */
	async function addUnscheduledItemToGlobal(type: string, itemId: string) {
		if (!collection.id) return;

		try {
			// Check if already in global view
			const alreadyGlobal = (collection.itinerary || []).some(
				(it) => it.object_id === itemId && it.is_global
			);

			if (alreadyGlobal) {
				addToast('info', $t('itinerary.item_already_in_trip_context'));
				return;
			}

			const order = globalItems.length;
			const res = await fetch('/api/itineraries/', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					collection: collection.id,
					content_type: type,
					object_id: itemId,
					is_global: true,
					order
				})
			});

			if (!res.ok) throw new Error('Failed to add to global itinerary');

			const created = await res.json();
			collection.itinerary = [...(collection.itinerary || []), created];

			// Remove from unscheduled by moving it to itinerary
			unscheduledItems = unscheduledItems.filter((item) => !(item.item.id === itemId));

			// Refresh global items
			globalItems = (collection.itinerary || [])
				.filter((it) => it.is_global)
				.map((it) => resolveItineraryItem(it, collection))
				.sort((a, b) => a.order - b.order);

			addToast('success', $t('itinerary.added_to_trip_context'));
		} catch (error) {
			console.error('Error adding item to global:', error);
			addToast('error', $t('itinerary.failed_to_add_to_trip_context'));
		}
	}

	function handleItemDelete(event: CustomEvent<CollectionItineraryItem | string | number>) {
		const payload = event.detail;

		// Support both cases:
		// 1) Card components dispatch a primitive id (string/number) when deleting the underlying object
		// 2) Some callers may dispatch a full itinerary item object
		if (typeof payload === 'string' || typeof payload === 'number') {
			const objectId = payload;

			// Remove any itinerary entries that reference this object
			collection.itinerary = collection.itinerary?.filter(
				(it) => String(it.object_id) !== String(objectId)
			);

			// Remove the object from all possible collections (location/transportation/lodging/note/checklist)
			if (collection.locations) {
				collection.locations = collection.locations.filter(
					(loc) => String(loc.id) !== String(objectId)
				);
			}
			if (collection.transportations) {
				collection.transportations = collection.transportations.filter(
					(t) => String(t.id) !== String(objectId)
				);
			}
			if (collection.lodging) {
				collection.lodging = collection.lodging.filter((l) => String(l.id) !== String(objectId));
			}
			if (collection.notes) {
				collection.notes = collection.notes.filter((n) => String(n.id) !== String(objectId));
			}
			if (collection.checklists) {
				collection.checklists = collection.checklists.filter(
					(c) => String(c.id) !== String(objectId)
				);
			}

			// Re-group days and return
			days = groupItemsByDay(collection);
			return;
		}

		// Otherwise expect a full itinerary-like object
		const itemToDelete = payload as CollectionItineraryItem;
		collection.itinerary = collection.itinerary?.filter((it) => it.id !== itemToDelete.id);
		// Also remove the associated object from the collection
		const objectType = itemToDelete.item?.type || '';
		if (objectType === 'location') {
			collection.locations = collection.locations?.filter(
				(loc) => loc.id !== itemToDelete.object_id
			);
		} else if (objectType === 'transportation') {
			collection.transportations = collection.transportations?.filter(
				(t) => t.id !== itemToDelete.object_id
			);
		} else if (objectType === 'lodging') {
			collection.lodging = collection.lodging?.filter((l) => l.id !== itemToDelete.object_id);
		} else if (objectType === 'note') {
			collection.notes = collection.notes?.filter((n) => n.id !== itemToDelete.object_id);
		} else if (objectType === 'checklist') {
			collection.checklists = collection.checklists?.filter((c) => c.id !== itemToDelete.object_id);
		}
		days = groupItemsByDay(collection);
	}

	let locationBeingUpdated: Location | null = null;
	let lodgingBeingUpdated: Lodging | null = null;
	let transportationBeingUpdated: Transportation | null = null;

	let isNoteModalOpen = false;
	let isChecklistModalOpen = false;
	let isItineraryLinkModalOpen = false;

	let noteToEdit: Note | null = null;
	let checklistToEdit: Checklist | null = null;

	// Store the target date and display date for the link modal
	let linkModalTargetDate: string = '';
	let linkModalDisplayDate: string = '';

	// Day picker modal state for unscheduled items
	let isDayPickModalOpen = false;
	let dayPickItemToAdd: { type: string; item: any } | null = null;
	let dayPickScheduledDates: string[] = [];
	let dayPickSourceVisit: { id: string; start_date: string } | null = null;
	let dayPickSourceItineraryItemId: string | null = null; // Track which specific itinerary item is being moved

	// When opening a "create new item" modal we store the target date here
	let pendingAddDate: string | null = null;
	let pendingLodgingAddDate: string | null = null;
	// Track if we've already added this location to the itinerary
	let addedToItinerary: Set<string> = new Set();

	function normalizeDateOnly(value: string | null | undefined): string | null {
		if (!value) return null;
		return value.includes('T') ? value.split('T')[0] : value;
	}

	function getTransportationIcon(type: string | null | undefined) {
		if (type && type in TRANSPORTATION_TYPES_ICONS) {
			return TRANSPORTATION_TYPES_ICONS[type as keyof typeof TRANSPORTATION_TYPES_ICONS];
		}
		return '🚗';
	}

	function formatTransportationDistance(distanceKm: number | null | undefined): string | null {
		if (distanceKm === null || distanceKm === undefined || Number.isNaN(distanceKm)) return null;
		if (distanceKm < 10) return `${distanceKm.toFixed(1)} km`;
		return `${Math.round(distanceKm)} km`;
	}

	const WALKING_SPEED_KMH = 5;
	const DRIVING_SPEED_KMH = 60;
	const WALKING_THRESHOLD_MINUTES = 20;
	const ROUTE_METRICS_BATCH_SIZE = 50;

	function toRadians(degrees: number): number {
		return (degrees * Math.PI) / 180;
	}

	function normalizeCoordinate(value: number | string | null | undefined): number | null {
		if (typeof value === 'number') {
			return Number.isFinite(value) ? value : null;
		}

		if (typeof value === 'string') {
			const trimmed = value.trim();
			if (!trimmed) return null;

			const parsed = Number(trimmed);
			return Number.isFinite(parsed) ? parsed : null;
		}

		return null;
	}

	function haversineDistanceKm(from: Location, to: Location): number | null {
		const fromLatitude = normalizeCoordinate(from.latitude);
		const fromLongitude = normalizeCoordinate(from.longitude);
		const toLatitude = normalizeCoordinate(to.latitude);
		const toLongitude = normalizeCoordinate(to.longitude);

		if (
			fromLatitude === null ||
			fromLongitude === null ||
			toLatitude === null ||
			toLongitude === null
		) {
			return null;
		}

		const earthRadiusKm = 6371;
		const latDelta = toRadians(toLatitude - fromLatitude);
		const lonDelta = toRadians(toLongitude - fromLongitude);
		const fromLat = toRadians(fromLatitude);
		const toLat = toRadians(toLatitude);

		const a =
			Math.sin(latDelta / 2) * Math.sin(latDelta / 2) +
			Math.cos(fromLat) * Math.cos(toLat) * Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2);
		const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

		const distanceKm = earthRadiusKm * c;
		return Number.isFinite(distanceKm) ? distanceKm : null;
	}

	function haversineDistanceBetweenCoordinates(
		from: { latitude: number; longitude: number },
		to: { latitude: number; longitude: number }
	): number {
		const earthRadiusKm = 6371;
		const latDelta = toRadians(to.latitude - from.latitude);
		const lonDelta = toRadians(to.longitude - from.longitude);
		const fromLat = toRadians(from.latitude);
		const toLat = toRadians(to.latitude);

		const a =
			Math.sin(latDelta / 2) * Math.sin(latDelta / 2) +
			Math.cos(fromLat) * Math.cos(toLat) * Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2);
		const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
		return earthRadiusKm * c;
	}

	function formatTravelDuration(minutes: number): string {
		const totalMinutes = Math.max(0, Math.round(minutes));
		const hours = Math.floor(totalMinutes / 60);
		const remainingMinutes = totalMinutes % 60;

		if (hours === 0) return `${remainingMinutes}m`;
		if (remainingMinutes === 0) return `${hours}h`;
		return `${hours}h ${remainingMinutes}m`;
	}

	type LocationConnector = {
		distanceLabel: string;
		durationLabel: string;
		mode: 'walking' | 'driving';
		unavailable?: boolean;
	};

	type ConnectorPair = {
		key: string;
		from: {
			latitude: number;
			longitude: number;
		};
		to: {
			latitude: number;
			longitude: number;
		};
	};

	type ConnectableItemType = 'location' | 'lodging' | 'transportation';
	type TransportationCoordinateRole = 'origin' | 'destination';

	type RouteMetricResult = {
		distance_label?: string;
		duration_label?: string;
		mode?: 'walking' | 'driving';
		distance_km?: number;
		duration_minutes?: number;
	};

	let connectorMetricsMap: Record<string, LocationConnector> = {};
	let activeConnectorFetchVersion = 0;

	function isConnectableItemType(type: string): type is ConnectableItemType {
		return type === 'location' || type === 'lodging' || type === 'transportation';
	}

	function getCoordinatesFromItineraryItem(
		item: ResolvedItineraryItem | null,
		transportationRole: TransportationCoordinateRole = 'origin'
	): { latitude: number; longitude: number } | null {
		if (!item) return null;

		const itemType = item.item?.type || '';
		if (!isConnectableItemType(itemType)) return null;

		if (itemType === 'transportation') {
			const transportation = item.resolvedObject as Transportation | null;
			if (!transportation) return null;

			const latitude = normalizeCoordinate(
				transportationRole === 'origin'
					? transportation.origin_latitude
					: transportation.destination_latitude
			);
			const longitude = normalizeCoordinate(
				transportationRole === 'origin'
					? transportation.origin_longitude
					: transportation.destination_longitude
			);
			if (latitude === null || longitude === null) return null;

			return { latitude, longitude };
		}

		const resolvedObj = item.resolvedObject as Location | Lodging | null;
		if (!resolvedObj) return null;

		const latitude = normalizeCoordinate(resolvedObj.latitude);
		const longitude = normalizeCoordinate(resolvedObj.longitude);
		if (latitude === null || longitude === null) return null;

		return { latitude, longitude };
	}

	function getFirstConnectableItem(items: ResolvedItineraryItem[]): ResolvedItineraryItem | null {
		for (const item of items) {
			if (item?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) continue;
			if (isConnectableItemType(item.item?.type || '')) return item;
		}

		return null;
	}

	function getDayWeatherAnchor(day: DayGroup): { latitude: number; longitude: number } | null {
		for (const item of day.items) {
			if (item?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) continue;
			const coordinates = getCoordinatesFromItineraryItem(item);
			if (coordinates) return coordinates;
		}

		const boundaryCandidates = [day.preTimelineLodging, day.postTimelineLodging];
		for (const boundary of boundaryCandidates) {
			const coordinates = getCoordinatesFromItineraryItem(boundary);
			if (coordinates) return coordinates;
		}

		return null;
	}

	async function loadDayTemperatures(dayGroups: DayGroup[], fetchVersion: number) {
		if (dayGroups.length === 0) {
			if (fetchVersion === activeTemperatureFetchVersion) {
				dayTemperatures = {};
			}
			return;
		}

		const payloadDays = dayGroups
			.map((day) => {
				const anchor = getDayWeatherAnchor(day);
				if (!anchor) return null;
				return {
					date: day.date,
					latitude: anchor.latitude,
					longitude: anchor.longitude
				};
			})
			.filter((entry): entry is { date: string; latitude: number; longitude: number } => !!entry);

		if (payloadDays.length === 0) {
			if (fetchVersion === activeTemperatureFetchVersion) {
				dayTemperatures = {};
			}
			return;
		}

		try {
			const response = await fetch('/api/weather/daily-temperatures/', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({ days: payloadDays })
			});

			if (!response.ok) throw new Error('Failed to load day temperatures');

			const data = await response.json();
			if (fetchVersion !== activeTemperatureFetchVersion) return;

			const nextMap: Record<string, DayTemperature> = {};
			for (const result of data?.results || []) {
				if (!result?.date) continue;
				nextMap[result.date] = {
					available: !!result.available,
					temperature_c:
						typeof result.temperature_c === 'number' ? result.temperature_c : null
				};
			}

			dayTemperatures = nextMap;
		} catch (error) {
			if (fetchVersion !== activeTemperatureFetchVersion) return;
			console.error('Failed to fetch day temperatures:', error);
			dayTemperatures = {};
		}
	}

	function getLastConnectableItem(items: ResolvedItineraryItem[]): ResolvedItineraryItem | null {
		for (let index = items.length - 1; index >= 0; index -= 1) {
			const item = items[index];
			if (item?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) continue;
			if (isConnectableItemType(item.item?.type || '')) return item;
		}

		return null;
	}

	function getResolvedScheduledLodgingItem(
		collection: Collection,
		lodging: Lodging
	): ResolvedItineraryItem | null {
		const sourceItineraryItem = collection.itinerary?.find((item) => {
			const objectType = item.item?.type || '';
			return objectType === 'lodging' && item.object_id === lodging.id;
		});

		if (!sourceItineraryItem) return null;

		return {
			...sourceItineraryItem,
			resolvedObject: lodging
		};
	}

	function getDirectionalBoundaryLodging(
		collection: Collection,
		dateISO: string
	): {
		preTimelineLodging: ResolvedItineraryItem | null;
		postTimelineLodging: ResolvedItineraryItem | null;
	} {
		const targetDate = DateTime.fromISO(dateISO).startOf('day');
		let preTimelineLodging: ResolvedItineraryItem | null = null;
		let postTimelineLodging: ResolvedItineraryItem | null = null;

		for (const lodging of collection.lodging || []) {
			if (!lodging.check_in || !lodging.check_out) continue;

			const checkIn = DateTime.fromISO(lodging.check_in.split('T')[0]).startOf('day');
			const checkOut = DateTime.fromISO(lodging.check_out.split('T')[0]).startOf('day');

			const isPreTimelineContext = targetDate > checkIn && targetDate <= checkOut;
			const isPostTimelineContext = targetDate >= checkIn && targetDate < checkOut;

			if (!isPreTimelineContext && !isPostTimelineContext) continue;

			const resolvedLodgingItem = getResolvedScheduledLodgingItem(collection, lodging);
			if (!resolvedLodgingItem) continue;

			if (!preTimelineLodging && isPreTimelineContext) {
				preTimelineLodging = resolvedLodgingItem;
			}

			if (!postTimelineLodging && isPostTimelineContext) {
				postTimelineLodging = resolvedLodgingItem;
			}

			if (preTimelineLodging && postTimelineLodging) break;
		}

		return { preTimelineLodging, postTimelineLodging };
	}

	function getDayTimelineItems(day: DayGroup): ResolvedItineraryItem[] {
		const boundaryIds = new Set(
			[day.preTimelineLodging?.id, day.postTimelineLodging?.id].filter(
				(id): id is string => !!id
			)
		);

		if (boundaryIds.size === 0) return day.items;
		return day.items.filter((item) => !boundaryIds.has(item.id));
	}

	function shouldShowOvernightSummary(day: DayGroup): boolean {
		return (
			day.overnightLodging.length > 0 &&
			!day.preTimelineLodging?.resolvedObject &&
			!day.postTimelineLodging?.resolvedObject
		);
	}

	function reinsertBoundaryLodgingItems(
		day: DayGroup,
		timelineItems: ResolvedItineraryItem[]
	): ResolvedItineraryItem[] {
		const boundaryCandidates = [day.preTimelineLodging, day.postTimelineLodging].filter(
			(item, index, list): item is ResolvedItineraryItem =>
				!!item && list.findIndex((candidate) => candidate?.id === item.id) === index
		);

		if (boundaryCandidates.length === 0) return timelineItems;

		let restoredItems = [...timelineItems];

		for (const boundaryItem of boundaryCandidates) {
			const existedOnThisDay = day.items.some((item) => item.id === boundaryItem.id);
			if (!existedOnThisDay) continue;
			if (restoredItems.some((item) => item.id === boundaryItem.id)) continue;

			const previousBoundaryIndex = day.items.findIndex((item) => item.id === boundaryItem.id);
			const insertIndex =
				previousBoundaryIndex >= 0
					? Math.min(previousBoundaryIndex, restoredItems.length)
					: restoredItems.length;

			restoredItems = [
				...restoredItems.slice(0, insertIndex),
				boundaryItem,
				...restoredItems.slice(insertIndex)
			];
		}

		return restoredItems;
	}

	function getLocationConnectorKey(
		currentItem: ResolvedItineraryItem,
		nextItem: ResolvedItineraryItem | null
	): string | null {
		if (!nextItem) return null;
		if (!currentItem?.id || !nextItem?.id) return null;
		return `${currentItem.id}:${nextItem.id}`;
	}

	function getConnectorPair(
		currentItem: ResolvedItineraryItem,
		nextItem: ResolvedItineraryItem | null
	): ConnectorPair | null {
		if (!nextItem) return null;

		const currentType = currentItem.item?.type || '';
		const nextType = nextItem.item?.type || '';
		if (!isConnectableItemType(currentType) || !isConnectableItemType(nextType)) return null;

		const fromCoordinates = getCoordinatesFromItineraryItem(
			currentItem,
			currentType === 'transportation' ? 'destination' : 'origin'
		);
		const toCoordinates = getCoordinatesFromItineraryItem(
			nextItem,
			'origin'
		);
		if (!fromCoordinates || !toCoordinates) return null;

		const key = getLocationConnectorKey(currentItem, nextItem);
		if (!key) return null;

		return {
			key,
			from: fromCoordinates,
			to: toCoordinates
		};
	}

	function findNextConnectableItem(
		items: ResolvedItineraryItem[],
		currentIndex: number
	): ResolvedItineraryItem | null {
		for (let index = currentIndex + 1; index < items.length; index += 1) {
			const candidate = items[index];
			if (candidate?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) {
				continue;
			}
			if (isConnectableItemType(candidate?.item?.type || '')) {
				return candidate;
			}
		}

		return null;
	}

	function getConnectorPairs(dayGroups: DayGroup[]): ConnectorPair[] {
		const pairs: ConnectorPair[] = [];
		const seenKeys = new Set<string>();

		function pushPair(pair: ConnectorPair | null) {
			if (!pair || seenKeys.has(pair.key)) return;
			seenKeys.add(pair.key);
			pairs.push(pair);
		}

		for (const dayGroup of dayGroups) {
			const dayTimelineItems = getDayTimelineItems(dayGroup);
			const firstConnectableItem = getFirstConnectableItem(dayGroup.items);
			const lastConnectableItem = getLastConnectableItem(dayGroup.items);

			if (dayGroup.preTimelineLodging && firstConnectableItem) {
				pushPair(getConnectorPair(dayGroup.preTimelineLodging, firstConnectableItem));
			}

			for (let index = 0; index < dayTimelineItems.length - 1; index += 1) {
				const currentItem = dayTimelineItems[index];
				if (currentItem?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) {
					continue;
				}
				const nextConnectableItem = findNextConnectableItem(dayTimelineItems, index);
				pushPair(getConnectorPair(currentItem, nextConnectableItem));
			}

			if (dayGroup.postTimelineLodging && lastConnectableItem) {
				pushPair(getConnectorPair(lastConnectableItem, dayGroup.postTimelineLodging));
			}
		}

		return pairs;
	}

	function chunkConnectorPairs(pairs: ConnectorPair[], chunkSize: number): ConnectorPair[][] {
		const chunks: ConnectorPair[][] = [];
		for (let index = 0; index < pairs.length; index += chunkSize) {
			chunks.push(pairs.slice(index, index + chunkSize));
		}
		return chunks;
	}

	function formatDistanceLabel(distanceKm: number): string {
		if (distanceKm < 10) return `${distanceKm.toFixed(1)} km`;
		return `${Math.round(distanceKm)} km`;
	}

	function normalizeRouteMetricResult(result: RouteMetricResult | null): LocationConnector | null {
		if (!result || (result.mode !== 'walking' && result.mode !== 'driving')) return null;

		let distanceLabel = result.distance_label;
		if (
			!distanceLabel &&
			typeof result.distance_km === 'number' &&
			Number.isFinite(result.distance_km)
		) {
			distanceLabel = formatDistanceLabel(Math.max(0, result.distance_km));
		}

		let durationLabel = result.duration_label;
		if (
			!durationLabel &&
			typeof result.duration_minutes === 'number' &&
			Number.isFinite(result.duration_minutes)
		) {
			durationLabel = formatTravelDuration(Math.max(0, result.duration_minutes));
		}

		if (!distanceLabel || !durationLabel) return null;

		return {
			distanceLabel,
			durationLabel,
			mode: result.mode
		};
	}

	async function fetchRouteMetricChunk(
		chunk: ConnectorPair[]
	): Promise<Record<string, LocationConnector>> {
		const response = await fetch('/api/route-metrics/query/', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				pairs: chunk.map((pair) => ({
					from: pair.from,
					to: pair.to
				}))
			})
		});

		if (!response.ok) {
			throw new Error(`Route metrics request failed (${response.status})`);
		}

		const payload = await response.json();
		const results = Array.isArray(payload?.results) ? payload.results : [];
		const normalizedChunk: Record<string, LocationConnector> = {};

		for (let index = 0; index < chunk.length; index += 1) {
			const connector = normalizeRouteMetricResult(results[index] || null);
			if (!connector) continue;
			normalizedChunk[chunk[index].key] = connector;
		}

		return normalizedChunk;
	}

	async function loadConnectorMetrics(connectorPairs: ConnectorPair[], fetchVersion: number) {
		if (connectorPairs.length === 0) {
			connectorMetricsMap = {};
			return;
		}

		try {
			const chunks = chunkConnectorPairs(connectorPairs, ROUTE_METRICS_BATCH_SIZE);
			const responses = await Promise.all(chunks.map((chunk) => fetchRouteMetricChunk(chunk)));

			if (fetchVersion !== activeConnectorFetchVersion) return;

			const mergedMap: Record<string, LocationConnector> = {};
			for (const chunkMap of responses) {
				Object.assign(mergedMap, chunkMap);
			}

			connectorMetricsMap = mergedMap;
		} catch (error) {
			if (fetchVersion !== activeConnectorFetchVersion) return;
			console.error('Failed to fetch connector route metrics:', error);
			if (connectorPairs.length === 0) {
				connectorMetricsMap = {};
			}
		}
	}

	$: {
		const connectorPairs = getConnectorPairs(days);
		activeConnectorFetchVersion += 1;
		const fetchVersion = activeConnectorFetchVersion;
		loadConnectorMetrics(connectorPairs, fetchVersion);
	}

	$: {
		const daySnapshot = days
			.map((day) => `${day.date}:${day.items.map((item) => item.id).join(',')}`)
			.join('|');
		daySnapshot;
		activeTemperatureFetchVersion += 1;
		const fetchVersion = activeTemperatureFetchVersion;
		loadDayTemperatures(days, fetchVersion);
	}

	function formatDayTemperature(day: DayGroup): string {
		const temperature = dayTemperatures[day.date];
		if (!temperature?.available || temperature.temperature_c === null) {
			return getI18nText('itinerary.temperature_unavailable', 'Temperature unavailable');
		}

		const rounded = Math.round(temperature.temperature_c);
		return `${rounded}°C`;
	}

	function optimizeDayOrder(dayIndex: number) {
		if (!canModify || isSavingOrder) return;

		const day = days[dayIndex];
		if (!day) return;

		const sortableItems = day.items.filter((item) => {
			if (item?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) return false;
			return !!getCoordinatesFromItineraryItem(item);
		});

		const nonSortableItems = day.items.filter((item) => {
			if (item?.[SHADOW_ITEM_MARKER_PROPERTY_NAME]) return true;
			return !getCoordinatesFromItineraryItem(item);
		});

		if (sortableItems.length < 2) {
			addToast('info', getI18nText('itinerary.optimize_not_enough_items', 'Not enough stops to optimize'));
			return;
		}

		const remaining = [...sortableItems];
		const sorted: ResolvedItineraryItem[] = [];

		const firstItem = remaining.shift();
		if (!firstItem) return;
		sorted.push(firstItem);

		while (remaining.length > 0) {
			const last = sorted[sorted.length - 1];
			const lastCoords = getCoordinatesFromItineraryItem(last);
			if (!lastCoords) break;

			let nearestIndex = 0;
			let nearestDistance = Number.POSITIVE_INFINITY;

			for (let index = 0; index < remaining.length; index += 1) {
				const candidate = remaining[index];
				const candidateCoords = getCoordinatesFromItineraryItem(candidate);
				if (!candidateCoords) continue;

				const distance = haversineDistanceBetweenCoordinates(lastCoords, candidateCoords);

				if (distance < nearestDistance) {
					nearestDistance = distance;
					nearestIndex = index;
				}
			}

			sorted.push(remaining.splice(nearestIndex, 1)[0]);
		}

		days[dayIndex].items = [...sorted, ...nonSortableItems];
		days = [...days];

		isSavingOrder = true;
		savingDay = day.date;
		saveReorderedItems()
			.then((saved) => {
				if (saved) {
					addToast('success', getI18nText('itinerary.optimize_success', 'Day optimized'));
					return;
				}
				addToast('error', getI18nText('itinerary.optimize_failed', 'Failed to optimize day'));
			})
			.catch(() => {
				addToast('error', getI18nText('itinerary.optimize_failed', 'Failed to optimize day'));
			})
			.finally(() => {
				isSavingOrder = false;
				savingDay = null;
			});
	}

	function getFallbackLocationConnector(
		currentItem: ResolvedItineraryItem,
		nextItem: ResolvedItineraryItem | null
	): LocationConnector | null {
		if (!nextItem) return null;

		const currentType = currentItem.item?.type || '';
		const nextType = nextItem.item?.type || '';
		if (!isConnectableItemType(currentType) || !isConnectableItemType(nextType)) return null;

		const unavailableConnector: LocationConnector = {
			distanceLabel: '',
			durationLabel: getI18nText('itinerary.route_unavailable', 'Route unavailable'),
			mode: 'walking',
			unavailable: true
		};

		const fromCoordinates = getCoordinatesFromItineraryItem(
			currentItem,
			currentType === 'transportation' ? 'destination' : 'origin'
		);
		const toCoordinates = getCoordinatesFromItineraryItem(
			nextItem,
			'origin'
		);
		if (!fromCoordinates || !toCoordinates) return unavailableConnector;

		const distanceKm = haversineDistanceKm(
			{ latitude: fromCoordinates.latitude, longitude: fromCoordinates.longitude } as Location,
			{ latitude: toCoordinates.latitude, longitude: toCoordinates.longitude } as Location
		);
		if (distanceKm === null) return unavailableConnector;

		const walkingMinutes = (distanceKm / WALKING_SPEED_KMH) * 60;
		const drivingMinutes = (distanceKm / DRIVING_SPEED_KMH) * 60;
		const useDriving = walkingMinutes > WALKING_THRESHOLD_MINUTES;

		return {
			distanceLabel: formatTransportationDistance(distanceKm) || `${distanceKm.toFixed(1)} km`,
			durationLabel: formatTravelDuration(useDriving ? drivingMinutes : walkingMinutes),
			mode: useDriving ? 'driving' : 'walking'
		};
	}

	function getLocationConnector(
		currentItem: ResolvedItineraryItem,
		nextItem: ResolvedItineraryItem | null
	): LocationConnector | null {
		const key = getLocationConnectorKey(currentItem, nextItem);
		if (key && connectorMetricsMap[key]) {
			return connectorMetricsMap[key];
		}

		return getFallbackLocationConnector(currentItem, nextItem);
	}

	function buildDirectionsUrl(
		currentItem: ResolvedItineraryItem,
		nextItem: ResolvedItineraryItem | null,
		mode: 'walking' | 'driving' = 'walking'
	): string | null {
		if (!nextItem) return null;

		const currentType = currentItem.item?.type || '';
		const nextType = nextItem.item?.type || '';
		if (!isConnectableItemType(currentType) || !isConnectableItemType(nextType)) return null;

		const fromCoordinates = getCoordinatesFromItineraryItem(
			currentItem,
			currentType === 'transportation' ? 'destination' : 'origin'
		);
		const toCoordinates = getCoordinatesFromItineraryItem(
			nextItem,
			'origin'
		);
		if (!fromCoordinates || !toCoordinates) return null;

		const fromLatitude = fromCoordinates.latitude;
		const fromLongitude = fromCoordinates.longitude;
		const toLatitude = toCoordinates.latitude;
		const toLongitude = toCoordinates.longitude;

		if (
			fromLatitude === null ||
			fromLongitude === null ||
			toLatitude === null ||
			toLongitude === null
		) {
			return null;
		}

		const engine = mode === 'driving' ? 'car' : 'foot';
		const route = `${fromLatitude},${fromLongitude};${toLatitude},${toLongitude}`;

		return `https://www.openstreetmap.org/directions?engine=fossgis_osrm_${engine}&route=${encodeURIComponent(route)}`;
	}

	function getI18nText(key: string, fallback: string): string {
		const translated = $t(key);
		return translated && translated !== key ? translated : fallback;
	}

	function upsertNote(note: Note) {
		const notes = collection.notes ? [...collection.notes] : [];
		const idx = notes.findIndex((n) => n.id === note.id);
		if (idx >= 0) {
			notes[idx] = note;
		} else {
			notes.push(note);
		}
		collection = { ...collection, notes };
	}

	function upsertChecklist(checklist: Checklist) {
		const checklists = collection.checklists ? [...collection.checklists] : [];
		const idx = checklists.findIndex((c) => c.id === checklist.id);
		if (idx >= 0) {
			checklists[idx] = checklist;
		} else {
			checklists.push(checklist);
		}
		collection = { ...collection, checklists };
	}

	async function handleNoteUpsert(note: Note) {
		// Get the old note to compare dates
		const oldNote = collection.notes?.find((n) => n.id === note.id);
		const oldDate = oldNote ? normalizeDateOnly(oldNote.date) : null;
		const newDate = normalizeDateOnly(note.date);

		upsertNote(note);

		const targetDate = newDate || pendingAddDate;

		try {
			// If the date changed, remove old itinerary items for this note on the old date
			if (oldDate && newDate && oldDate !== newDate) {
				// Remove itinerary items from the old date
				const itemsToRemove =
					collection.itinerary?.filter(
						(it) => it.item?.type === 'note' && it.object_id === note.id && it.date === oldDate
					) || [];

				for (const item of itemsToRemove) {
					await fetch(`/api/itinerary/${item.id}`, { method: 'DELETE' });
				}

				collection.itinerary =
					collection.itinerary?.filter(
						(it) => !(it.item?.type === 'note' && it.object_id === note.id && it.date === oldDate)
					) || [];
			}

			const isAlreadyScheduled = collection.itinerary?.some(
				(it) => it.item?.type === 'note' && it.object_id === note.id && it.date === targetDate
			);

			if (targetDate && !isAlreadyScheduled) {
				await addItineraryItemForObject('note', note.id, targetDate, !newDate && !!pendingAddDate);
			}
		} finally {
			pendingAddDate = null;
			isNoteModalOpen = false;
		}
	}

	async function handleChecklistUpsert(checklist: Checklist) {
		// Get the old checklist to compare dates
		const oldChecklist = collection.checklists?.find((c) => c.id === checklist.id);
		const oldDate = oldChecklist ? normalizeDateOnly(oldChecklist.date) : null;
		const newDate = normalizeDateOnly(checklist.date);

		upsertChecklist(checklist);

		const targetDate = newDate || pendingAddDate;

		try {
			// If the date changed, remove old itinerary items for this checklist on the old date
			if (oldDate && newDate && oldDate !== newDate) {
				// Remove itinerary items from the old date
				const itemsToRemove =
					collection.itinerary?.filter(
						(it) =>
							it.item?.type === 'checklist' && it.object_id === checklist.id && it.date === oldDate
					) || [];

				for (const item of itemsToRemove) {
					await fetch(`/api/itinerary/${item.id}`, { method: 'DELETE' });
				}

				collection.itinerary =
					collection.itinerary?.filter(
						(it) =>
							!(
								it.item?.type === 'checklist' &&
								it.object_id === checklist.id &&
								it.date === oldDate
							)
					) || [];
			}

			const isAlreadyScheduled = collection.itinerary?.some(
				(it) =>
					it.item?.type === 'checklist' && it.object_id === checklist.id && it.date === targetDate
			);

			if (targetDate && !isAlreadyScheduled) {
				await addItineraryItemForObject(
					'checklist',
					checklist.id,
					targetDate,
					!newDate && !!pendingAddDate
				);
			}
		} finally {
			pendingAddDate = null;
			isChecklistModalOpen = false;
		}
	}

	// Sync the
	//  with the collection.locations array
	$: if (locationBeingUpdated && locationBeingUpdated.id && collection) {
		// Make a shallow copy of locations (ensure array exists)
		const locs = collection.locations ? [...collection.locations] : [];

		const index = locs.findIndex((loc) => loc.id === locationBeingUpdated.id);

		if (index !== -1) {
			// Ensure visits are properly synced and replace the item immutably
			locs[index] = {
				...locs[index],
				...locationBeingUpdated,
				visits: locationBeingUpdated.visits || locs[index].visits || []
			};
		} else {
			// Prepend new/updated location
			locs.unshift({ ...locationBeingUpdated });
		}

		// Assign back to collection immutably to trigger reactivity
		collection = { ...collection, locations: locs };
	}

	// If a new location was just created and we have a pending add-date,
	// attach it to that date in the itinerary.
	$: if (
		locationBeingUpdated?.id &&
		pendingAddDate &&
		!addedToItinerary.has(locationBeingUpdated.id)
	) {
		addItineraryItemForObject('location', locationBeingUpdated.id, pendingAddDate);
		// Mark this location as added to prevent duplicates
		addedToItinerary.add(locationBeingUpdated.id);
		addedToItinerary = addedToItinerary; // trigger reactivity
	}

	// Sync the lodgingBeingUpdated with the collection.lodging array
	$: if (lodgingBeingUpdated && lodgingBeingUpdated.id && collection) {
		// Make a shallow copy of lodging (ensure array exists)
		const lodgings = collection.lodging ? [...collection.lodging] : [];

		const index = lodgings.findIndex((lodge) => lodge.id === lodgingBeingUpdated.id);

		if (index !== -1) {
			// Replace the item immutably
			lodgings[index] = {
				...lodgings[index],
				...lodgingBeingUpdated
			};
		} else {
			// Prepend new/updated lodging
			lodgings.unshift({ ...lodgingBeingUpdated });
		}

		// Assign back to collection immutably to trigger reactivity
		collection = { ...collection, lodging: lodgings };
	}

	// If a new lodging was just created and we have a pending add-date,
	// attach it to that date in the itinerary.
	$: {
		const targetPendingDate = pendingLodgingAddDate || pendingAddDate;
		if (
			lodgingBeingUpdated?.id &&
			targetPendingDate &&
			!addedToItinerary.has(lodgingBeingUpdated.id)
		) {
			// Normalize check_in to date-only (YYYY-MM-DD) if present
			const lodgingCheckInDate = lodgingBeingUpdated.check_in
				? String(lodgingBeingUpdated.check_in).split('T')[0]
				: null;
			const targetDate = lodgingCheckInDate || targetPendingDate;

			addItineraryItemForObject('lodging', lodgingBeingUpdated.id, targetDate);
			// Mark this lodging as added to prevent duplicates
			addedToItinerary.add(lodgingBeingUpdated.id);
			addedToItinerary = addedToItinerary; // trigger reactivity
			pendingAddDate = null;
			pendingLodgingAddDate = null;
		}
	}

	// Sync the transportationBeingUpdated with the collection.transportations array
	$: if (transportationBeingUpdated && transportationBeingUpdated.id && collection) {
		// Make a shallow copy of transportations (ensure array exists)
		const transports = collection.transportations ? [...collection.transportations] : [];

		const index = transports.findIndex((t) => t.id === transportationBeingUpdated.id);

		if (index !== -1) {
			// Replace the item immutably
			transports[index] = {
				...transports[index],
				...transportationBeingUpdated
			};
		} else {
			// Prepend new/updated transportation
			transports.unshift({ ...transportationBeingUpdated });
		}

		// Assign back to collection immutably to trigger reactivity
		collection = { ...collection, transportations: transports };
	}

	// If a new transportation was just created and we have a pending add-date,
	// attach it to that date in the itinerary.
	$: if (
		transportationBeingUpdated?.id &&
		pendingAddDate &&
		!addedToItinerary.has(transportationBeingUpdated.id)
	) {
		addItineraryItemForObject('transportation', transportationBeingUpdated.id, pendingAddDate);
		// Mark this transportation as added to prevent duplicates
		addedToItinerary.add(transportationBeingUpdated.id);
		addedToItinerary = addedToItinerary; // trigger reactivity
	}

	/**
	 * Get lodging items where the guest is staying overnight on a given date
	 * (i.e., the date is between check_in and check_out, but NOT the check_in date itself)
	 */
	function getOvernightLodgingForDate(collection: Collection, dateISO: string): Lodging[] {
		if (!collection.lodging) return [];

		const targetDate = DateTime.fromISO(dateISO).startOf('day');

		// Helper: only include lodging that has been added to the itinerary
		function isLodgingScheduled(lodgingId: any): boolean {
			return !!collection.itinerary?.some((it) => {
				const objectType = it.item?.type || '';
				return objectType === 'lodging' && it.object_id === lodgingId;
			});
		}

		return collection.lodging.filter((lodging) => {
			// Only consider lodging entries that have both check-in and check-out
			if (!lodging.check_in || !lodging.check_out) return false;

			// Skip lodgings that are not scheduled in the itinerary
			if (!isLodgingScheduled(lodging.id)) return false;

			// Extract just the date portion (YYYY-MM-DD) to avoid timezone shifts
			const checkInDateStr = lodging.check_in.split('T')[0];
			const checkOutDateStr = lodging.check_out.split('T')[0];

			const checkIn = DateTime.fromISO(checkInDateStr).startOf('day');
			const checkOut = DateTime.fromISO(checkOutDateStr).startOf('day');

			// The guest is staying overnight if the target date is between
			// check-in (inclusive) and check-out (exclusive). This includes the
			// check-in night as requested.
			return targetDate >= checkIn && targetDate < checkOut;
		});
	}

	function getGlobalItemsByDate(collection: Collection): Map<string, ResolvedItineraryItem[]> {
		const grouped = new Map<string, ResolvedItineraryItem[]>();

		// Filter for global items only (no date filter - extract from resolved object)
		// Determine collection date range for filtering visits
		let collectionStart: DateTime | null = null;
		let collectionEnd: DateTime | null = null;
		if (collection.start_date)
			collectionStart = DateTime.fromISO(collection.start_date).startOf('day');
		if (collection.end_date) collectionEnd = DateTime.fromISO(collection.end_date).startOf('day');

		collection.itinerary
			?.filter((item) => item.is_global)
			.forEach((item) => {
				const resolved = resolveItineraryItem(item, collection);
				const objectType = resolved.item?.type || '';
				const datesToAdd = new Set<string>();

				// Helper to clamp dates to collection range and dedupe
				function addDateIfInRange(date: DateTime) {
					if (collectionStart && date < collectionStart) return;
					if (collectionEnd && date > collectionEnd) return;
					const iso = date.toISODate();
					if (iso) datesToAdd.add(iso);
				}

				// Extract date(s) from the resolved object based on its type
				if (objectType === 'location') {
					const location = resolved.resolvedObject as Location | null;
					if (location?.visits && location.visits.length > 0) {
						location.visits.forEach((visit) => {
							if (!visit.start_date) return;
							const start = DateTime.fromISO(visit.start_date.split('T')[0]).startOf('day');
							const end = visit.end_date
								? DateTime.fromISO(visit.end_date.split('T')[0]).startOf('day')
								: start;

							let cursor = start;
							// If end is before start, treat as single day
							const last = end < start ? start : end;
							while (cursor <= last) {
								addDateIfInRange(cursor);
								cursor = cursor.plus({ days: 1 });
							}
						});
					}
				} else if (objectType === 'transportation') {
					const transport = resolved.resolvedObject as Transportation | null;
					if (transport?.date) {
						addDateIfInRange(DateTime.fromISO(transport.date.split('T')[0]).startOf('day'));
					}
				} else if (objectType === 'lodging') {
					const lodging = resolved.resolvedObject as Lodging | null;
					if (lodging?.check_in) {
						const start = DateTime.fromISO(lodging.check_in.split('T')[0]).startOf('day');
						const end = lodging.check_out
							? DateTime.fromISO(lodging.check_out.split('T')[0]).startOf('day').minus({ days: 1 })
							: start;
						const last = end < start ? start : end;

						let cursor = start;
						while (cursor <= last) {
							addDateIfInRange(cursor);
							cursor = cursor.plus({ days: 1 });
						}
					}
				} else if (objectType === 'note') {
					const note = resolved.resolvedObject as Note | null;
					if (note?.date) {
						addDateIfInRange(DateTime.fromISO(note.date.split('T')[0]).startOf('day'));
					}
				} else if (objectType === 'checklist') {
					const checklist = resolved.resolvedObject as Checklist | null;
					if (checklist?.date) {
						addDateIfInRange(DateTime.fromISO(checklist.date.split('T')[0]).startOf('day'));
					}
				}

				// Add the item to each applicable date
				datesToAdd.forEach((dateISO) => {
					if (!grouped.has(dateISO)) grouped.set(dateISO, []);
					grouped.get(dateISO)!.push(resolved);
				});
			});

		// Sort items within each date group by order
		grouped.forEach((items) => {
			items.sort((a, b) => a.order - b.order);
		});

		return grouped;
	}

	function resolveItineraryItem(
		item: CollectionItineraryItem,
		collection: Collection
	): ResolvedItineraryItem {
		let resolvedObject = null;

		// Resolve based on item.type which tells us the object type
		const objectType = item.item?.type || '';

		if (objectType === 'location') {
			// Find location by ID
			resolvedObject = collection.locations?.find((loc) => loc.id === item.object_id) || null;
		} else if (objectType === 'transportation') {
			resolvedObject = collection.transportations?.find((t) => t.id === item.object_id) || null;
		} else if (objectType === 'lodging') {
			resolvedObject = collection.lodging?.find((l) => l.id === item.object_id) || null;
		} else if (objectType === 'note') {
			resolvedObject = collection.notes?.find((n) => n.id === item.object_id) || null;
		} else if (objectType === 'checklist') {
			resolvedObject = collection.checklists?.find((c) => c.id === item.object_id) || null;
		}

		return {
			...item,
			resolvedObject
		};
	}

	function groupItemsByDay(collection: Collection): DayGroup[] {
		const globalByDate = getGlobalItemsByDate(collection);

		// Build a map of date -> resolved items from existing itinerary entries
		const grouped = new Map<string, ResolvedItineraryItem[]>();

		collection.itinerary?.forEach((item) => {
			if (item.date) {
				if (!grouped.has(item.date)) grouped.set(item.date, []);
				const resolved = resolveItineraryItem(item, collection);
				grouped.get(item.date)!.push(resolved);
			}
		});

		// Determine a date range to display. Prefer explicit collection start/end if present,
		// otherwise use min/max dates found in itinerary items. If no dates at all, return []
		let startDateISO: string | null = null;
		let endDateISO: string | null = null;

		if (collection.start_date && collection.end_date) {
			startDateISO = collection.start_date;
			endDateISO = collection.end_date;
		} else {
			// derive from itinerary dates if available
			const dates = Array.from(grouped.keys()).sort();
			if (dates.length > 0) {
				startDateISO = dates[0];
				endDateISO = dates[dates.length - 1];
			}
		}

		if (!startDateISO || !endDateISO) return [];

		const start = DateTime.fromISO(startDateISO).startOf('day');
		const end = DateTime.fromISO(endDateISO).startOf('day');

		const days: DayGroup[] = [];
		for (let dt = start; dt <= end; dt = dt.plus({ days: 1 })) {
			const iso = dt.toISODate();
			const items = (grouped.get(iso) || []).sort((a, b) => a.order - b.order);
			const overnightLodging = getOvernightLodgingForDate(collection, iso);
			const { preTimelineLodging, postTimelineLodging } = getDirectionalBoundaryLodging(
				collection,
				iso
			);
			const globalDatedItems = globalByDate.get(iso) || [];

			// Find day metadata for this date
			const dayMetadata = collection.itinerary_days?.find((d) => d.date === iso) || null;

			days.push({
				date: iso,
				displayDate: dt.toFormat('cccc, LLLL d, yyyy'),
				items,
				preTimelineLodging,
				postTimelineLodging,
				overnightLodging,
				globalDatedItems,
				dayMetadata
			});
		}

		return days;
	}

	function getUnscheduledItems(collection: Collection): any[] {
		// Get all items that are linked to collection but not in itinerary
		const scheduledIds = new Set(collection.itinerary?.map((item) => item.object_id) || []);

		const unscheduled: any[] = [];

		// Check locations
		collection.locations?.forEach((location) => {
			if (!scheduledIds.has(location.id)) {
				unscheduled.push({ type: 'location', item: location });
			}
		});

		// Check transportation
		collection.transportations?.forEach((transport) => {
			if (!scheduledIds.has(transport.id)) {
				unscheduled.push({ type: 'transportation', item: transport });
			}
		});

		// Check lodging
		collection.lodging?.forEach((lodge) => {
			if (!scheduledIds.has(lodge.id)) {
				unscheduled.push({ type: 'lodging', item: lodge });
			}
		});

		// Check notes
		collection.notes?.forEach((note) => {
			if (!scheduledIds.has(note.id)) {
				unscheduled.push({ type: 'note', item: note });
			}
		});

		// Check checklists
		collection.checklists?.forEach((checklist) => {
			if (!scheduledIds.has(checklist.id)) {
				unscheduled.push({ type: 'checklist', item: checklist });
			}
		});

		return unscheduled;
	}

	function isMultiDay(item: ResolvedItineraryItem): boolean {
		if (item.start_datetime && item.end_datetime) {
			const start = DateTime.fromISO(item.start_datetime);
			const end = DateTime.fromISO(item.end_datetime);
			return !start.hasSame(end, 'day');
		}
		return false;
	}

	function handleDndConsider(dayIndex: number, e: CustomEvent) {
		const { items: newItems } = e.detail;
		const day = days[dayIndex];
		if (!day) return;
		// Update the local state immediately for smooth drag feedback
		days[dayIndex].items = reinsertBoundaryLodgingItems(day, newItems);
		days = [...days];
	}

	function handleDndConsiderGlobal(e: CustomEvent) {
		const { items: newItems } = e.detail;
		globalItems = newItems;
	}

	async function handleDndFinalizeGlobal(e: CustomEvent) {
		const { items: newItems, info } = e.detail;
		globalItems = newItems;
		if (
			info.trigger === TRIGGERS.DROPPED_INTO_ZONE ||
			info.trigger === TRIGGERS.DROPPED_INTO_ANOTHER
		) {
			if (!isSavingOrder) {
				isSavingOrder = true;
				try {
					await saveReorderedItems();
				} finally {
					isSavingOrder = false;
				}
			}
		}
	}

	async function handleDndFinalize(dayIndex: number, e: CustomEvent) {
		const { items: newItems, info } = e.detail;
		const day = days[dayIndex];
		if (!day) return;

		// Update local state
		days[dayIndex].items = reinsertBoundaryLodgingItems(day, newItems);
		days = [...days];

		// Save to backend if item was actually moved (not just considered)
		if (
			info.trigger === TRIGGERS.DROPPED_INTO_ZONE ||
			info.trigger === TRIGGERS.DROPPED_INTO_ANOTHER
		) {
			// Prevent further dragging while we persist the new order
			if (!isSavingOrder) {
				isSavingOrder = true;
				// mark this day as saving so we can show a spinner on that day's header
				savingDay = days[dayIndex]?.date || null;
				try {
					await saveReorderedItems();
				} finally {
					isSavingOrder = false;
					savingDay = null;
				}
			}
		}
	}

	async function saveReorderedItems(): Promise<boolean> {
		try {
			// Collect all items across all days with their new positions
			const dayUpdates = days.flatMap((day) =>
				day.items
					.filter((item) => item.id && !item[SHADOW_ITEM_MARKER_PROPERTY_NAME])
					.map((item, index) => ({
						id: item.id,
						date: day.date,
						order: index
					}))
			);

			const globalUpdates = globalItems
				.filter((item) => item.id && !item[SHADOW_ITEM_MARKER_PROPERTY_NAME])
				.map((item, index) => ({ id: item.id, is_global: true, date: null, order: index }));

			const itemsToUpdate = [...dayUpdates, ...globalUpdates];

			if (itemsToUpdate.length === 0) {
				return true;
			}

			const response = await fetch('/api/itineraries/reorder/', {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					items: itemsToUpdate
				})
			});

			if (!response.ok) {
				throw new Error('Failed to save item order');
			}

			// Optionally show success feedback
			// console.log('Itinerary order saved successfully');
			// Make sure to sync the collection.itinerary with the new order
			const updatedItinerary = collection.itinerary?.map((it) => {
				const updatedItem = itemsToUpdate.find((upd) => upd.id === it.id);
				if (updatedItem) {
					return {
						...it,
						date: updatedItem.date,
						is_global: updatedItem.is_global ?? it.is_global,
						order: updatedItem.order
					};
				}
				return it;
			});
			collection.itinerary = updatedItinerary;

			// Rebuild canonical local state immediately after persisting reorder
			days = groupItemsByDay(collection);
			globalItems = (collection.itinerary || [])
				.filter((it) => it.is_global)
				.map((it) => resolveItineraryItem(it, collection))
				.sort((a, b) => a.order - b.order);
			return true;
		} catch (error) {
			console.error('Error saving itinerary order:', error);
			// Optionally show error notification to user
			alert('Failed to save itinerary order. Please try again.');
			return false;
		}
	}

	// Add a trip-wide (global) itinerary item
	async function addGlobalItineraryItemForObject(objectType: string, objectId: string) {
		const tempId = `temp-global-${Date.now()}`;
		const order = globalItems.length;

		const newIt = {
			id: tempId,
			collection: collection.id,
			content_type: objectType,
			object_id: objectId,
			item: { id: objectId, type: objectType },
			date: null,
			is_global: true,
			order,
			created_at: new Date().toISOString()
		};

		collection.itinerary = [...(collection.itinerary || []), newIt];
		// trigger reactive globals and days
		days = groupItemsByDay(collection);
		globalItems = (collection.itinerary || [])
			.filter((it) => it.is_global)
			.map((it) => resolveItineraryItem(it, collection))
			.sort((a, b) => a.order - b.order);

		try {
			const res = await fetch('/api/itineraries/', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					collection: collection.id,
					content_type: objectType,
					object_id: objectId,
					is_global: true,
					order
				})
			});

			if (!res.ok) {
				const j = await res.json().catch(() => ({}));
				throw new Error(j.detail || 'Failed to add global itinerary item');
			}

			const created = await res.json();
			collection.itinerary = collection.itinerary.map((it) => (it.id === tempId ? created : it));
			// refresh
			days = groupItemsByDay(collection);
			globalItems = (collection.itinerary || [])
				.filter((it) => it.is_global)
				.map((it) => resolveItineraryItem(it, collection))
				.sort((a, b) => a.order - b.order);
		} catch (err) {
			console.error('Error creating global itinerary item:', err);
			alert('Failed to add item to trip-wide itinerary.');
			collection.itinerary = collection.itinerary.filter((it) => it.id !== tempId);
			days = groupItemsByDay(collection);
			globalItems = (collection.itinerary || [])
				.filter((it) => it.is_global)
				.map((it) => resolveItineraryItem(it, collection))
				.sort((a, b) => a.order - b.order);
		}
	}

	// Handle opening the day picker modal for an item (scheduled or unscheduled)
	// currentItineraryDate: the date of the itinerary entry being moved (if any)
	function handleOpenDayPickerForItem(
		type: string,
		item: any,
		forcePicker: boolean = false,
		currentItineraryDate: string | null = null
	) {
		// Check if the item already has a date, and if so, add it directly
		let itemDate: string | null = null;
		// Track all itinerary dates this item is already scheduled on (non-global)
		const scheduledDates = (collection.itinerary || [])
			.filter((it) => it.object_id === item.id && it.date && !it.is_global)
			.map((it) => it.date as string);

		// If moving from a specific itinerary date, track which itinerary item it is
		if (currentItineraryDate) {
			const sourceItineraryItem = (collection.itinerary || []).find(
				(it) => it.object_id === item.id && it.date === currentItineraryDate && !it.is_global
			);
			dayPickSourceItineraryItemId = sourceItineraryItem?.id || null;
		} else {
			dayPickSourceItineraryItemId = null;
		}

		if (type === 'location') {
			// For locations, prefer the visit matching the current itinerary date
			let matchedVisit = null;
			if (currentItineraryDate) {
				matchedVisit = item.visits?.find((v) => v.start_date?.startsWith(currentItineraryDate));
			}
			const firstVisit = matchedVisit || item.visits?.[0];
			if (firstVisit?.start_date) {
				itemDate = firstVisit.start_date.split('T')[0]; // Extract date only (YYYY-MM-DD)
				dayPickSourceVisit = { id: firstVisit.id, start_date: firstVisit.start_date };
			}
		} else if (type === 'transportation') {
			if (item.date) {
				itemDate = item.date.split('T')[0]; // Extract date only (YYYY-MM-DD)
			}
		} else if (type === 'lodging') {
			if (item.check_in) {
				itemDate = item.check_in.split('T')[0]; // Extract date only (YYYY-MM-DD)
			}
		} else if (type === 'note') {
			if (item.date) {
				itemDate = item.date.split('T')[0]; // Extract date only (YYYY-MM-DD)
			}
		} else if (type === 'checklist') {
			if (item.date) {
				itemDate = item.date.split('T')[0]; // Extract date only (YYYY-MM-DD)
			}
		}

		// If caller explicitly wants the picker, bypass auto-add
		if (forcePicker) {
			dayPickItemToAdd = { type, item };
			dayPickScheduledDates = scheduledDates;
			// Capture source visit for locations (match itinerary date first)
			dayPickSourceVisit = null;
			if (type === 'location') {
				const matchedVisit = currentItineraryDate
					? item.visits?.find((v) => v.start_date?.startsWith(currentItineraryDate))
					: null;
				const firstVisit = matchedVisit || item.visits?.[0];
				if (firstVisit?.start_date) {
					dayPickSourceVisit = { id: firstVisit.id, start_date: firstVisit.start_date };
				}
			}
			isDayPickModalOpen = true;
			return;
		}

		// If we found a date, add it directly to that date
		// Helper: check if a date is within collection start/end bounds (if set)
		function isDateWithinCollectionRange(dateISO: string | null) {
			if (!dateISO) return false;
			if (!collection) return true; // no collection context -> allow
			try {
				const d = DateTime.fromISO(dateISO).startOf('day');
				if (collection.start_date) {
					const s = DateTime.fromISO(collection.start_date).startOf('day');
					if (d < s) return false;
				}
				if (collection.end_date) {
					const e = DateTime.fromISO(collection.end_date).startOf('day');
					if (d > e) return false;
				}
				return true;
			} catch (err) {
				return false;
			}
		}

		if (itemDate) {
			// If the item's date is outside the collection range, prompt the day picker
			if (!isDateWithinCollectionRange(itemDate)) {
				dayPickItemToAdd = { type, item };
				dayPickScheduledDates = scheduledDates;
				dayPickSourceVisit = null;
				if (type === 'location') {
					// Prefer the visit that matches itemDate if present
					const source = item.visits?.find((v) => v.start_date?.startsWith(itemDate));
					const useVisit = source || item.visits?.[0];
					if (useVisit?.start_date) {
						dayPickSourceVisit = { id: useVisit.id, start_date: useVisit.start_date };
					}
				}
				isDayPickModalOpen = true;
				return;
			}

			// We have a valid date; ensure dayPickSourceVisit is aligned for locations with multiple visits
			if (type === 'location' && !dayPickSourceVisit) {
				const source = item.visits?.find((v) => v.start_date?.startsWith(itemDate));
				if (source?.start_date) {
					dayPickSourceVisit = { id: source.id, start_date: source.start_date };
				}
			}

			addItineraryItemForObject(type, item.id, itemDate, false);
		} else {
			// Otherwise, show the day picker modal
			dayPickItemToAdd = { type, item };
			dayPickScheduledDates = scheduledDates;
			dayPickSourceVisit = null;
			if (type === 'location') {
				const matchedVisit = currentItineraryDate
					? item.visits?.find((v) => v.start_date?.startsWith(currentItineraryDate))
					: item.visits?.[0];
				if (matchedVisit?.start_date) {
					dayPickSourceVisit = { id: matchedVisit.id, start_date: matchedVisit.start_date };
				}
			}
			isDayPickModalOpen = true;
		}
	}

	// Handle day selection from the day picker modal
	async function handleDaySelected(
		event: CustomEvent<{ date: string; updateDate: boolean; deleteSourceVisit?: boolean }>
	) {
		const { date: selectedDate, updateDate, deleteSourceVisit } = event.detail;
		if (!dayPickItemToAdd) return;

		const { type, item } = dayPickItemToAdd;
		const objectType = type; // 'location', 'transportation', 'lodging', 'note', 'checklist'
		const objectId = item.id;

		// Identify existing dated itinerary entries for this object
		const existingDatedItems = (collection.itinerary || []).filter(
			(it) => it.object_id === objectId && it.date && !it.is_global
		);

		// Avoid duplicate add if already scheduled for the selected date
		const alreadyScheduledForSelectedDate = existingDatedItems.some(
			(it) => it.date === selectedDate
		);

		try {
			if (!alreadyScheduledForSelectedDate) {
				// Add the item to the selected day
				await addItineraryItemForObject(objectType, objectId, selectedDate, updateDate);
			}

			// Optionally delete the source visit (for locations) — skip if we're updating it
			if (deleteSourceVisit && objectType === 'location' && dayPickSourceVisit?.id && !updateDate) {
				try {
					await fetch(`/api/visits/${dayPickSourceVisit.id}/`, { method: 'DELETE' });
					// Update local state: remove visit from the location
					if (collection.locations) {
						collection.locations = collection.locations.map((loc) => {
							if (loc.id === objectId) {
								return {
									...loc,
									visits: (loc.visits || []).filter((v) => v.id !== dayPickSourceVisit?.id)
								};
							}
							return loc;
						});
					}
				} catch (e) {
					console.error('Failed to delete source visit', dayPickSourceVisit.id, e);
				}
			}

			// Only remove the specific itinerary entry being moved, not all occurrences
			if (updateDate && dayPickSourceItineraryItemId) {
				// Only delete the specific itinerary item that was being moved
				try {
					await fetch(`/api/itineraries/${dayPickSourceItineraryItemId}`, { method: 'DELETE' });
					// Update local state to reflect removal
					collection.itinerary = (collection.itinerary || []).filter(
						(it) => it.id !== dayPickSourceItineraryItemId
					);
					days = groupItemsByDay(collection);
				} catch (e) {
					console.error('Failed to remove source itinerary item', dayPickSourceItineraryItemId, e);
				}
			}
		} finally {
			// Reset state regardless of success/failure
			dayPickItemToAdd = null;
			dayPickScheduledDates = [];
			dayPickSourceVisit = null;
			dayPickSourceItineraryItemId = null;
			isDayPickModalOpen = false;
		}
	}

	// Helper: validate UUID format to avoid sending temporary IDs to backend
	function isValidUUID(id: string | undefined | null): boolean {
		if (!id) return false;
		const uuidRegex =
			/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
		return uuidRegex.test(id);
	}

	// Add an itinerary item locally and attempt to persist to backend
	async function addItineraryItemForObject(
		objectType: string,
		objectId: string,
		dateISO: string,
		updateItemDate: boolean = false
	) {
		const tempId = `temp-${Date.now()}`;
		const day = days.find((d) => d.date === dateISO);
		const order = day ? day.items.length : 0;

		const newIt = {
			id: tempId,
			collection: collection.id,
			content_type: objectType,
			object_id: objectId,
			item: { id: objectId, type: objectType },
			date: dateISO,
			order,
			created_at: new Date().toISOString()
		};

		collection.itinerary = [...(collection.itinerary || []), newIt];
		days = groupItemsByDay(collection);

		try {
			const res = await fetch('/api/itineraries/', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					collection: collection.id,
					content_type: objectType,
					object_id: objectId,
					date: dateISO,
					order,
					update_item_date: updateItemDate,
					// Pass source visit ID so backend can update the existing visit
					source_visit_id:
						objectType === 'location' && updateItemDate && isValidUUID(dayPickSourceVisit?.id)
							? dayPickSourceVisit!.id
							: undefined
				})
			});

			if (!res.ok) {
				const j = await res.json().catch(() => ({}));
				throw new Error(j.detail || 'Failed to add itinerary item');
			}

			const created = await res.json();
			collection.itinerary = collection.itinerary.map((it) => (it.id === tempId ? created : it));
			pendingAddDate = null;

			// If we updated the item's date, sync the updated object from server response
			if (updateItemDate && created.updated_object) {
				if (objectType === 'transportation') {
					if (collection.transportations) {
						collection.transportations = collection.transportations.map((t) =>
							t.id === objectId ? { ...t, ...created.updated_object } : t
						);
					}
				} else if (objectType === 'lodging') {
					if (collection.lodging) {
						collection.lodging = collection.lodging.map((l) =>
							l.id === objectId ? { ...l, ...created.updated_object } : l
						);
					}
				}
			} else if (updateItemDate) {
				// Fallback: if server didn't return updated_object, do manual update for other types
				const isoDate = `${dateISO}T00:00:00`;

				if (objectType === 'location') {
					// Shift the existing visit dates locally to match the new itinerary date
					if (collection.locations) {
						collection.locations = collection.locations.map((loc) => {
							if (loc.id !== objectId) return loc;

							const visits = loc.visits || [];
							// Prefer matching by visit id (source_visit) then by old date
							const sourceId = dayPickSourceVisit?.id;
							const oldDate = dayPickSourceVisit?.start_date
								? dayPickSourceVisit.start_date.split('T')[0]
								: null;
							let idx = sourceId ? visits.findIndex((v) => v.id === sourceId) : -1;
							if (idx === -1 && oldDate) {
								idx = visits.findIndex((v) => v.start_date?.startsWith(oldDate));
							}

							if (idx === -1) return loc;

							const v = visits[idx];
							const startDT = v.start_date ? DateTime.fromISO(v.start_date) : null;
							const endDT = v.end_date ? DateTime.fromISO(v.end_date) : null;
							const baseStart = DateTime.fromISO(dateISO);
							const newStart = startDT
								? baseStart
										.set({
											second: startDT.second,
											minute: startDT.minute,
											hour: startDT.hour,
											millisecond: startDT.millisecond
										})
										.toISO()
								: `${dateISO}T00:00:00`;
							const newEnd = endDT
								? DateTime.fromISO(dateISO)
										.set({
											second: endDT.second,
											minute: endDT.minute,
											hour: endDT.hour,
											millisecond: endDT.millisecond
										})
										.toISO()
								: `${dateISO}T23:59:59`;

							const nextVisits = [...visits];
							nextVisits[idx] = { ...v, start_date: newStart, end_date: newEnd };
							return { ...loc, visits: nextVisits };
						});
					}
				} else if (objectType === 'lodging') {
					if (collection.lodging) {
						// Set check_in to selected day, check_out to next day
						const checkOutDate = DateTime.fromISO(dateISO).plus({ days: 1 }).toISODate();
						collection.lodging = collection.lodging.map((l) =>
							l.id === objectId
								? { ...l, check_in: `${dateISO}T00:00:00`, check_out: `${checkOutDate}T00:00:00` }
								: l
						);
					}
				} else if (objectType === 'note') {
					if (collection.notes) {
						collection.notes = collection.notes.map((n) =>
							n.id === objectId ? { ...n, date: isoDate } : n
						);
					}
				} else if (objectType === 'checklist') {
					if (collection.checklists) {
						collection.checklists = collection.checklists.map((c) =>
							c.id === objectId ? { ...c, date: isoDate } : c
						);
					}
				}
			}

			days = groupItemsByDay(collection);
		} catch (err) {
			console.error('Error creating itinerary item:', err);
			alert('Failed to add item to itinerary.');
			collection.itinerary = collection.itinerary.filter((it) => it.id !== tempId);
			days = groupItemsByDay(collection);
		}
	}

	// Save or update day metadata (name and description)
	async function saveDayMetadata(date: string, name: string | null, description: string | null) {
		if (!canModify) return;

		try {
			// Find existing day metadata for this date
			const existing = collection.itinerary_days?.find((d) => d.date === date);

			if (existing) {
				// Update existing day metadata
				const response = await fetch(`/api/itinerary-days/${existing.id}/`, {
					method: 'PATCH',
					headers: {
						'Content-Type': 'application/json'
					},
					body: JSON.stringify({
						name: name || null,
						description: description || null
					})
				});

				if (!response.ok) throw new Error('Failed to update day metadata');

				const updated = await response.json();

				// Update collection.itinerary_days immutably
				collection.itinerary_days = collection.itinerary_days?.map((d) =>
					d.id === existing.id ? updated : d
				);
			} else {
				// Create new day metadata
				const response = await fetch('/api/itinerary-days/', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json'
					},
					body: JSON.stringify({
						collection: collection.id,
						date,
						name: name || null,
						description: description || null
					})
				});

				if (!response.ok) throw new Error('Failed to create day metadata');

				const newDay = await response.json();

				// Add to collection.itinerary_days immutably
				collection.itinerary_days = [...(collection.itinerary_days || []), newDay];
			}

			// Trigger reactivity by reassigning collection
			collection = { ...collection };
			days = groupItemsByDay(collection);
		} catch (err) {
			console.error('Error saving day metadata:', err);
		}
	}
</script>

{#if isLocationModalOpen}
	<NewLocationModal
		on:close={() => {
			isLocationModalOpen = false;
			locationToEdit = null;
			locationBeingUpdated = null;
			pendingAddDate = null;
			addedToItinerary.clear();
			addedToItinerary = addedToItinerary;
		}}
		{user}
		{locationToEdit}
		bind:location={locationBeingUpdated}
		{collection}
		initialVisitDate={pendingAddDate}
	/>
{/if}

{#if isLodgingModalOpen}
	<LodgingModal
		on:close={() => {
			isLodgingModalOpen = false;
			lodgingToEdit = null;
			lodgingBeingUpdated = null;
			pendingAddDate = null;
			pendingLodgingAddDate = null;
			addedToItinerary.clear();
			addedToItinerary = addedToItinerary;
		}}
		{user}
		{lodgingToEdit}
		bind:lodging={lodgingBeingUpdated}
		{collection}
		initialVisitDate={pendingLodgingAddDate || pendingAddDate}
	/>
{/if}

{#if isTransportationModalOpen}
	<TransportationModal
		on:close={() => {
			isTransportationModalOpen = false;
			transportationToEdit = null;
			transportationBeingUpdated = null;
			pendingAddDate = null;
			addedToItinerary.clear();
			addedToItinerary = addedToItinerary;
		}}
		{user}
		{transportationToEdit}
		bind:transportation={transportationBeingUpdated}
		{collection}
		initialVisitDate={pendingAddDate}
	/>
{/if}

{#if isNoteModalOpen}
	<NoteModal
		on:close={() => {
			pendingAddDate = null;
			noteToEdit = null;
			isNoteModalOpen = false;
		}}
		{collection}
		{user}
		note={noteToEdit}
		on:create={(e) => void handleNoteUpsert(e.detail)}
		on:save={(e) => void handleNoteUpsert(e.detail)}
		initialVisitDate={pendingAddDate}
	/>
{/if}

{#if isChecklistModalOpen}
	<ChecklistModal
		on:close={() => {
			pendingAddDate = null;
			checklistToEdit = null;
			isChecklistModalOpen = false;
		}}
		{collection}
		{user}
		checklist={checklistToEdit}
		on:create={(e) => void handleChecklistUpsert(e.detail)}
		on:save={(e) => void handleChecklistUpsert(e.detail)}
		initialVisitDate={pendingAddDate}
	/>
{/if}

{#if isItineraryLinkModalOpen}
	<ItineraryLinkModal
		{collection}
		{user}
		targetDate={linkModalTargetDate}
		displayDate={linkModalDisplayDate}
		on:close={() => (isItineraryLinkModalOpen = false)}
		on:addItem={(e) => {
			const { type, itemId, updateDate } = e.detail;
			addItineraryItemForObject(type, itemId, linkModalTargetDate, updateDate);
		}}
	/>
{/if}

{#if isDayPickModalOpen}
	<ItineraryDayPickModal
		isOpen={isDayPickModalOpen}
		{days}
		itemName={dayPickItemToAdd?.item?.name || `${$t('checklist.item')}`}
		scheduledDates={dayPickScheduledDates}
		sourceVisitDate={dayPickSourceVisit ? dayPickSourceVisit.start_date.split('T')[0] : null}
		on:daySelected={handleDaySelected}
		on:close={() => {
			isDayPickModalOpen = false;
			dayPickItemToAdd = null;
			dayPickScheduledDates = [];
			dayPickSourceVisit = null;
			dayPickSourceItineraryItemId = null;
		}}
	/>
{/if}

{#if canAutoGenerate}
	<div class="alert alert-info shadow-lg mb-6">
		<div class="flex-1 flex items-center gap-3 min-w-0">
			<Info class="w-6 h-6 stroke-current flex-shrink-0" />
			<div class="min-w-0">
				<div class="flex items-baseline gap-3">
					<h3 class="font-bold truncate">{$t('itinerary.auto_generate_itinerary')}</h3>
				</div>
				<div class="text-sm opacity-90 truncate">
					{$t('itinerary.auto_generate_itinerary_desc')}
				</div>
			</div>
		</div>
		<div class="flex-none ml-3">
			<button
				class="btn btn-sm btn-primary"
				disabled={isAutoGenerating}
				on:click={handleAutoGenerate}
			>
				{#if isAutoGenerating}
					<span class="loading loading-spinner loading-sm"></span>
					{$t('itinerary.generating')}...
				{:else}
					{$t('itinerary.auto_generate')}
				{/if}
			</button>
		</div>
	</div>
{/if}

{#if days.length === 0 && unscheduledItems.length === 0}
	<div class="card bg-base-200 shadow-xl">
		<div class="card-body text-center py-12">
			<CalendarBlank class="w-16 h-16 mx-auto mb-4 opacity-50" />
			<h3 class="text-2xl font-bold mb-2">{$t('itinerary.no_itinerary_yet')}</h3>
			<p class="opacity-70">{$t('itinerary.start_planning')}</p>
		</div>
	</div>
{:else}
	<div class="space-y-6">
		<!-- Trip-wide (Global) Items -->
		{#if globalItems.length > 0 || canModify}
			<div class="card bg-base-200 shadow-xl">
				<div class="card-body">
					<div class="flex items-center gap-3 mb-4 pb-4 border-b border-base-300">
						<div
							class="w-6 h-6 rounded-full bg-primary/20 flex items-center justify-center text-primary"
						>
							<CalendarBlank class="w-4 h-4" />
						</div>
						<h3 class="text-xl font-bold">
							{$t('itinerary.trip_context') || 'Trip Context'}
						</h3>
						<!-- Info bubble explaining trip context -->
						<div class="ml-2 tooltip tooltip-right" data-tip={$t('itinerary.trip_context_info')}>
							<button
								type="button"
								class="btn btn-ghost btn-sm btn-square p-1"
								aria-label="Trip context info"
							>
								<Info class="w-4 h-4" />
							</button>
						</div>
					</div>

					{#if globalItems.length === 0}
						<div
							class="card bg-base-100 shadow-sm border border-dashed border-base-300 p-4 text-center"
						>
							<div class="card-body p-2">
								<CalendarBlank class="w-8 h-8 mx-auto mb-2 opacity-40" />
								<p class="opacity-70">
									{$t('itinerary.no_trip_context_items')}
								</p>
							</div>
						</div>
					{:else}
						<div
							use:dndzone={{
								items: globalItems,
								flipDurationMs,
								dropTargetStyle: { outline: 'none', border: 'none' },
								dragDisabled: isSavingOrder || !canModify,
								dropFromOthersDisabled: true
							}}
							on:consider={handleDndConsiderGlobal}
							on:finalize={handleDndFinalizeGlobal}
							class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3"
						>
							{#each globalItems as item (item.id)}
								{@const objectType = item.item?.type || ''}
								{@const resolvedObj = item.resolvedObject}
								<div
									class="group relative transition-all duration-200 pointer-events-auto h-full"
									animate:flip={{ duration: flipDurationMs }}
								>
									{#if resolvedObj}
										{#if canModify}
											<div
												class="absolute left-2 top-2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
												title={$t('itinerary.drag_to_reorder')}
											>
												<div
													class="itinerary-drag-handle btn btn-circle btn-xs btn-ghost bg-base-100/80 backdrop-blur-sm shadow-sm hover:bg-base-200 cursor-grab active:cursor-grabbing"
													aria-label={$t('itinerary.drag_to_reorder')}
													role="button"
													tabindex="0"
												>
													<svg
														xmlns="http://www.w3.org/2000/svg"
														class="h-3 w-3"
														fill="none"
														viewBox="0 0 24 24"
														stroke="currentColor"
														><path
															stroke-linecap="round"
															stroke-linejoin="round"
															stroke-width="2"
															d="M4 8h16M4 16h16"
														/></svg
													>
												</div>
											</div>
										{/if}
										{#if objectType === 'location'}
											<LocationCard
												adventure={resolvedObj}
												on:edit={handleEditLocation}
												on:delete={handleItemDelete}
												on:duplicate={handleDuplicateLocation}
												itineraryItem={item}
												on:removeFromItinerary={handleRemoveItineraryItem}
												on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
												{user}
												{collection}
												compact={true}
												showImage={false}
											/>
										{:else if objectType === 'transportation'}
											<TransportationCard
												transportation={resolvedObj}
												{user}
												{collection}
												readOnly={!canModify}
												compact={true}
												showImage={false}
												on:delete={handleItemDelete}
												itineraryItem={item}
												on:removeFromItinerary={handleRemoveItineraryItem}
												on:edit={handleEditTransportation}
												on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
											/>
										{:else if objectType === 'lodging'}
											<LodgingCard
												lodging={resolvedObj}
												{user}
												{collection}
												itineraryItem={item}
												showImage={false}
												compact={true}
												on:delete={handleItemDelete}
												on:removeFromItinerary={handleRemoveItineraryItem}
												on:edit={handleEditLodging}
												on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
											/>
										{:else if objectType === 'note'}
											<NoteCard
												note={resolvedObj}
												{user}
												{collection}
												on:delete={handleItemDelete}
												itineraryItem={item}
												on:removeFromItinerary={handleRemoveItineraryItem}
												on:edit={handleEditNote}
												on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
											/>
										{:else if objectType === 'checklist'}
											<ChecklistCard
												checklist={resolvedObj}
												{user}
												{collection}
												on:delete={handleItemDelete}
												itineraryItem={item}
												on:removeFromItinerary={handleRemoveItineraryItem}
												on:edit={handleEditChecklist}
												on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
											/>
										{/if}
									{:else}
										<div class="alert alert-warning">
											<span>⚠️ {$t('itinerary.item_not_found')} (ID: {item.object_id})</span>
										</div>
									{/if}
								</div>
							{/each}
						</div>
					{/if}
				</div>
			</div>
		{/if}
		<!-- Scheduled Days -->
		{#each days as day, dayIndex}
			{@const dayNumber = dayIndex + 1}
			{@const totalDays = days.length}
			{@const weekday = DateTime.fromISO(day.date).toFormat('ccc')}
			{@const dayOfMonth = DateTime.fromISO(day.date).toFormat('d')}
			{@const monthAbbrev = DateTime.fromISO(day.date).toFormat('LLL')}
			{@const preTimelineLodging = day.preTimelineLodging}
			{@const postTimelineLodging = day.postTimelineLodging}
			{@const dayTimelineItems = getDayTimelineItems(day)}
			{@const firstConnectableItem = getFirstConnectableItem(day.items)}
			{@const lastConnectableItem = getLastConnectableItem(day.items)}
			{@const noLocationsInDay = !firstConnectableItem && !lastConnectableItem}
			{@const shouldCollapseBoundaryLodging =
				noLocationsInDay &&
				preTimelineLodging?.id &&
				postTimelineLodging?.id &&
				preTimelineLodging.id === postTimelineLodging.id}
			{@const startBoundaryConnector =
				preTimelineLodging && firstConnectableItem
					? getLocationConnector(preTimelineLodging, firstConnectableItem)
					: null}
			{@const startBoundaryDirectionsUrl =
				preTimelineLodging && firstConnectableItem
					? buildDirectionsUrl(
							preTimelineLodging,
							firstConnectableItem,
							startBoundaryConnector?.mode || 'walking'
						)
					: null}
			{@const endBoundaryConnector =
				postTimelineLodging && lastConnectableItem
					? getLocationConnector(lastConnectableItem, postTimelineLodging)
					: null}
			{@const endBoundaryDirectionsUrl =
				postTimelineLodging && lastConnectableItem
					? buildDirectionsUrl(
							lastConnectableItem,
							postTimelineLodging,
							endBoundaryConnector?.mode || 'walking'
						)
					: null}

			<div class="card bg-base-200 shadow-xl">
				<div class="card-body">
					<!-- Day Header (compact, shows date pill + Day X of Y + items + add/save) -->

					<div class="flex items-start gap-4 mb-4 pb-4 border-b border-base-300">
						<!-- Date pill -->
						<div class="flex-none">
							<div class="text-center bg-base-300 rounded-lg px-3 py-2 w-20">
								<div class="text-xs opacity-70">{weekday}</div>
								<div class="text-2xl font-bold -mt-1">{dayOfMonth}</div>
								<div class="text-xs opacity-70">{monthAbbrev}</div>
								<div class="text-[10px] opacity-80 mt-1">{formatDayTemperature(day)}</div>
							</div>
						</div>

						<!-- Title and meta -->
						<div class="flex-1 min-w-0 space-y-1">
							<!-- Main date title + optional day name -->
							<div class="flex items-baseline gap-2 flex-wrap">
								<h3 class="text-lg md:text-xl font-bold">{day.displayDate}</h3>

								<!-- Day name - inline with date -->
								{#if canModify}
									{#if day.dayMetadata?.name}
										<input
											type="text"
											class="input input-ghost text-base font-medium px-1 py-0 -ml-1 focus:bg-base-100 focus:px-2 transition-all flex-shrink min-w-0"
											style="width: {(day.dayMetadata.name.length + 5) * 8}px; max-width: 300px;"
											value={day.dayMetadata.name}
											placeholder="Day name"
											on:blur={(e) => {
												const newName = e.currentTarget.value.trim() || null;
												if (newName !== day.dayMetadata?.name) {
													saveDayMetadata(day.date, newName, day.dayMetadata?.description || null);
												}
											}}
										/>
									{:else}
										<button
											type="button"
											class="text-sm opacity-40 hover:opacity-100 transition-opacity px-1"
											on:click={(e) => {
												const input = e.currentTarget.nextElementSibling;
												if (input) input.focus();
											}}
										>
											+ {$t('adventures.name')}
										</button>
										<input
											type="text"
											class="input input-ghost text-base font-medium px-1 py-0 opacity-0 focus:opacity-100 focus:bg-base-100 focus:px-2 transition-all w-0 focus:w-auto"
											style="max-width: 300px;"
											placeholder="Day name"
											value=""
											on:blur={(e) => {
												const newName = e.currentTarget.value.trim() || null;
												if (newName) {
													saveDayMetadata(day.date, newName, day.dayMetadata?.description || null);
												} else {
													e.currentTarget.classList.add('w-0');
													e.currentTarget.classList.remove('w-auto');
												}
											}}
											on:focus={(e) => {
												e.currentTarget.classList.remove('w-0');
												e.currentTarget.classList.add('w-auto');
											}}
										/>
									{/if}
								{:else if day.dayMetadata?.name}
									<span class="text-base font-medium opacity-90">— {day.dayMetadata.name}</span>
								{/if}
							</div>

							<!-- Day meta info -->
							<div class="text-sm opacity-70 flex items-center gap-3">
								<span class="font-medium"
									>{$t('calendar.day')} {dayNumber} {$t('worldtravel.of')} {totalDays}</span
								>
								<span class="opacity-50">•</span>
								<span
									>{day.items.length}
									{day.items.length === 1 ? $t('checklist.item') : $t('checklist.items')}</span
								>
								{#if day.overnightLodging.length > 0}
									<span class="badge badge-info badge-outline badge-sm"
										>{$t('adventures.overnight')}</span
									>
								{/if}
							</div>

							<!-- Description - shows when present, ghost input when editing -->
							{#if canModify}
								<textarea
									class="textarea textarea-ghost w-full px-2 py-1 text-sm leading-relaxed resize-none focus:bg-base-100 transition-all {day
										.dayMetadata?.description
										? ''
										: 'opacity-40 hover:opacity-70 focus:opacity-100'}"
									rows="2"
									placeholder={'+ ' + $t('itinerary.add_description') + '...'}
									value={day.dayMetadata?.description || ''}
									on:blur={(e) => {
										const newDesc = e.currentTarget.value.trim() || null;
										if (newDesc !== day.dayMetadata?.description) {
											saveDayMetadata(day.date, day.dayMetadata?.name || null, newDesc);
										}
									}}
								/>
							{:else if day.dayMetadata?.description}
								<p class="text-sm leading-relaxed opacity-80 whitespace-pre-wrap px-2 py-1">
									{day.dayMetadata.description}
								</p>
							{/if}
						</div>

						<!-- Actions: saving indicator + day quick actions -->
						<div class="flex-none ml-3 flex items-start gap-2">
							{#if savingDay === day.date}
								<div>
									<div class="badge badge-neutral-300 gap-2 p-2">
										<span class="loading loading-spinner loading-sm"></span>
										{$t('adventures.saving')}...
									</div>
								</div>
							{/if}

							{#if canModify}
								<button
									type="button"
									class="btn btn-sm btn-outline"
									disabled={isSavingOrder}
									title={$t('itinerary.optimize')}
									on:click={() => optimizeDayOrder(dayIndex)}
								>
									{$t('itinerary.optimize')}
								</button>
							{/if}
						</div>
					</div>

					<!-- Day Items (vertical timeline with ordered stops) -->
					<div>
						{#if preTimelineLodging?.resolvedObject}
							<div class="mb-3">
								<LodgingCard
									lodging={preTimelineLodging.resolvedObject}
									{user}
									{collection}
									itineraryItem={preTimelineLodging}
									showImage={false}
									compact={true}
									on:delete={handleItemDelete}
									on:removeFromItinerary={handleRemoveItineraryItem}
									on:edit={handleEditLodging}
									on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
									on:changeDay={(e) =>
										handleOpenDayPickerForItem(
											e.detail.type,
											e.detail.item,
											e.detail.forcePicker,
											day.date
										)}
								/>
								{#if startBoundaryConnector}
									<div
										class="mt-2 rounded-lg border border-base-300 bg-base-200/60 px-3 py-2 text-xs"
									>
										{#if startBoundaryConnector.unavailable}
											<div class="flex items-center gap-2 flex-wrap text-base-content/80">
												<span class="inline-flex items-center gap-1 font-medium">
													<LocationMarker class="w-3.5 h-3.5" />
													{startBoundaryConnector.durationLabel}
												</span>
												<span class="text-base-content/40">•</span>
												{#if startBoundaryDirectionsUrl}
													<a
														href={startBoundaryDirectionsUrl}
														target="_blank"
														rel="noopener noreferrer"
														class="inline-flex items-center gap-1 text-primary/80 font-medium underline underline-offset-2"
													>
														<LocationMarker class="w-3.5 h-3.5" />
														{getI18nText('itinerary.directions', 'Directions')}
													</a>
												{/if}
											</div>
										{:else}
											<div class="flex items-center gap-2 flex-wrap text-base-content">
												<span class="inline-flex items-center gap-1 font-medium">
													{#if startBoundaryConnector.mode === 'driving'}
														<Car class="w-3.5 h-3.5" />
													{:else}
														<Walk class="w-3.5 h-3.5" />
													{/if}
													{startBoundaryConnector.durationLabel}
												</span>
												<span class="text-base-content/50">•</span>
												<span class="font-medium">{startBoundaryConnector.distanceLabel}</span>
												{#if startBoundaryDirectionsUrl}
													<span class="text-base-content/50">•</span>
													<a
														href={startBoundaryDirectionsUrl}
														target="_blank"
														rel="noopener noreferrer"
														class="inline-flex items-center gap-1 text-primary font-medium underline underline-offset-2"
													>
														<LocationMarker class="w-3.5 h-3.5" />
														{getI18nText('itinerary.directions', 'Directions')}
													</a>
												{/if}
											</div>
										{/if}
									</div>
								{/if}
							</div>
						{/if}

						{#if dayTimelineItems.length === 0 && !preTimelineLodging?.resolvedObject && !postTimelineLodging?.resolvedObject}
							<div
								class="card bg-base-100 shadow-sm border border-dashed border-base-300 p-4 text-center"
							>
								<div class="card-body p-2">
									<CalendarBlank class="w-8 h-8 mx-auto mb-2 opacity-40" />
									<p class="opacity-70">{$t('itinerary.no_plans_for_day')}</p>
								</div>
							</div>
						{:else}
							<div
								use:dndzone={{
									items: dayTimelineItems,
									flipDurationMs,
									dropTargetStyle: { outline: 'none', border: 'none' },
									dragDisabled: isSavingOrder || !canModify,
									dropFromOthersDisabled: true
								}}
								on:consider={(e) => handleDndConsider(dayIndex, e)}
								on:finalize={(e) => handleDndFinalize(dayIndex, e)}
								class="space-y-3"
							>
								{#each dayTimelineItems as item, index (item.id)}
									{@const objectType = item.item?.type || ''}
									{@const resolvedObj = item.resolvedObject}
									{@const multiDay = isMultiDay(item)}
									{@const nextConnectableItem = findNextConnectableItem(dayTimelineItems, index)}
									{@const locationConnector = getLocationConnector(item, nextConnectableItem)}
									{@const directionsUrl = buildDirectionsUrl(
										item,
										nextConnectableItem,
										locationConnector?.mode || 'walking'
									)}
									{@const isDraggingShadow = item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
									{@const timelineNumber = index + 1}

									<div
										class="group relative transition-all duration-200 pointer-events-auto {isDraggingShadow
											? 'opacity-40 scale-95'
											: ''}"
										animate:flip={{ duration: flipDurationMs }}
									>
										{#if resolvedObj}
											<div class="flex gap-3">
												<div class="relative flex flex-col items-center shrink-0 pt-1">
													<div
														class="w-7 h-7 rounded-full bg-primary text-primary-content text-xs font-bold flex items-center justify-center"
													>
														{timelineNumber}
													</div>
													{#if index < dayTimelineItems.length - 1}
														<div class="w-px bg-base-300 flex-1 min-h-10 mt-1"></div>
													{/if}
												</div>

												<div class="relative flex-1 min-w-0">
													{#if canModify}
														<div
															class="absolute left-0 top-0 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
															title={$t('itinerary.drag_to_reorder')}
														>
															<div
																class="itinerary-drag-handle btn btn-circle btn-xs btn-ghost bg-base-100/80 backdrop-blur-sm shadow-sm hover:bg-base-200 cursor-grab active:cursor-grabbing"
																aria-label={$t('itinerary.drag_to_reorder')}
																role="button"
																tabindex="0"
															>
																<svg
																	xmlns="http://www.w3.org/2000/svg"
																	class="h-3 w-3"
																	fill="none"
																	viewBox="0 0 24 24"
																	stroke="currentColor"
																>
																	<path
																		stroke-linecap="round"
																		stroke-linejoin="round"
																		stroke-width="2"
																		d="M4 8h16M4 16h16"
																	/>
																</svg>
															</div>
														</div>
													{/if}

													{#if objectType === 'transportation'}
														<TransportationCard
															transportation={resolvedObj}
															{user}
															{collection}
															itineraryItem={item}
															compact={true}
															showImage={false}
															on:delete={handleItemDelete}
															on:removeFromItinerary={handleRemoveItineraryItem}
															on:edit={handleEditTransportation}
															on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
															on:changeDay={(e) =>
																handleOpenDayPickerForItem(
																	e.detail.type,
																	e.detail.item,
																	e.detail.forcePicker,
																	day.date
																)}
														/>
													{:else}
														{#if multiDay && objectType === 'lodging'}
															<div class="mb-2">
																<div class="badge badge-info badge-xs gap-1 shadow-sm">
																	<span class="text-xs">{$t('itinerary.multi_day')}</span>
																</div>
															</div>
														{/if}

														{#if objectType === 'location'}
															<LocationCard
																adventure={resolvedObj}
																on:edit={handleEditLocation}
																on:delete={handleItemDelete}
																on:duplicate={handleDuplicateLocation}
																itineraryItem={item}
																on:removeFromItinerary={handleRemoveItineraryItem}
																on:moveToGlobal={(e) =>
																	moveItemToGlobal(e.detail.type, e.detail.id)}
																{user}
																{collection}
																compact={true}
																showImage={false}
																on:changeDay={(e) =>
																	handleOpenDayPickerForItem(
																		e.detail.type,
																		e.detail.item,
																		e.detail.forcePicker,
																		day.date
																	)}
															/>
														{:else if objectType === 'lodging'}
															<LodgingCard
																lodging={resolvedObj}
																{user}
																{collection}
																itineraryItem={item}
																showImage={false}
																compact={true}
																on:delete={handleItemDelete}
																on:removeFromItinerary={handleRemoveItineraryItem}
																on:edit={handleEditLodging}
																on:moveToGlobal={(e) =>
																	moveItemToGlobal(e.detail.type, e.detail.id)}
																on:changeDay={(e) =>
																	handleOpenDayPickerForItem(
																		e.detail.type,
																		e.detail.item,
																		e.detail.forcePicker,
																		day.date
																	)}
															/>
														{:else if objectType === 'note'}
															<NoteCard
																note={resolvedObj}
																{user}
																{collection}
																on:delete={handleItemDelete}
																itineraryItem={item}
																on:removeFromItinerary={handleRemoveItineraryItem}
																on:edit={handleEditNote}
																on:moveToGlobal={(e) =>
																	moveItemToGlobal(e.detail.type, e.detail.id)}
																on:changeDay={(e) =>
																	handleOpenDayPickerForItem(
																		e.detail.type,
																		e.detail.item,
																		e.detail.forcePicker,
																		day.date
																	)}
															/>
														{:else if objectType === 'checklist'}
															<ChecklistCard
																checklist={resolvedObj}
																{user}
																{collection}
																on:delete={handleItemDelete}
																itineraryItem={item}
																on:removeFromItinerary={handleRemoveItineraryItem}
																on:edit={handleEditChecklist}
																on:moveToGlobal={(e) =>
																	moveItemToGlobal(e.detail.type, e.detail.id)}
																on:changeDay={(e) =>
																	handleOpenDayPickerForItem(
																		e.detail.type,
																		e.detail.item,
																		e.detail.forcePicker,
																		day.date
																	)}
															/>
														{/if}
													{/if}

													{#if locationConnector}
														<div
															class="mt-2 rounded-lg border border-base-300 bg-base-200/60 px-3 py-2 text-xs"
														>
															{#if locationConnector.unavailable}
																<div class="flex items-center gap-2 flex-wrap text-base-content/80">
																	<span class="inline-flex items-center gap-1 font-medium">
																		<LocationMarker class="w-3.5 h-3.5" />
																		{locationConnector.durationLabel}
																	</span>
																	<span class="text-base-content/40">•</span>
																	{#if directionsUrl}
																		<a
																			href={directionsUrl}
																			target="_blank"
																			rel="noopener noreferrer"
																			class="inline-flex items-center gap-1 text-primary/80 font-medium underline underline-offset-2"
																		>
																			<LocationMarker class="w-3.5 h-3.5" />
																			{getI18nText('itinerary.directions', 'Directions')}
																		</a>
																	{:else}
																		<span
																			class="inline-flex items-center gap-1 text-primary/80 font-medium underline underline-offset-2"
																		>
																			<LocationMarker class="w-3.5 h-3.5" />
																			{getI18nText('itinerary.directions', 'Directions')}
																		</span>
																	{/if}
																</div>
															{:else}
																<div class="flex items-center gap-2 flex-wrap text-base-content">
																	<span class="inline-flex items-center gap-1 font-medium">
																		{#if locationConnector.mode === 'driving'}
																			<Car class="w-3.5 h-3.5" />
																		{:else}
																			<Walk class="w-3.5 h-3.5" />
																		{/if}
																		{locationConnector.durationLabel}
																	</span>
																	<span class="text-base-content/50">•</span>
																	<span class="font-medium">{locationConnector.distanceLabel}</span>
																	<span class="text-base-content/50">•</span>
																	{#if directionsUrl}
																		<a
																			href={directionsUrl}
																			target="_blank"
																			rel="noopener noreferrer"
																			class="inline-flex items-center gap-1 text-primary font-medium underline underline-offset-2"
																		>
																			<LocationMarker class="w-3.5 h-3.5" />
																			{getI18nText('itinerary.directions', 'Directions')}
																		</a>
																	{:else}
																		<span
																			class="inline-flex items-center gap-1 text-primary font-medium underline underline-offset-2"
																		>
																			<LocationMarker class="w-3.5 h-3.5" />
																			{getI18nText('itinerary.directions', 'Directions')}
																		</span>
																	{/if}
																</div>
															{/if}
														</div>
													{/if}
												</div>
											</div>
										{:else}
											<!-- Fallback for unresolved items -->
											<div class="alert alert-warning">
												<span>⚠️ {$t('itinerary.item_not_found')} (ID: {item.object_id})</span>
											</div>
										{/if}
									</div>
								{/each}
							</div>
						{/if}

						{#if postTimelineLodging?.resolvedObject && !shouldCollapseBoundaryLodging}
							<div class="mt-3">
								{#if endBoundaryConnector}
									<div
										class="mb-2 rounded-lg border border-base-300 bg-base-200/60 px-3 py-2 text-xs"
									>
										{#if endBoundaryConnector.unavailable}
											<div class="flex items-center gap-2 flex-wrap text-base-content/80">
												<span class="inline-flex items-center gap-1 font-medium">
													<LocationMarker class="w-3.5 h-3.5" />
													{endBoundaryConnector.durationLabel}
												</span>
												<span class="text-base-content/40">•</span>
												{#if endBoundaryDirectionsUrl}
													<a
														href={endBoundaryDirectionsUrl}
														target="_blank"
														rel="noopener noreferrer"
														class="inline-flex items-center gap-1 text-primary/80 font-medium underline underline-offset-2"
													>
														<LocationMarker class="w-3.5 h-3.5" />
														{getI18nText('itinerary.directions', 'Directions')}
													</a>
												{/if}
											</div>
										{:else}
											<div class="flex items-center gap-2 flex-wrap text-base-content">
												<span class="inline-flex items-center gap-1 font-medium">
													{#if endBoundaryConnector.mode === 'driving'}
														<Car class="w-3.5 h-3.5" />
													{:else}
														<Walk class="w-3.5 h-3.5" />
													{/if}
													{endBoundaryConnector.durationLabel}
												</span>
												<span class="text-base-content/50">•</span>
												<span class="font-medium">{endBoundaryConnector.distanceLabel}</span>
												{#if endBoundaryDirectionsUrl}
													<span class="text-base-content/50">•</span>
													<a
														href={endBoundaryDirectionsUrl}
														target="_blank"
														rel="noopener noreferrer"
														class="inline-flex items-center gap-1 text-primary font-medium underline underline-offset-2"
													>
														<LocationMarker class="w-3.5 h-3.5" />
														{getI18nText('itinerary.directions', 'Directions')}
													</a>
												{/if}
											</div>
										{/if}
									</div>
								{/if}

								<LodgingCard
									lodging={postTimelineLodging.resolvedObject}
									{user}
									{collection}
									itineraryItem={postTimelineLodging}
									showImage={false}
									compact={true}
									on:delete={handleItemDelete}
									on:removeFromItinerary={handleRemoveItineraryItem}
									on:edit={handleEditLodging}
									on:moveToGlobal={(e) => moveItemToGlobal(e.detail.type, e.detail.id)}
									on:changeDay={(e) =>
										handleOpenDayPickerForItem(
											e.detail.type,
											e.detail.item,
											e.detail.forcePicker,
											day.date
										)}
								/>
							</div>
						{/if}

						{#if canModify}
							<div class="mt-4 pt-4 border-t border-base-300 border-dashed">
								<div class="flex items-center justify-end gap-3 flex-wrap">
									<div class="dropdown dropdown-end z-30">
										<button
											type="button"
											class="btn btn-sm btn-outline"
											aria-haspopup="menu"
											aria-expanded="false"
										>
											<Plus class="w-4 h-4" />
											{$t('adventures.add')}
										</button>
										<ul
											class="dropdown-content menu p-2 shadow bg-base-300 rounded-box w-56"
											role="menu"
										>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														linkModalTargetDate = day.date;
														linkModalDisplayDate = day.displayDate;
														isItineraryLinkModalOpen = true;
													}}
												>
													{$t('itinerary.link_existing_item')}
												</button>
											</li>
											<li class="menu-title">{$t('adventures.create_new')}</li>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														pendingAddDate = day.date;
														locationToEdit = null;
														locationBeingUpdated = null;
														isLocationModalOpen = true;
													}}
												>
													{$t('locations.location')}
												</button>
											</li>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														pendingAddDate = day.date;
														pendingLodgingAddDate = day.date;
														lodgingToEdit = null;
														lodgingBeingUpdated = null;
														isLodgingModalOpen = true;
													}}
												>
													{$t('adventures.lodging')}
												</button>
											</li>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														pendingAddDate = day.date;
														isTransportationModalOpen = true;
													}}
												>
													{$t('adventures.transportation')}
												</button>
											</li>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														pendingAddDate = day.date;
														isNoteModalOpen = true;
													}}
												>
													{$t('adventures.note')}
												</button>
											</li>
											<li>
												<button
													type="button"
													role="menuitem"
													on:click={() => {
														pendingAddDate = day.date;
														isChecklistModalOpen = true;
													}}
												>
													{$t('adventures.checklist')}
												</button>
											</li>
										</ul>
									</div>
								</div>
							</div>
						{/if}
					</div>

					<!-- Overnight Lodging + Dated Trip-wide Indicators (share row to save space) -->
					{#if shouldShowOvernightSummary(day) || day.globalDatedItems.length > 0}
						<div class="mt-4 pt-4 border-t border-base-300 border-dashed">
							<div class="flex flex-wrap gap-6 items-start">
								{#if shouldShowOvernightSummary(day)}
									<div class="space-y-2 min-w-[240px] flex-1">
										<div class="flex items-center gap-2 mb-1 opacity-70">
											<Bed class="w-4 h-4" />
											<span class="text-sm font-medium">{$t('itinerary.staying_overnight')}</span>
										</div>
										<div class="space-y-2">
											{#each day.overnightLodging as lodging}
												{@const checkOut = lodging.check_out
													? DateTime.fromISO(lodging.check_out.split('T')[0]).toFormat('LLL d')
													: null}
												<div
													class="flex items-center gap-3 bg-base-100 rounded-lg px-4 py-3 border border-base-300"
												>
													<div
														class="flex items-center justify-center w-8 h-8 rounded-full bg-info/20 text-info"
													>
														<Bed class="w-4 h-4" />
													</div>
													<div class="flex-1 min-w-0">
														<a
															href={`/lodging/${lodging.id}`}
															class="hover:text-primary transition-colors duration-200 line-clamp-2 text-md font-semibold"
														>
															{lodging.name}
														</a>
														{#if lodging.location}
															<p class="text-xs opacity-60 truncate">{lodging.location}</p>
														{/if}
													</div>
													{#if checkOut}
														<div class="badge badge-ghost badge-sm">
															{$t('adventures.check_out')}: {checkOut}
														</div>
													{/if}
												</div>
											{/each}
										</div>
									</div>
								{/if}

								{#if day.globalDatedItems.length > 0}
									<div class="space-y-2 min-w-[220px] flex-1">
										<div class="flex items-center gap-2 mb-1 opacity-70">
											<Globe class="w-4 h-4" />
											<span class="text-sm font-medium"
												>{$t('itinerary.trip_context') || 'Trip Context'}</span
											>
										</div>
										<div class="space-y-2">
											{#each day.globalDatedItems as globalItem (globalItem.id)}
												{@const type = globalItem.item?.type || ''}
												{@const obj = globalItem.resolvedObject}
												{@const name = obj?.name || globalItem.item?.type || 'Item'}
												{@const secondary =
													type === 'location'
														? obj?.location
														: type === 'transportation'
															? obj?.to_location || obj?.from_location
															: type === 'lodging'
																? obj?.location
																: type === 'note' || type === 'checklist'
																	? obj?.name
																	: null}
												<div
													class="flex items-center gap-3 bg-base-100 rounded-lg px-4 py-3 border border-base-300"
												>
													<div
														class="flex items-center justify-center w-8 h-8 rounded-full bg-base-300 text-base-content"
													>
														{#if type === 'lodging'}
															<Bed class="w-4 h-4" />
														{:else if type === 'location'}
															{#if obj?.category?.icon}
																<span class="text-lg">{obj.category.icon}</span>
															{:else}
																<LocationMarker class="w-4 h-4" />
															{/if}
														{:else if type === 'transportation'}
															<Car class="w-4 h-4" />
														{:else}
															<Info class="w-4 h-4" />
														{/if}
													</div>
													<div class="flex-1 min-w-0">
														{#if type === 'location' && obj?.id}
															<a
																href={`/locations/${obj.id}`}
																class="hover:text-primary transition-colors text-md font-semibold line-clamp-2 block"
																>{name}</a
															>
														{:else if type === 'lodging' && obj?.id}
															<a
																href={`/lodging/${obj.id}`}
																class="hover:text-primary transition-colors text-md font-semibold line-clamp-2 block"
																>{name}</a
															>
														{:else if type === 'transportation' && obj?.id}
															<a
																href={`/transportations/${obj.id}`}
																class="hover:text-primary transition-colors text-md font-semibold line-clamp-2 block"
																>{name}</a
															>
														{:else}
															<p class="text-md font-semibold line-clamp-2">{name}</p>
														{/if}
														{#if secondary}
															<p class="text-xs opacity-60 truncate">{secondary}</p>
														{/if}
													</div>
												</div>
											{/each}
										</div>
									</div>
								{/if}
							</div>
						</div>
					{/if}
				</div>
			</div>
		{/each}

		<!-- Unscheduled Items -->
		{#if unscheduledItems.length > 0}
			<div class="card bg-base-200 shadow-xl border-2 border-dashed border-base-300">
				<div class="card-body">
					<!-- Unscheduled Header -->
					<div class="flex items-center gap-3 mb-4 pb-4 border-b border-base-300">
						<div class="w-6 h-6 rounded-full border-2 border-dashed border-base-content/30"></div>
						<h3 class="text-xl font-bold opacity-70">{$t('itinerary.unscheduled_items')}</h3>
						<div class="badge badge-ghost ml-auto">
							{unscheduledItems.length}
							{unscheduledItems.length === 1 ? $t('checklist.item') : $t('checklist.items')}
						</div>
					</div>

					<p class="text-sm opacity-70 mb-4">
						{$t('itinerary.unscheduled_items_desc')}
					</p>

					<!-- Unscheduled Items List -->
					<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
						{#each unscheduledItems as { type, item }}
							<div class="relative group opacity-60 hover:opacity-100 transition-opacity h-full">
								<!-- "Add to itinerary" indicator -->
								{#if canModify}
									<div
										class="absolute left-2 top-2 z-20 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
									>
										<div
											class="join bg-base-100/80 rounded-md p-1 shadow-sm backdrop-blur-sm border border-base-300"
										>
											<button
												aria-label={$t('itinerary.add_to_day')}
												class="btn btn-circle btn-xs btn-primary join-item shadow-sm"
												title={$t('itinerary.add_to_day')}
												on:click={() => handleOpenDayPickerForItem(type, item)}
											>
												<svg
													xmlns="http://www.w3.org/2000/svg"
													class="h-4 w-4"
													fill="none"
													viewBox="0 0 24 24"
													stroke="currentColor"
												>
													<path
														stroke-linecap="round"
														stroke-linejoin="round"
														stroke-width="2"
														d="M12 4v16m8-8H4"
													/>
												</svg>
											</button>
											<button
												aria-label={$t('itinerary.add_to_trip_context')}
												class="btn btn-circle btn-xs btn-outline join-item shadow-sm"
												title={$t('itinerary.add_to_trip_context')}
												on:click={() => addGlobalItineraryItemForObject(type, item.id)}
											>
												<svg
													xmlns="http://www.w3.org/2000/svg"
													class="h-4 w-4"
													fill="none"
													viewBox="0 0 24 24"
													stroke="currentColor"
													><path
														stroke-linecap="round"
														stroke-linejoin="round"
														stroke-width="2"
														d="M8 17l4 4 4-4m-4-13v17"
													/></svg
												>
											</button>
										</div>
									</div>
								{/if}

								<!-- Display the appropriate card -->
								{#if type === 'location'}
									<LocationCard
										adventure={item}
										on:edit={handleEditLocation}
										on:delete={handleItemDelete}
										on:duplicate={handleDuplicateLocation}
										{user}
										{collection}
										compact={true}
										showImage={false}
									/>
								{:else if type === 'transportation'}
									<TransportationCard
										transportation={item}
										{user}
										{collection}
										readOnly={!canModify}
										compact={true}
										showImage={false}
										on:delete={handleItemDelete}
										on:edit={handleEditTransportation}
									/>
								{:else if type === 'lodging'}
									<LodgingCard
										lodging={item}
										{user}
										{collection}
										showImage={false}
										compact={true}
										on:delete={handleItemDelete}
										on:edit={handleEditLodging}
									/>
								{:else if type === 'note'}
									<NoteCard
										note={item}
										{user}
										{collection}
										on:delete={handleItemDelete}
										on:edit={handleEditNote}
									/>
								{:else if type === 'checklist'}
									<ChecklistCard
										checklist={item}
										{user}
										{collection}
										on:delete={handleItemDelete}
										on:edit={handleEditChecklist}
									/>
								{/if}
							</div>
						{/each}
					</div>
				</div>
			</div>
		{/if}
	</div>
{/if}
