<script lang="ts">
	import { createEventDispatcher, onMount } from 'svelte';
	import { t } from 'svelte-i18n';
	import { mdiSend, mdiPlus, mdiDelete, mdiMenu, mdiClose } from '@mdi/js';
	import type { ChatProviderCatalogEntry } from '$lib/types.js';
	import { addToast } from '$lib/toasts';

	type ToolResultEntry = {
		name: string;
		result: unknown;
	};

	type ToolSummary = {
		icon: string;
		text: string;
	};

	type PlaceResult = {
		name: string;
		address?: string;
		rating?: number;
		latitude?: number | string;
		longitude?: number | string;
	};

	type Conversation = {
		id: string;
		title?: string;
	};

	type ChatMessage = {
		id: string;
		role: 'user' | 'assistant' | 'tool';
		content: string;
		name?: string;
		tool_calls?: Array<{ id?: string }>;
		tool_call_id?: string;
		tool_results?: ToolResultEntry[];
	};

	type ChatProviderCatalogConfiguredEntry = ChatProviderCatalogEntry & {
		instance_configured: boolean;
		user_configured: boolean;
	};

	type UserAISettingsResponse = {
		preferred_provider: string | null;
		preferred_model: string | null;
	};

	export let embedded = false;
	export let collectionId: string | undefined = undefined;
	export let collectionName: string | undefined = undefined;
	export let startDate: string | undefined = undefined;
	export let endDate: string | undefined = undefined;
	export let destination: string | undefined = undefined;

	let conversations: Conversation[] = [];
	let activeConversation: Conversation | null = null;
	let messages: ChatMessage[] = [];
	let inputMessage = '';
	let isStreaming = false;
	let sidebarOpen = !embedded;
	let streamingContent = '';

	let selectedProvider = '';
	let selectedModel = '';
	let availableModels: string[] = [];
	let modelsLoading = false;
	let chatProviders: ChatProviderCatalogConfiguredEntry[] = [];
	let providerError = '';
	let selectedProviderDefaultModel = '';
	let savedDefaultProvider = '';
	let savedDefaultModel = '';
	let initialDefaultsApplied = false;
	let loadedModelsForProvider = '';
	let showDateSelector = false;
	let selectedPlaceToAdd: PlaceResult | null = null;
	let selectedDate = '';
	let settingsOpen = false;
	let settingsDropdownRef: HTMLDetailsElement;

	const dispatch = createEventDispatcher<{
		close: void;
		itemAdded: { locationId: string; date: string };
	}>();

	const MODEL_PREFS_STORAGE_KEY = 'voyage_chat_model_prefs';
	$: promptTripContext = collectionName || destination || '';

	onMount(() => {
		void initializeChat();

		const handleOutsideSettings = (event: Event) => {
			if (!settingsOpen || !settingsDropdownRef) {
				return;
			}

			const target = event.target as Node | null;
			if (target && !settingsDropdownRef.contains(target)) {
				settingsOpen = false;
			}
		};

		const handleSettingsEscape = (event: KeyboardEvent) => {
			if (event.key === 'Escape') {
				settingsOpen = false;
			}
		};

		const outsideEvents: Array<keyof DocumentEventMap> = ['pointerdown', 'mousedown', 'touchstart'];
		outsideEvents.forEach((eventName) => {
			document.addEventListener(eventName, handleOutsideSettings);
		});
		document.addEventListener('keydown', handleSettingsEscape);

		return () => {
			outsideEvents.forEach((eventName) => {
				document.removeEventListener(eventName, handleOutsideSettings);
			});
			document.removeEventListener('keydown', handleSettingsEscape);
		};
	});

	async function initializeChat(): Promise<void> {
		await Promise.all([loadConversations(), loadProviderCatalog(), loadUserAISettings()]);
		await applyInitialDefaults();
	}

	async function loadUserAISettings(): Promise<void> {
		try {
			const res = await fetch('/api/integrations/ai-settings/', {
				credentials: 'include'
			});
			if (!res.ok) {
				return;
			}

			const settings = (await res.json()) as UserAISettingsResponse[];
			const first = settings[0];
			if (!first) {
				return;
			}

			savedDefaultProvider = (first.preferred_provider || '').trim().toLowerCase();
			savedDefaultModel = (first.preferred_model || '').trim();
		} catch (e) {
			console.error('Failed to load AI settings:', e);
		}
	}

	async function applyInitialDefaults(): Promise<void> {
		if (initialDefaultsApplied || chatProviders.length === 0) {
			return;
		}

		if (
			savedDefaultProvider &&
			chatProviders.some((provider) => provider.id === savedDefaultProvider)
		) {
			selectedProvider = savedDefaultProvider;
		} else {
			const userConfigured = chatProviders.find((provider) => provider.user_configured);
			selectedProvider = (userConfigured || chatProviders[0]).id;
		}

		await loadModelsForProvider(selectedProvider);

		if (savedDefaultModel && selectedProvider === savedDefaultProvider) {
			selectedModel = availableModels.includes(savedDefaultModel)
				? savedDefaultModel
				: selectedProviderDefaultModel || availableModels[0] || '';
		} else {
			selectedModel = selectedProviderDefaultModel || availableModels[0] || '';
		}

		saveModelPref(selectedProvider, selectedModel);
		loadedModelsForProvider = selectedProvider;
		initialDefaultsApplied = true;
	}

	async function loadProviderCatalog(): Promise<void> {
		try {
			const res = await fetch('/api/chat/providers/', {
				credentials: 'include'
			});
			if (!res.ok) {
				providerError = 'Failed to load AI providers';
				return;
			}

			const data = await res.json();
			const providers = Array.isArray(data)
				? (data as ChatProviderCatalogConfiguredEntry[])
				: ((data.providers || []) as ChatProviderCatalogConfiguredEntry[]);

			const usable = providers.filter(
				(provider) =>
					provider.available_for_chat && (provider.user_configured || provider.instance_configured)
			);
			chatProviders = usable;

			if (usable.length > 0) {
				providerError = '';
				if (selectedProvider && !usable.some((provider) => provider.id === selectedProvider)) {
					selectedProvider = '';
				}
			} else {
				selectedProvider = '';
				availableModels = [];
				providerError = 'No AI providers configured. Add an API key in Settings.';
			}
		} catch (e) {
			console.error('Failed to load provider catalog:', e);
			providerError = 'Failed to load AI providers';
		}
	}

	async function loadModelsForProvider(providerId: string) {
		if (!providerId) {
			availableModels = [];
			return;
		}

		modelsLoading = true;
		try {
			const res = await fetch(`/api/chat/providers/${providerId}/models/`, {
				credentials: 'include'
			});
			const data = await res.json();

			if (data.models && data.models.length > 0) {
				availableModels = data.models;
			} else {
				availableModels = [];
			}
		} catch (e) {
			console.error('Failed to load models:', e);
			availableModels = [];
		} finally {
			modelsLoading = false;
		}
	}

	function saveModelPref(provider: string, model: string) {
		if (typeof window === 'undefined') {
			return;
		}

		try {
			const raw = window.localStorage.getItem(MODEL_PREFS_STORAGE_KEY);
			const prefs: Record<string, string> = raw ? JSON.parse(raw) : {};
			prefs[provider] = model;
			window.localStorage.setItem(MODEL_PREFS_STORAGE_KEY, JSON.stringify(prefs));
		} catch {
			// ignore localStorage persistence failures
		}
	}

	$: selectedProviderDefaultModel =
		chatProviders.find((provider) => provider.id === selectedProvider)?.default_model ?? '';

	$: if (
		selectedProvider &&
		initialDefaultsApplied &&
		loadedModelsForProvider !== selectedProvider
	) {
		loadedModelsForProvider = selectedProvider;
		void (async () => {
			await loadModelsForProvider(selectedProvider);
			if (!selectedModel || !availableModels.includes(selectedModel)) {
				selectedModel = selectedProviderDefaultModel || availableModels[0] || '';
			}
			saveModelPref(selectedProvider, selectedModel);
		})();
	}

	$: if (selectedProvider && initialDefaultsApplied) {
		saveModelPref(selectedProvider, selectedModel);
	}

	async function loadConversations() {
		const res = await fetch('/api/chat/conversations/');
		if (res.ok) {
			conversations = await res.json();
		}
	}

	async function createConversation(): Promise<Conversation | null> {
		const res = await fetch('/api/chat/conversations/', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({})
		});
		if (!res.ok) {
			return null;
		}

		const conv: Conversation = await res.json();
		conversations = [conv, ...conversations];
		activeConversation = conv;
		messages = [];
		return conv;
	}

	async function selectConversation(conv: Conversation) {
		activeConversation = conv;
		const res = await fetch(`/api/chat/conversations/${conv.id}/`);
		if (res.ok) {
			const data = await res.json();
			messages = rebuildConversationMessages(data.messages || []);
		}
	}

	function parseStoredToolResult(msg: ChatMessage): ToolResultEntry | null {
		if (msg.role !== 'tool') {
			return null;
		}

		try {
			return {
				name: msg.name || 'tool',
				result: JSON.parse(msg.content)
			};
		} catch {
			return {
				name: msg.name || 'tool',
				result: msg.content
			};
		}
	}

	function rebuildConversationMessages(rawMessages: ChatMessage[]): ChatMessage[] {
		const rebuilt = rawMessages.map((msg) => ({
			...msg,
			tool_results: msg.tool_results ? [...msg.tool_results] : undefined
		}));

		let activeAssistant: ChatMessage | null = null;

		for (const msg of rebuilt) {
			if (msg.role === 'assistant') {
				activeAssistant = Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0 ? msg : null;
				continue;
			}

			if (msg.role !== 'tool' || !activeAssistant) {
				continue;
			}

			const toolCallIds = (activeAssistant.tool_calls || [])
				.map((toolCall) => toolCall?.id)
				.filter((toolCallId): toolCallId is string => !!toolCallId);

			if (msg.tool_call_id && toolCallIds.length > 0 && !toolCallIds.includes(msg.tool_call_id)) {
				continue;
			}

			const parsedResult = parseStoredToolResult(msg);
			if (!parsedResult) {
				continue;
			}

			activeAssistant.tool_results = [...(activeAssistant.tool_results || []), parsedResult];

			if (
				toolCallIds.length > 0 &&
				(activeAssistant.tool_results?.length || 0) >= toolCallIds.length
			) {
				activeAssistant = null;
			}
		}

		return rebuilt;
	}

	async function deleteConversation(conv: Conversation) {
		await fetch(`/api/chat/conversations/${conv.id}/`, { method: 'DELETE' });
		conversations = conversations.filter((conversation) => conversation.id !== conv.id);
		if (activeConversation?.id === conv.id) {
			activeConversation = null;
			messages = [];
		}
	}

	async function sendMessage() {
		if (!inputMessage.trim() || isStreaming) return;
		if (!chatProviders.some((provider) => provider.id === selectedProvider)) return;

		let conversation = activeConversation;
		if (!conversation) {
			conversation = await createConversation();
			if (!conversation) return;
		}

		const userMsg: ChatMessage = { role: 'user', content: inputMessage, id: crypto.randomUUID() };
		messages = [...messages, userMsg];
		const msgText = inputMessage;
		inputMessage = '';
		isStreaming = true;
		streamingContent = '';

		const assistantMsg: ChatMessage = { role: 'assistant', content: '', id: crypto.randomUUID() };
		messages = [...messages, assistantMsg];

		try {
			const res = await fetch(`/api/chat/conversations/${conversation.id}/send_message/`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					message: msgText,
					provider: selectedProvider,
					model: selectedModel || undefined,
					collection_id: collectionId,
					collection_name: collectionName,
					start_date: startDate,
					end_date: endDate,
					destination
				})
			});

			if (!res.ok) {
				const err = await res.json();
				assistantMsg.content = err.error || $t('chat.connection_error');
				messages = [...messages];
				isStreaming = false;
				return;
			}

			const reader = res.body?.getReader();
			const decoder = new TextDecoder();
			let buffer = '';

			if (!reader) {
				isStreaming = false;
				return;
			}

			while (true) {
				const { done, value } = await reader.read();
				if (done) break;

				buffer += decoder.decode(value, { stream: true });
				const lines = buffer.split('\n');
				buffer = lines.pop() || '';

				for (const line of lines) {
					if (!line.startsWith('data: ')) continue;
					const data = line.slice(6).trim();
					if (!data || data === '[DONE]') continue;

					try {
						const parsed = JSON.parse(data);

						if (parsed.error) {
							assistantMsg.content = parsed.error;
							messages = [...messages];
							break;
						}

						if (parsed.content) {
							streamingContent += parsed.content;
							assistantMsg.content = streamingContent;
							messages = [...messages];
						}

						if (parsed.tool_result) {
							const toolResult: ToolResultEntry = {
								name: parsed.tool_result.name || parsed.tool_result.tool || 'tool',
								result: parsed.tool_result.result
							};
							assistantMsg.tool_results = [...(assistantMsg.tool_results || []), toolResult];
							messages = [...messages];
						}
					} catch {
						// ignore malformed chunks
					}
				}
			}

			loadConversations();
		} catch {
			assistantMsg.content = $t('chat.connection_error');
			messages = [...messages];
		} finally {
			isStreaming = false;
		}
	}

	async function sendPresetMessage(message: string) {
		if (isStreaming || chatProviders.length === 0) {
			return;
		}

		inputMessage = message;
		await sendMessage();
	}

	function handleKeydown(e: KeyboardEvent) {
		if (e.key === 'Enter' && !e.shiftKey) {
			e.preventDefault();
			sendMessage();
		}
	}

	function hasPlaceResults(result: ToolResultEntry): boolean {
		return (
			result.name === 'search_places' &&
			typeof result.result === 'object' &&
			result.result !== null &&
			Array.isArray((result.result as { results?: unknown[] }).results)
		);
	}

	function getPlaceResults(result: ToolResultEntry): any[] {
		if (!hasPlaceResults(result)) {
			return [];
		}

		return (result.result as { results: any[] }).results;
	}

	function hasWebSearchResults(result: ToolResultEntry): boolean {
		return (
			result.name === 'web_search' &&
			typeof result.result === 'object' &&
			result.result !== null &&
			Array.isArray((result.result as { results?: unknown[] }).results)
		);
	}

	function getWebSearchResults(result: ToolResultEntry): any[] {
		if (!hasWebSearchResults(result)) {
			return [];
		}

		return (result.result as { results: any[] }).results;
	}

	function parseCoordinate(value: unknown): number | null {
		if (typeof value === 'number' && Number.isFinite(value)) {
			return value;
		}

		if (typeof value === 'string') {
			const parsed = Number(value);
			return Number.isFinite(parsed) ? parsed : null;
		}

		return null;
	}

	function hasPlaceCoordinates(place: PlaceResult): boolean {
		return parseCoordinate(place.latitude) !== null && parseCoordinate(place.longitude) !== null;
	}

	function openDateSelector(place: PlaceResult) {
		selectedPlaceToAdd = place;
		selectedDate = startDate || '';
		showDateSelector = true;
	}

	function closeDateSelector() {
		showDateSelector = false;
		selectedPlaceToAdd = null;
		selectedDate = '';
	}

	async function addPlaceToItinerary(place: PlaceResult, date: string) {
		if (!collectionId || !date) {
			return;
		}

		const latitude = parseCoordinate(place.latitude);
		const longitude = parseCoordinate(place.longitude);
		if (latitude === null || longitude === null) {
			addToast('error', $t('chat.connection_error'));
			return;
		}

		try {
			const locationResponse = await fetch('/api/locations/', {
				method: 'POST',
				credentials: 'include',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					name: place.name,
					location: place.address || place.name,
					latitude,
					longitude,
					collections: [collectionId],
					is_public: false
				})
			});

			if (!locationResponse.ok) {
				throw new Error('Failed to create location');
			}

			const location = await locationResponse.json();

			const itineraryResponse = await fetch('/api/itineraries/', {
				method: 'POST',
				credentials: 'include',
				headers: {
					'Content-Type': 'application/json'
				},
				body: JSON.stringify({
					collection: collectionId,
					content_type: 'location',
					object_id: location.id,
					date,
					order: 0
				})
			});

			if (!itineraryResponse.ok) {
				throw new Error('Failed to add to itinerary');
			}

			dispatch('itemAdded', { locationId: location.id, date });
			addToast('success', $t('added_successfully'));
			closeDateSelector();
		} catch (error) {
			console.error('Failed to add to itinerary:', error);
			addToast('error', $t('chat.connection_error'));
		}
	}

	let messagesContainer: HTMLElement;
	$: visibleMessages = messages.filter((msg) => msg.role !== 'tool');
	$: lastVisibleMessageId = visibleMessages[visibleMessages.length - 1]?.id;
	$: if (messages && messagesContainer) {
		setTimeout(() => {
			messagesContainer?.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' });
		}, 50);
	}

	function asRecord(value: unknown): Record<string, unknown> | null {
		if (!value || typeof value !== 'object' || Array.isArray(value)) {
			return null;
		}

		return value as Record<string, unknown>;
	}

	function getToolSummary(result: ToolResultEntry): ToolSummary {
		const payload = asRecord(result.result);
		const hasError = !!(payload && typeof payload.error === 'string' && payload.error.trim());

		if (hasError) {
			return {
				icon: '⚠️',
				text: `${result.name.replaceAll('_', ' ')} could not be completed.`
			};
		}

		if (result.name === 'list_trips') {
			const tripCount = Array.isArray(payload?.trips) ? payload.trips.length : 0;
			return {
				icon: '🧳',
				text:
					tripCount > 0
						? `Found ${tripCount} trip${tripCount === 1 ? '' : 's'}.`
						: 'No trips found.'
			};
		}

		if (result.name === 'get_trip_details') {
			const trip = asRecord(payload?.trip);
			const tripName = typeof trip?.name === 'string' ? trip.name : 'trip';
			const itineraryCount = Array.isArray(trip?.itinerary) ? trip.itinerary.length : 0;
			return {
				icon: '🗺️',
				text: `Loaded details for ${tripName} (${itineraryCount} itinerary item${itineraryCount === 1 ? '' : 's'}).`
			};
		}

		if (result.name === 'add_to_itinerary') {
			const location = asRecord(payload?.location);
			const locationName = typeof location?.name === 'string' ? location.name : 'location';
			return {
				icon: '📌',
				text: `Added ${locationName} to the itinerary.`
			};
		}

		if (result.name === 'get_weather') {
			const entries = Array.isArray(payload?.results) ? payload.results : [];
			const availableCount = entries.filter((entry) => asRecord(entry)?.available === true).length;
			return {
				icon: '🌤️',
				text: `Checked weather for ${entries.length} date${entries.length === 1 ? '' : 's'} (${availableCount} available).`
			};
		}

		return {
			icon: '🛠️',
			text: `${result.name.replaceAll('_', ' ')} completed.`
		};
	}
</script>

<div
	class="card"
	class:bg-base-200={!embedded}
	class:bg-base-100={embedded}
	class:shadow-xl={!embedded}
	class:border={embedded}
	class:border-base-300={embedded}
>
	<div class="card-body p-0">
		<div
			class="flex"
			class:h-[calc(100vh-64px)]={!embedded}
			class:h-[65vh]={embedded}
			class:min-h-[30rem]={embedded}
			class:max-h-[46rem]={embedded}
		>
			<div
				id="chat-conversations-sidebar"
				class="bg-base-200 flex flex-col border-r border-base-300 {embedded
					? 'w-60'
					: 'w-72'} {sidebarOpen ? '' : 'hidden'} lg:flex"
			>
				<div class="p-3 flex items-center justify-between border-b border-base-300">
					<h2 class="text-lg font-semibold">{$t('chat.conversations')}</h2>
					<button
						class="btn btn-sm btn-ghost"
						on:click={createConversation}
						title={$t('chat.new_conversation')}
					>
						<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
							<path d={mdiPlus}></path>
						</svg>
					</button>
				</div>
				<div class="flex-1 overflow-y-auto">
					{#each conversations as conv}
						<div
							class="w-full p-3 hover:bg-base-300 flex items-center gap-2 {activeConversation?.id ===
							conv.id
								? 'bg-base-300'
								: ''}"
						>
							<button
								class="flex-1 text-left truncate text-sm"
								on:click={() => selectConversation(conv)}
							>
								{conv.title || $t('chat.untitled')}
							</button>
							<button
								class="btn btn-xs btn-ghost"
								on:click={() => deleteConversation(conv)}
								title={$t('chat.delete_conversation')}
							>
								<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
									<path d={mdiDelete}></path>
								</svg>
							</button>
						</div>
					{/each}
					{#if conversations.length === 0}
						<p class="p-4 text-sm opacity-60">{$t('chat.no_conversations')}</p>
					{/if}
				</div>
			</div>

			<div class="flex-1 flex flex-col min-w-0">
				<div class="p-3 border-b border-base-300 flex items-center gap-3">
					<button
						class="btn btn-sm btn-ghost lg:hidden"
						on:click={() => (sidebarOpen = !sidebarOpen)}
						aria-controls="chat-conversations-sidebar"
						aria-expanded={sidebarOpen}
						aria-label={sidebarOpen
							? $t('chat_a11y.hide_conversations_aria')
							: $t('chat_a11y.show_conversations_aria')}
					>
						{#if sidebarOpen}
							<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
								<path d={mdiClose}></path>
							</svg>
						{:else}
							<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
								<path d={mdiMenu}></path>
							</svg>
						{/if}
					</button>
					<div class="flex items-center gap-2">
						<span class="text-2xl">✈️</span>
						<div>
							<h3 class="text-lg font-bold">
								{#if collectionName}
									{$t('travel_assistant')} · {collectionName}
								{:else}
									{$t('travel_assistant')}
								{/if}
							</h3>
						</div>
					</div>
					<div class="ml-auto flex items-center gap-2">
						<details
							class="dropdown dropdown-end"
							bind:open={settingsOpen}
							bind:this={settingsDropdownRef}
						>
							<summary
								class="btn btn-sm btn-ghost"
								aria-label={$t('chat_a11y.ai_settings_aria')}
								aria-expanded={settingsOpen}
							>
								⚙️
							</summary>
							<div
								class="dropdown-content z-20 mt-2 w-72 rounded-box border border-base-300 bg-base-100 p-3 shadow"
							>
								<div class="space-y-2">
									<label class="label py-0" for="chat-provider-select">
										<span class="label-text text-xs opacity-70">{$t('settings.provider')}</span>
									</label>
									<select
										id="chat-provider-select"
										class="select select-bordered select-sm w-full"
										bind:value={selectedProvider}
										disabled={chatProviders.length === 0}
									>
										{#each chatProviders as provider}
											<option value={provider.id}>
												{provider.label}
												{#if provider.user_configured}
													✓{/if}
											</option>
										{/each}
									</select>
									<label class="label py-0" for="chat-model-select">
										<span class="label-text text-xs opacity-70">{$t('chat.model_label')}</span>
									</label>
									<select
										id="chat-model-select"
										class="select select-bordered select-sm w-full"
										bind:value={selectedModel}
										disabled={chatProviders.length === 0}
									>
										{#if modelsLoading}
											<option value="">Loading...</option>
										{:else if availableModels.length === 0}
											<option value="">{$t('chat.model_placeholder')}</option>
										{:else}
											{#each availableModels as model}
												<option value={model}>{model}</option>
											{/each}
										{/if}
									</select>
								</div>
							</div>
						</details>
					</div>
				</div>

				{#if chatProviders.length === 0}
					<div class="p-4">
						<div class="alert alert-warning">
							<span
								>{providerError || 'No AI providers configured.'}
								<a href="/settings" class="link">Add an API key in Settings</a></span
							>
						</div>
					</div>
				{:else}
					<div class="flex-1 overflow-y-auto p-4 space-y-4" bind:this={messagesContainer}>
						{#if messages.length === 0 && !activeConversation}
							<div class="flex flex-col items-center justify-center h-full text-center">
								<div class="text-6xl opacity-40 mb-4">🌍</div>
								<h3 class="text-2xl font-bold mb-2">{$t('chat.welcome_title')}</h3>
								<p class="text-base-content/60 max-w-md">{$t('chat.welcome_message')}</p>
							</div>
						{:else}
							{#each visibleMessages as msg}
								<div class="flex {msg.role === 'user' ? 'justify-end' : 'justify-start'}">
									<div class="chat {msg.role === 'user' ? 'chat-end' : 'chat-start'}">
										<div
											class="chat-bubble {msg.role === 'user'
												? 'chat-bubble-primary'
												: 'chat-bubble-neutral'}"
										>
											<div class="whitespace-pre-wrap">{msg.content}</div>
											{#if msg.role === 'assistant' && msg.tool_results}
												<div class="mt-2 space-y-2">
													{#each msg.tool_results as result}
														{#if hasPlaceResults(result)}
															<div class="grid gap-2">
																{#each getPlaceResults(result) as place}
																	<div class="card card-compact bg-base-200 p-3">
																		<h4 class="font-semibold">{place.name}</h4>
																		{#if place.address}
																			<p class="text-sm text-base-content/70">{place.address}</p>
																		{/if}
																		{#if place.rating}
																			<div class="flex items-center gap-1 text-sm">
																				<span>⭐</span>
																				<span>{place.rating}</span>
																			</div>
																		{/if}
																		{#if collectionId}
																			<button
																				class="btn btn-xs btn-primary btn-outline mt-2"
																				on:click={() => openDateSelector(place)}
																				disabled={!hasPlaceCoordinates(place)}
																			>
																				{$t('add_to_itinerary')}
																			</button>
																		{/if}
																	</div>
																{/each}
															</div>
														{:else if hasWebSearchResults(result)}
															<div class="grid gap-2">
																{#each getWebSearchResults(result) as item}
																	<a
																		href={item.url}
																		target="_blank"
																		rel="noopener noreferrer"
																		class="card card-compact bg-base-200 p-3 hover:bg-base-300 transition-colors block"
																	>
																		<h4 class="font-semibold link">{item.title}</h4>
																		<p class="text-sm text-base-content/70 line-clamp-2">
																			{item.snippet}
																		</p>
																	</a>
																{/each}
															</div>
														{:else}
															<div class="bg-base-200 rounded p-2 text-sm flex items-center gap-2">
																<span>{getToolSummary(result).icon}</span>
																<span>{getToolSummary(result).text}</span>
															</div>
														{/if}
													{/each}
												</div>
											{/if}
											{#if msg.role === 'assistant' && isStreaming && msg.id === lastVisibleMessageId}
												<div class="mt-2 inline-flex items-center gap-2 text-xs opacity-70">
													<span class="loading loading-dots loading-sm"></span>
													<span>{$t('processing')}</span>
												</div>
											{/if}
										</div>
									</div>
								</div>
							{/each}
						{/if}
					</div>

					<div class="border-t border-base-300 p-3 sm:p-4">
						<div class:mx-auto={!embedded} class:max-w-4xl={!embedded}>
							<div
								class="mb-3 flex gap-2"
								class:flex-wrap={!embedded}
								class:overflow-x-auto={embedded}
								class:pb-1={embedded}
							>
								{#if promptTripContext}
									<button
										class="btn btn-ghost"
										class:btn-xs={embedded}
										class:btn-sm={!embedded}
										class:whitespace-nowrap={embedded}
										on:click={() =>
											sendPresetMessage(
												`What are the best restaurants to include across my ${promptTripContext} itinerary?`
											)}
										disabled={isStreaming || chatProviders.length === 0}
									>
										🍽️ Restaurants
									</button>
									<button
										class="btn btn-ghost"
										class:btn-xs={embedded}
										class:btn-sm={!embedded}
										class:whitespace-nowrap={embedded}
										on:click={() =>
											sendPresetMessage(
												`What activities should I plan across my ${promptTripContext} itinerary?`
											)}
										disabled={isStreaming || chatProviders.length === 0}
									>
										🎯 Activities
									</button>
								{/if}
								{#if startDate && endDate}
									<button
										class="btn btn-ghost"
										class:btn-xs={embedded}
										class:btn-sm={!embedded}
										class:whitespace-nowrap={embedded}
										on:click={() =>
											sendPresetMessage(
												`What should I pack for my trip from ${startDate} to ${endDate}?`
											)}
										disabled={isStreaming || chatProviders.length === 0}
									>
										🎒 Packing tips
									</button>
								{/if}
								<button
									class="btn btn-ghost"
									class:btn-xs={embedded}
									class:btn-sm={!embedded}
									class:whitespace-nowrap={embedded}
									on:click={() =>
										sendPresetMessage('Can you help me plan a day-by-day itinerary for this trip?')}
									disabled={isStreaming || chatProviders.length === 0}
								>
									📅 Itinerary help
								</button>
							</div>
						</div>
						<div class="flex items-end gap-2" class:mx-auto={!embedded} class:max-w-4xl={!embedded}>
							<textarea
								class="textarea textarea-bordered flex-1 resize-none"
								placeholder={$t('chat.input_placeholder')}
								bind:value={inputMessage}
								on:keydown={handleKeydown}
								rows="1"
								disabled={isStreaming}
							></textarea>
							<button
								class="btn btn-primary self-end"
								on:click={sendMessage}
								disabled={isStreaming || !inputMessage.trim() || chatProviders.length === 0}
								title={$t('chat.send')}
							>
								{#if isStreaming}
									<span class="loading loading-spinner loading-sm"></span>
								{:else}
									<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
										<path d={mdiSend}></path>
									</svg>
								{/if}
							</button>
						</div>
					</div>
				{/if}
			</div>
		</div>
	</div>
</div>

{#if showDateSelector && selectedPlaceToAdd}
	<div class="modal modal-open">
		<div class="modal-box">
			<h3 class="font-bold text-lg">{$t('add_to_itinerary')}</h3>
			<p class="py-4">
				{$t('add_to_which_day', { values: { placeName: selectedPlaceToAdd.name } })}
			</p>

			<input
				type="date"
				class="input input-bordered w-full"
				bind:value={selectedDate}
				min={startDate}
				max={endDate}
			/>

			<div class="modal-action">
				<button class="btn btn-ghost" on:click={closeDateSelector}>{$t('adventures.cancel')}</button
				>
				<button
					class="btn btn-primary"
					on:click={() =>
						selectedPlaceToAdd && addPlaceToItinerary(selectedPlaceToAdd, selectedDate)}
					disabled={!selectedDate}
				>
					{$t('adventures.add')}
				</button>
			</div>
		</div>
	</div>
{/if}
