<script lang="ts">
	/**
	 * ChatWindow - Main container for the chat interface
	 * Handles sending messages, streaming responses, and tool execution
	 */

	import { chatState, modelsState, conversationsState, toolsState, promptsState, toastState } from '$lib/stores';
	import { serverConversationsState } from '$lib/stores/server-conversations.svelte';
	import { streamingMetricsState } from '$lib/stores/streaming-metrics.svelte';
	import { ollamaClient } from '$lib/ollama';
	import { addMessage as addStoredMessage, updateConversation, createConversation as createStoredConversation } from '$lib/storage';
	import {
		contextManager,
		generateSummary,
		selectMessagesForSummarization,
		calculateTokenSavings,
		formatSummaryAsContext,
		searchSimilar,
		formatResultsAsContext,
		getKnowledgeBaseStats
	} from '$lib/memory';
	import { runToolCalls, formatToolResultsForChat, getFunctionModel, USE_FUNCTION_MODEL } from '$lib/tools';
	import type { OllamaMessage, OllamaToolCall, OllamaToolDefinition } from '$lib/ollama';
	import type { Conversation } from '$lib/types/conversation';
	import MessageList from './MessageList.svelte';
	import ChatInput from './ChatInput.svelte';
	import EmptyState from './EmptyState.svelte';
	import ContextUsageBar from './ContextUsageBar.svelte';
	import ContextFullModal from './ContextFullModal.svelte';
	import SummaryBanner from './SummaryBanner.svelte';
	import StreamingStats from './StreamingStats.svelte';
	import SystemPromptSelector from './SystemPromptSelector.svelte';
	import ModelParametersPanel from '$lib/components/settings/ModelParametersPanel.svelte';
	import { settingsState } from '$lib/stores/settings.svelte';

	/**
	 * Props interface for ChatWindow
	 * - mode: 'new' for new chat page, 'conversation' for existing conversations
	 * - onFirstMessage: callback for when first message is sent in 'new' mode
	 * - conversation: conversation metadata when in 'conversation' mode
	 */
	interface Props {
		mode?: 'new' | 'conversation';
		onFirstMessage?: (content: string, images?: string[]) => Promise<void>;
		conversation?: Conversation | null;
		/** Bindable prop for thinking mode - synced with parent in 'new' mode */
		thinkingEnabled?: boolean;
	}

	let {
		mode = 'new',
		onFirstMessage,
		conversation,
		thinkingEnabled = $bindable(true)
	}: Props = $props();

	// Local state for abort controller
	let abortController: AbortController | null = $state(null);

	// Summarization state
	let isSummarizing = $state(false);

	// Context full modal state
	let showContextFullModal = $state(false);
	let pendingMessage: { content: string; images?: string[] } | null = $state(null);

	// Tool execution state
	let isExecutingTools = $state(false);

	// RAG (Retrieval-Augmented Generation) state
	let ragEnabled = $state(true);
	let hasKnowledgeBase = $state(false);
	let lastRagContext = $state<string | null>(null);

	// System prompt for new conversations (before a conversation is created)
	let newChatPromptId = $state<string | null>(null);

	// File picker trigger function (bound from ChatInput -> FileUpload)
	let triggerFilePicker: (() => void) | undefined = $state();

	// Derived: Check if selected model supports thinking
	const supportsThinking = $derived.by(() => {
		const caps = modelsState.selectedCapabilities;
		return caps.includes('thinking');
	});

	// Check for knowledge base on mount
	$effect(() => {
		checkKnowledgeBase();
	});

	// Track previous context state for threshold crossing detection
	let previousContextState: 'normal' | 'warning' | 'critical' | 'full' = 'normal';

	// Context warning toasts - show once per threshold crossing
	$effect(() => {
		const percentage = contextManager.contextUsage.percentage;
		let currentState: 'normal' | 'warning' | 'critical' | 'full' = 'normal';

		if (percentage >= 100) {
			currentState = 'full';
		} else if (percentage >= 95) {
			currentState = 'critical';
		} else if (percentage >= 85) {
			currentState = 'warning';
		}

		// Only show toast when crossing INTO a worse state
		if (currentState !== previousContextState) {
			if (currentState === 'warning' && previousContextState === 'normal') {
				toastState.warning('Context is 85% full. Consider starting a new chat soon.');
			} else if (currentState === 'critical' && previousContextState !== 'full') {
				toastState.warning('Context almost full (95%). Summarize or start a new chat.');
			} else if (currentState === 'full') {
				// Full state is handled by the modal, no toast needed
			}
			previousContextState = currentState;
		}
	});

	/**
	 * Check if knowledge base has any documents
	 */
	async function checkKnowledgeBase(): Promise<void> {
		try {
			const stats = await getKnowledgeBaseStats();
			hasKnowledgeBase = stats.documentCount > 0;
		} catch {
			hasKnowledgeBase = false;
		}
	}

	/**
	 * Retrieve relevant context from knowledge base for the query
	 */
	async function retrieveRagContext(query: string): Promise<string | null> {
		if (!ragEnabled || !hasKnowledgeBase) return null;

		try {
			const results = await searchSimilar(query, 3, 0.5);
			if (results.length === 0) return null;

			const context = formatResultsAsContext(results);
			return context;
		} catch (error) {
			console.error('[RAG] Failed to retrieve context:', error);
			return null;
		}
	}

	/**
	 * Convert OllamaToolCall to the format expected by tool executor
	 * Ollama doesn't provide IDs, so we generate them
	 */
	function convertToolCalls(ollamaCalls: OllamaToolCall[]): Array<{ id: string; function: { name: string; arguments: string } }> {
		return ollamaCalls.map((call, index) => ({
			id: `tool-${Date.now()}-${index}`,
			function: {
				name: call.function.name,
				arguments: JSON.stringify(call.function.arguments)
			}
		}));
	}

	/**
	 * Get tool definitions for the API call
	 */
	function getToolsForApi(): OllamaToolDefinition[] | undefined {
		if (!toolsState.toolsEnabled) return undefined;
		const tools = toolsState.getEnabledToolDefinitions();
		return tools.length > 0 ? tools as OllamaToolDefinition[] : undefined;
	}

	// Derived: Check if there are any messages
	const hasMessages = $derived(chatState.visibleMessages.length > 0);

	// Update context manager when model changes
	$effect(() => {
		const model = modelsState.selectedId;
		if (model) {
			contextManager.setModel(model);
		}
	});

	// Update context manager when messages change
	$effect(() => {
		contextManager.updateMessages(chatState.visibleMessages);
	});

	// Invalidate streaming message token cache on content update
	// Only do this occasionally (the throttling in contextManager handles the rest)
	$effect(() => {
		if (chatState.streamingMessageId && chatState.streamBuffer) {
			contextManager.invalidateMessage(chatState.streamingMessageId);
		}
	});

	// Flush pending context updates when streaming finishes
	$effect(() => {
		if (!chatState.isStreaming) {
			// Force a full context update when streaming ends
			contextManager.flushPendingUpdate();
			contextManager.updateMessages(chatState.visibleMessages, true);
		}
	});

	/**
	 * Convert chat state messages to Ollama API format
	 * Uses messagesForContext to exclude summarized originals but include summaries
	 */
	function getMessagesForApi(): OllamaMessage[] {
		return chatState.messagesForContext.map((node) => ({
			role: node.message.role as OllamaMessage['role'],
			content: node.message.content,
			images: node.message.images
		}));
	}

	/**
	 * Handle summarization of older messages
	 */
	async function handleSummarize(): Promise<void> {
		const selectedModel = modelsState.selectedId;
		if (!selectedModel || isSummarizing) return;

		const messages = chatState.visibleMessages;
		const { toSummarize, toKeep } = selectMessagesForSummarization(messages, 0);

		if (toSummarize.length === 0) {
			toastState.warning('No messages available to summarize');
			return;
		}

		isSummarizing = true;

		try {
			// Generate summary using the LLM
			const summary = await generateSummary(toSummarize, selectedModel);

			// Calculate savings for logging
			const savedTokens = calculateTokenSavings(toSummarize, summary);

			// Mark original messages as summarized (they'll be hidden from UI and context)
			const messageIdsToSummarize = toSummarize.map((node) => node.id);
			chatState.markAsSummarized(messageIdsToSummarize);

			// Insert the summary message at the beginning (after any system messages)
			chatState.insertSummaryMessage(summary);

			// Force context recalculation with updated message list
			contextManager.updateMessages(chatState.visibleMessages, true);

			// Show success notification
			toastState.success(
				`Summarized ${toSummarize.length} messages, saved ~${Math.round(savedTokens / 100) * 100} tokens`
			);
		} catch (error) {
			console.error('Summarization failed:', error);
			toastState.error('Summarization failed. Please try again.');
		} finally {
			isSummarizing = false;
		}
	}

	// =========================================================================
	// Context Full Modal Handlers
	// =========================================================================

	/**
	 * Handle "Summarize & Continue" from context full modal
	 */
	async function handleContextFullSummarize(): Promise<void> {
		showContextFullModal = false;
		await handleSummarize();

		// After summarization, try to send the pending message
		if (pendingMessage && contextManager.contextUsage.percentage < 100) {
			const { content, images } = pendingMessage;
			pendingMessage = null;
			await handleSendMessage(content, images);
		} else if (pendingMessage) {
			// Still full after summarization - show toast
			toastState.warning('Context still full after summarization. Try starting a new chat.');
			pendingMessage = null;
		}
	}

	/**
	 * Handle "Start New Chat" from context full modal
	 */
	function handleContextFullNewChat(): void {
		showContextFullModal = false;
		pendingMessage = null;
		chatState.reset();
		contextManager.reset();
		toastState.info('Started new chat. Previous conversation was saved.');
	}

	/**
	 * Handle "Continue Anyway" from context full modal
	 */
	async function handleContextFullDismiss(): Promise<void> {
		showContextFullModal = false;

		// Try to send the message anyway (may fail or get truncated)
		if (pendingMessage) {
			const { content, images } = pendingMessage;
			pendingMessage = null;
			// Bypass the context check by calling the inner logic directly
			await sendMessageInternal(content, images);
		}
	}

	/**
	 * Check if summarization is possible (enough messages)
	 */
	const canSummarizeConversation = $derived(chatState.visibleMessages.length >= 6);

	/**
	 * Send a message - checks context and may show modal
	 */
	async function handleSendMessage(content: string, images?: string[]): Promise<void> {
		const selectedModel = modelsState.selectedId;

		if (!selectedModel) {
			toastState.error('Please select a model first');
			return;
		}

		// Check if context is full (100%+)
		if (contextManager.contextUsage.percentage >= 100) {
			// Store pending message and show modal
			pendingMessage = { content, images };
			showContextFullModal = true;
			return;
		}

		await sendMessageInternal(content, images);
	}

	/**
	 * Internal: Send message and stream response (bypasses context check)
	 */
	async function sendMessageInternal(content: string, images?: string[]): Promise<void> {
		const selectedModel = modelsState.selectedId;
		if (!selectedModel) return;

		// In 'new' mode with no messages yet, create conversation first
		if (mode === 'new' && !hasMessages && onFirstMessage) {
			await onFirstMessage(content, images);
			return;
		}

		let conversationId = chatState.conversationId;

		// Auto-create conversation if none exists (fallback for edge cases)
		if (!conversationId) {
			const title = content.length > 50 ? content.substring(0, 47) + '...' : content;
			const result = await createStoredConversation({
				title,
				model: selectedModel,
				isPinned: false,
				isArchived: false
			});

			if (result.success) {
				conversationId = result.data.id;
				chatState.conversationId = conversationId;
				conversationsState.add(result.data);
			}
		}

		// Add user message to tree
		const userMessageId = chatState.addMessage({
			role: 'user',
			content,
			images
		});

		// Persist user message to IndexedDB with the SAME ID as chatState
		if (conversationId) {
			const parentId = chatState.activePath.length >= 2
				? chatState.activePath[chatState.activePath.length - 2]
				: null;
			await addStoredMessage(conversationId, { role: 'user', content, images }, parentId, userMessageId);
		}

		// Stream assistant message with optional tool support
		await streamAssistantResponse(selectedModel, userMessageId, conversationId);
	}

	/**
	 * Stream assistant response with tool call handling and RAG context
	 */
	async function streamAssistantResponse(
		model: string,
		parentMessageId: string,
		conversationId: string | null
	): Promise<void> {
		const assistantMessageId = chatState.startStreaming();
		abortController = new AbortController();

		// Start streaming metrics tracking
		streamingMetricsState.startStream();

		// Track tool calls received during streaming
		let pendingToolCalls: OllamaToolCall[] | null = null;

		try {
			let messages = getMessagesForApi();
			const tools = getToolsForApi();

			// Build system prompt from active prompt + thinking + RAG context
			const systemParts: string[] = [];

			// Wait for prompts to be loaded
			await promptsState.ready();

			// Priority: per-conversation prompt > new chat prompt > global active prompt > none
			let promptContent: string | null = null;
			if (conversation?.systemPromptId) {
				// Use per-conversation prompt
				const conversationPrompt = promptsState.get(conversation.systemPromptId);
				if (conversationPrompt) {
					promptContent = conversationPrompt.content;
				}
			} else if (newChatPromptId) {
				// Use new chat selected prompt (before conversation is created)
				const newChatPrompt = promptsState.get(newChatPromptId);
				if (newChatPrompt) {
					promptContent = newChatPrompt.content;
				}
			} else if (promptsState.activePrompt) {
				// Fall back to global active prompt
				promptContent = promptsState.activePrompt.content;
			}

			if (promptContent) {
				systemParts.push(promptContent);
			}

			// RAG: Retrieve relevant context for the last user message
			const lastUserMessage = messages.filter(m => m.role === 'user').pop();
			if (lastUserMessage && ragEnabled && hasKnowledgeBase) {
				const ragContext = await retrieveRagContext(lastUserMessage.content);
				if (ragContext) {
					lastRagContext = ragContext;
					systemParts.push(`You have access to a knowledge base. Use the following relevant context to help answer the user's question. If the context isn't relevant, you can ignore it.\n\n${ragContext}`);
				}
			}

			// Inject combined system message
			if (systemParts.length > 0) {
				const systemMessage: OllamaMessage = {
					role: 'system',
					content: systemParts.join('\n\n---\n\n')
				};
				messages = [systemMessage, ...messages];
			}

			// Use function model for tool routing if enabled and tools are present
			const chatModel = (tools && tools.length > 0 && USE_FUNCTION_MODEL)
				? getFunctionModel(model)
				: model;

			// Determine if we should use native thinking mode
			const useNativeThinking = supportsThinking && thinkingEnabled;

			// Track thinking content during streaming
			let streamingThinking = '';
			let thinkingClosed = false;

			await ollamaClient.streamChatWithCallbacks(
				{
					model: chatModel,
					messages,
					tools,
					think: useNativeThinking,
					options: settingsState.apiParameters
				},
				{
					onThinkingToken: (token) => {
						// Accumulate thinking and update the message
						if (!streamingThinking) {
							// Start the thinking block
							chatState.appendToStreaming('<think>');
						}
						streamingThinking += token;
						chatState.appendToStreaming(token);
						// Track thinking tokens for metrics
						streamingMetricsState.incrementTokens();
					},
					onToken: (token) => {
						// Close thinking block when content starts
						if (streamingThinking && !thinkingClosed) {
							chatState.appendToStreaming('</think>\n\n');
							thinkingClosed = true;
						}
						chatState.appendToStreaming(token);
						// Track content tokens for metrics
						streamingMetricsState.incrementTokens();
					},
					onToolCall: (toolCalls) => {
						// Store tool calls to process after streaming completes
						pendingToolCalls = toolCalls;
					},
					onComplete: async () => {
						// Close thinking block if it was opened but not closed (e.g., tool calls without content)
						if (streamingThinking && !thinkingClosed) {
							chatState.appendToStreaming('</think>\n\n');
							thinkingClosed = true;
						}

						chatState.finishStreaming();
						streamingMetricsState.endStream();
						abortController = null;

						// Handle tool calls if received
						if (pendingToolCalls && pendingToolCalls.length > 0) {
							await executeToolsAndContinue(
								model,
								assistantMessageId,
								pendingToolCalls,
								conversationId
							);
							return; // Tool continuation handles persistence
						}

						// Persist assistant message to IndexedDB with the SAME ID as chatState
						if (conversationId) {
							const node = chatState.messageTree.get(assistantMessageId);
							if (node) {
								await addStoredMessage(
									conversationId,
									{ role: 'assistant', content: node.message.content },
									parentMessageId,
									assistantMessageId
								);
								await updateConversation(conversationId, {});
								conversationsState.update(conversationId, {});
							}
						}
					},
					onError: (error) => {
						console.error('Streaming error:', error);
						chatState.finishStreaming();
						streamingMetricsState.endStream();
						abortController = null;
					}
				},
				abortController.signal
			);
		} catch (error) {
			toastState.error('Failed to send message. Please try again.');
			chatState.finishStreaming();
			streamingMetricsState.endStream();
			abortController = null;
		}
	}

	/**
	 * Execute tool calls and continue the conversation with results
	 */
	async function executeToolsAndContinue(
		model: string,
		assistantMessageId: string,
		toolCalls: OllamaToolCall[],
		conversationId: string | null
	): Promise<void> {
		isExecutingTools = true;

		try {
			// Convert tool calls to executor format with stable IDs
			const callIds = toolCalls.map(() => crypto.randomUUID());
			const convertedCalls = toolCalls.map((tc, i) => ({
				id: callIds[i],
				name: tc.function.name,
				arguments: tc.function.arguments
			}));

			// Execute all tools (including custom tools)
			const results = await runToolCalls(convertedCalls, undefined, toolsState.customTools);

			// Format results for model context (still needed for LLM to respond)
			const toolResultContent = formatToolResultsForChat(results);

			// Update the assistant message with structured tool call data (including results)
			const assistantNode = chatState.messageTree.get(assistantMessageId);
			if (assistantNode) {
				// Store structured tool call data WITH results for display
				// Results are shown collapsed in ToolCallDisplay - NOT as raw message content
				assistantNode.message.toolCalls = toolCalls.map((tc, i) => {
					const result = results[i];
					return {
						id: callIds[i],
						name: tc.function.name,
						arguments: JSON.stringify(tc.function.arguments),
						result: result.success ? (typeof result.result === 'object' ? JSON.stringify(result.result) : String(result.result)) : undefined,
						error: result.success ? undefined : result.error
					};
				});

				// DON'T add tool results to message content - that's what floods the UI
				// The results are stored in toolCalls and displayed by ToolCallDisplay
			}

			// Persist the assistant message (including toolCalls for reload persistence)
			if (conversationId && assistantNode) {
				const parentOfAssistant = assistantNode.parentId;
				await addStoredMessage(
					conversationId,
					{
						role: 'assistant',
						content: assistantNode.message.content,
						toolCalls: assistantNode.message.toolCalls
					},
					parentOfAssistant,
					assistantMessageId
				);
			}

			// Add tool results as a hidden message (for model context, not displayed in UI)
			const toolMessageId = chatState.addMessage({
				role: 'user',
				content: `Tool execution results:\n${toolResultContent}\n\nBased on these results, either provide a helpful response OR call another tool if you need more information.`,
				hidden: true
			});

			if (conversationId) {
				await addStoredMessage(
					conversationId,
					{ role: 'user', content: `Tool execution results:\n${toolResultContent}` },
					assistantMessageId,
					toolMessageId
				);
			}

			// Stream the final response
			await streamAssistantResponse(model, toolMessageId, conversationId);

		} catch (error) {
			toastState.error('Tool execution failed');
			// Update assistant message with error
			const node = chatState.messageTree.get(assistantMessageId);
			if (node) {
				node.message.content = `Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
			}
		} finally {
			isExecutingTools = false;
		}
	}

	/**
	 * Stop the current streaming response
	 */
	function handleStopStreaming(): void {
		if (abortController) {
			abortController.abort();
			abortController = null;
		}
		chatState.finishStreaming();
	}

	/**
	 * Regenerate the last assistant response
	 * Creates a new sibling message for the assistant response and streams a new answer
	 */
	async function handleRegenerate(): Promise<void> {
		if (!chatState.canRegenerate) return;

		const selectedModel = modelsState.selectedId;
		if (!selectedModel) return;

		// Get the last message (should be an assistant message)
		const lastMessageId = chatState.activePath[chatState.activePath.length - 1];
		const lastNode = chatState.messageTree.get(lastMessageId);
		if (!lastNode || lastNode.message.role !== 'assistant') return;

		const conversationId = chatState.conversationId;

		// Use the new startRegeneration method which creates a sibling and sets up streaming
		const newMessageId = chatState.startRegeneration(lastMessageId);
		if (!newMessageId) {
			toastState.error('Failed to regenerate response');
			return;
		}

		// Get the parent user message for context
		const parentUserMessage = chatState.getParentUserMessage(newMessageId);
		const parentUserMessageId = parentUserMessage?.id;

		abortController = new AbortController();

		// Start streaming metrics tracking
		streamingMetricsState.startStream();

		// Track tool calls received during streaming
		let pendingToolCalls: OllamaToolCall[] | null = null;

		try {
			// Get messages for API - excludes the current empty assistant message being streamed
			const messages = getMessagesForApi().filter(m => m.content !== '');
			const tools = getToolsForApi();

			// Use function model for tool routing if enabled and tools are present
			const chatModel = (tools && tools.length > 0 && USE_FUNCTION_MODEL)
				? getFunctionModel(selectedModel)
				: selectedModel;

			await ollamaClient.streamChatWithCallbacks(
				{
					model: chatModel,
					messages,
					tools,
					options: settingsState.apiParameters
				},
				{
					onToken: (token) => {
						chatState.appendToStreaming(token);
						streamingMetricsState.incrementTokens();
					},
					onToolCall: (toolCalls) => {
						pendingToolCalls = toolCalls;
					},
					onComplete: async () => {
						chatState.finishStreaming();
						streamingMetricsState.endStream();
						abortController = null;

						// Handle tool calls if received
						if (pendingToolCalls && pendingToolCalls.length > 0) {
							await executeToolsAndContinue(
								selectedModel,
								newMessageId,
								pendingToolCalls,
								conversationId
							);
							return;
						}

						// Persist regenerated assistant message to IndexedDB with the SAME ID
						if (conversationId && parentUserMessageId) {
							const node = chatState.messageTree.get(newMessageId);
							if (node) {
								await addStoredMessage(
									conversationId,
									{ role: 'assistant', content: node.message.content },
									parentUserMessageId,
									newMessageId
								);
								// Update conversation timestamp
								await updateConversation(conversationId, {});
								conversationsState.update(conversationId, {});
							}
						}
					},
					onError: (error) => {
						console.error('Regenerate error:', error);
						chatState.finishStreaming();
						streamingMetricsState.endStream();
						abortController = null;
					}
				},
				abortController.signal
			);
		} catch (error) {
			toastState.error('Failed to regenerate. Please try again.');
			chatState.finishStreaming();
			streamingMetricsState.endStream();
			abortController = null;
		}
	}

	/**
	 * Edit a user message and regenerate
	 * Creates a new sibling user message and triggers a new assistant response
	 */
	async function handleEditMessage(messageId: string, newContent: string): Promise<void> {
		const selectedModel = modelsState.selectedId;
		if (!selectedModel) return;

		// Find the message
		const node = chatState.messageTree.get(messageId);
		if (!node || node.message.role !== 'user') return;

		const conversationId = chatState.conversationId;

		// Use the new startEditWithNewBranch method which creates a sibling user message
		const newUserMessageId = chatState.startEditWithNewBranch(
			messageId,
			newContent,
			node.message.images
		);

		if (!newUserMessageId) {
			toastState.error('Failed to edit message');
			return;
		}

		// Persist the new user message to IndexedDB with the SAME ID
		if (conversationId) {
			// Get the parent of the original message (which is also the parent of our new message)
			const parentId = node.parentId;
			await addStoredMessage(
				conversationId,
				{ role: 'user', content: newContent, images: node.message.images },
				parentId,
				newUserMessageId
			);
		}

		// Stream the response using the shared function (with tool support)
		await streamAssistantResponse(selectedModel, newUserMessageId, conversationId);
	}
</script>

<div class="flex h-full flex-col bg-theme-primary">
	{#if hasMessages}
		<div class="flex-1 overflow-hidden">
			<MessageList
				onRegenerate={handleRegenerate}
				onEditMessage={handleEditMessage}
				showThinking={thinkingEnabled}
			/>
		</div>
	{:else}
		<div class="flex flex-1 items-center justify-center">
			<EmptyState />
		</div>
	{/if}

	<!-- Input area with subtle gradient fade -->
	<div class="relative">
		<!-- Gradient fade at top -->
		<div class="pointer-events-none absolute -top-8 left-0 right-0 h-8 bg-gradient-to-t from-[var(--color-bg-primary)] to-transparent"></div>

		<div class="border-t border-theme bg-theme-primary/95 backdrop-blur-sm">
			<!-- Summary recommendation banner -->
			<SummaryBanner onSummarize={handleSummarize} isLoading={isSummarizing} />

			<!-- Context usage indicator -->
			{#if hasMessages}
				<div class="px-4 pt-3">
					<ContextUsageBar />
				</div>
			{/if}

			<!-- Streaming performance stats -->
			<div class="flex justify-center px-4 pt-2">
				<StreamingStats />
			</div>

			<!-- Chat options bar: [Custom] [System Prompt] ... [Attach] [Thinking] -->
			<div class="flex items-center justify-between gap-3 px-4 pt-3">
				<!-- Left side: Settings gear + System prompt selector -->
				<div class="flex items-center gap-2">
					<button
						type="button"
						onclick={() => settingsState.togglePanel()}
						class="flex items-center gap-1.5 rounded px-2 py-1 text-xs text-theme-muted transition-colors hover:bg-theme-hover hover:text-theme-primary"
						class:bg-theme-secondary={settingsState.isPanelOpen}
						class:text-sky-400={settingsState.isPanelOpen || settingsState.useCustomParameters}
						aria-label="Toggle model parameters"
						aria-expanded={settingsState.isPanelOpen}
					>
						<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
						</svg>
						{#if settingsState.useCustomParameters}
							<span class="text-[10px]">Custom</span>
						{/if}
					</button>

					<!-- System prompt selector -->
					{#if mode === 'conversation' && conversation}
						<SystemPromptSelector
							conversationId={conversation.id}
							currentPromptId={conversation.systemPromptId}
						/>
					{:else if mode === 'new'}
						<SystemPromptSelector
							currentPromptId={newChatPromptId}
							onSelect={(promptId) => (newChatPromptId = promptId)}
						/>
					{/if}
				</div>

				<!-- Right side: Attach files + Thinking mode toggle -->
				<div class="flex items-center gap-3">
					<!-- Attach files button -->
					<button
						type="button"
						onclick={() => triggerFilePicker?.()}
						disabled={!modelsState.selectedId}
						class="flex items-center gap-1.5 rounded px-2 py-1 text-xs text-theme-muted transition-colors hover:bg-theme-hover hover:text-theme-primary disabled:cursor-not-allowed disabled:opacity-50"
						aria-label="Attach files"
					>
						<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4">
							<path fill-rule="evenodd" d="M15.621 4.379a3 3 0 0 0-4.242 0l-7 7a3 3 0 0 0 4.241 4.243h.001l.497-.5a.75.75 0 0 1 1.064 1.057l-.498.501-.002.002a4.5 4.5 0 0 1-6.364-6.364l7-7a4.5 4.5 0 0 1 6.368 6.36l-3.455 3.553A2.625 2.625 0 1 1 9.52 9.52l3.45-3.451a.75.75 0 1 1 1.061 1.06l-3.45 3.451a1.125 1.125 0 0 0 1.587 1.595l3.454-3.553a3 3 0 0 0 0-4.242Z" clip-rule="evenodd" />
						</svg>
						<span>Attach</span>
					</button>

					<!-- Thinking mode toggle -->
					{#if supportsThinking}
						<label class="flex cursor-pointer items-center gap-2 text-xs text-theme-muted">
							<span class="flex items-center gap-1">
								<span class="text-amber-400">🧠</span>
								Thinking
							</span>
							<button
								type="button"
								role="switch"
								aria-checked={thinkingEnabled}
								onclick={() => (thinkingEnabled = !thinkingEnabled)}
								class="relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 focus:ring-offset-theme-primary {thinkingEnabled ? 'bg-amber-600' : 'bg-theme-tertiary'}"
							>
								<span
									class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out {thinkingEnabled ? 'translate-x-4' : 'translate-x-0'}"
								></span>
							</button>
						</label>
					{/if}
				</div>
			</div>

			<!-- Model parameters panel -->
			<div class="px-4 pt-2">
				<ModelParametersPanel />
			</div>

			<div class="px-4 pb-4 pt-2">
				<ChatInput
					onSend={handleSendMessage}
					onStop={handleStopStreaming}
					isStreaming={chatState.isStreaming}
					disabled={!modelsState.selectedId}
					hideAttachButton={true}
					bind:triggerFilePicker
				/>
			</div>
		</div>
	</div>
</div>

<!-- Context full modal -->
<ContextFullModal
	isOpen={showContextFullModal}
	onSummarize={handleContextFullSummarize}
	onNewChat={handleContextFullNewChat}
	onDismiss={handleContextFullDismiss}
	{isSummarizing}
	canSummarize={canSummarizeConversation}
/>
