1<script lang="ts">
  2	import { goto } from '$app/navigation';
  3	import { page } from '$app/state';
  4	import { Trash2 } from '@lucide/svelte';
  5	import { ChatSidebarConversationItem, DialogConfirmation } from '$lib/components/app';
  6	import ScrollArea from '$lib/components/ui/scroll-area/scroll-area.svelte';
  7	import * as Sidebar from '$lib/components/ui/sidebar';
  8	import * as AlertDialog from '$lib/components/ui/alert-dialog';
  9	import Input from '$lib/components/ui/input/input.svelte';
 10	import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
 11	import { chatStore } from '$lib/stores/chat.svelte';
 12	import { getPreviewText } from '$lib/utils/text';
 13	import ChatSidebarActions from './ChatSidebarActions.svelte';
 14
 15	const sidebar = Sidebar.useSidebar();
 16
 17	let currentChatId = $derived(page.params.id);
 18	let isSearchModeActive = $state(false);
 19	let searchQuery = $state('');
 20	let showDeleteDialog = $state(false);
 21	let showEditDialog = $state(false);
 22	let selectedConversation = $state<DatabaseConversation | null>(null);
 23	let editedName = $state('');
 24	let selectedConversationNamePreview = $derived.by(() =>
 25		selectedConversation ? getPreviewText(selectedConversation.name) : ''
 26	);
 27
 28	let filteredConversations = $derived.by(() => {
 29		if (searchQuery.trim().length > 0) {
 30			return conversations().filter((conversation: { name: string }) =>
 31				conversation.name.toLowerCase().includes(searchQuery.toLowerCase())
 32			);
 33		}
 34
 35		return conversations();
 36	});
 37
 38	async function handleDeleteConversation(id: string) {
 39		const conversation = conversations().find((conv) => conv.id === id);
 40		if (conversation) {
 41			selectedConversation = conversation;
 42			showDeleteDialog = true;
 43		}
 44	}
 45
 46	async function handleEditConversation(id: string) {
 47		const conversation = conversations().find((conv) => conv.id === id);
 48		if (conversation) {
 49			selectedConversation = conversation;
 50			editedName = conversation.name;
 51			showEditDialog = true;
 52		}
 53	}
 54
 55	function handleConfirmDelete() {
 56		if (selectedConversation) {
 57			showDeleteDialog = false;
 58
 59			setTimeout(() => {
 60				conversationsStore.deleteConversation(selectedConversation.id);
 61				selectedConversation = null;
 62			}, 100); // Wait for animation to finish
 63		}
 64	}
 65
 66	function handleConfirmEdit() {
 67		if (!editedName.trim() || !selectedConversation) return;
 68
 69		showEditDialog = false;
 70
 71		conversationsStore.updateConversationName(selectedConversation.id, editedName);
 72		selectedConversation = null;
 73	}
 74
 75	export function handleMobileSidebarItemClick() {
 76		if (sidebar.isMobile) {
 77			sidebar.toggle();
 78		}
 79	}
 80
 81	export function activateSearchMode() {
 82		isSearchModeActive = true;
 83	}
 84
 85	export function editActiveConversation() {
 86		if (currentChatId) {
 87			const activeConversation = filteredConversations.find((conv) => conv.id === currentChatId);
 88
 89			if (activeConversation) {
 90				const event = new CustomEvent('edit-active-conversation', {
 91					detail: { conversationId: currentChatId }
 92				});
 93				document.dispatchEvent(event);
 94			}
 95		}
 96	}
 97
 98	async function selectConversation(id: string) {
 99		if (isSearchModeActive) {
100			isSearchModeActive = false;
101			searchQuery = '';
102		}
103
104		await goto(`#/chat/${id}`);
105	}
106
107	function handleStopGeneration(id: string) {
108		chatStore.stopGenerationForChat(id);
109	}
110</script>
111
112<ScrollArea class="h-[100vh]">
113	<Sidebar.Header class=" top-0 z-10 gap-6 bg-sidebar/50 px-4 py-4 pb-2 backdrop-blur-lg md:sticky">
114		<a href="#/" onclick={handleMobileSidebarItemClick}>
115			<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
116		</a>
117
118		<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
119	</Sidebar.Header>
120
121	<Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
122		{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
123			<Sidebar.GroupLabel>
124				{isSearchModeActive ? 'Search results' : 'Conversations'}
125			</Sidebar.GroupLabel>
126		{/if}
127
128		<Sidebar.GroupContent>
129			<Sidebar.Menu>
130				{#each filteredConversations as conversation (conversation.id)}
131					<Sidebar.MenuItem class="mb-1">
132						<ChatSidebarConversationItem
133							conversation={{
134								id: conversation.id,
135								name: conversation.name,
136								lastModified: conversation.lastModified,
137								currNode: conversation.currNode
138							}}
139							{handleMobileSidebarItemClick}
140							isActive={currentChatId === conversation.id}
141							onSelect={selectConversation}
142							onEdit={handleEditConversation}
143							onDelete={handleDeleteConversation}
144							onStop={handleStopGeneration}
145						/>
146					</Sidebar.MenuItem>
147				{/each}
148
149				{#if filteredConversations.length === 0}
150					<div class="px-2 py-4 text-center">
151						<p class="mb-4 p-4 text-sm text-muted-foreground">
152							{searchQuery.length > 0
153								? 'No results found'
154								: isSearchModeActive
155									? 'Start typing to see results'
156									: 'No conversations yet'}
157						</p>
158					</div>
159				{/if}
160			</Sidebar.Menu>
161		</Sidebar.GroupContent>
162	</Sidebar.Group>
163</ScrollArea>
164
165<DialogConfirmation
166	bind:open={showDeleteDialog}
167	title="Delete Conversation"
168	description={selectedConversation
169		? `Are you sure you want to delete "${selectedConversationNamePreview}"? This action cannot be undone and will permanently remove all messages in this conversation.`
170		: ''}
171	confirmText="Delete"
172	cancelText="Cancel"
173	variant="destructive"
174	icon={Trash2}
175	onConfirm={handleConfirmDelete}
176	onCancel={() => {
177		showDeleteDialog = false;
178		selectedConversation = null;
179	}}
180/>
181
182<AlertDialog.Root bind:open={showEditDialog}>
183	<AlertDialog.Content>
184		<AlertDialog.Header>
185			<AlertDialog.Title>Edit Conversation Name</AlertDialog.Title>
186			<AlertDialog.Description>
187				<Input
188					class="mt-4 text-foreground"
189					onkeydown={(e) => {
190						if (e.key === 'Enter') {
191							e.preventDefault();
192							handleConfirmEdit();
193						}
194					}}
195					placeholder="Enter a new name"
196					type="text"
197					bind:value={editedName}
198				/>
199			</AlertDialog.Description>
200		</AlertDialog.Header>
201		<AlertDialog.Footer>
202			<AlertDialog.Cancel
203				onclick={() => {
204					showEditDialog = false;
205					selectedConversation = null;
206				}}>Cancel</AlertDialog.Cancel
207			>
208			<AlertDialog.Action onclick={handleConfirmEdit}>Save</AlertDialog.Action>
209		</AlertDialog.Footer>
210	</AlertDialog.Content>
211</AlertDialog.Root>