<script lang="ts">
	import { untrack } from 'svelte';
	import { sessionState, setCurrentSession, removeSession, reloadSessionsForProject } from '$frontend/stores/core/sessions.svelte';
	import { projectState } from '$frontend/stores/core/projects.svelte';
	import { addNotification } from '$frontend/stores/ui/notification.svelte';
	import ws from '$frontend/utils/ws';
	import type { ChatSession } from '$shared/types/database/schema';
	import Icon from '$frontend/components/common/display/Icon.svelte';
	import AvatarBubble from '$frontend/components/common/display/AvatarBubble.svelte';
	import Modal from '$frontend/components/common/overlay/Modal.svelte';
	import Dialog from '$frontend/components/common/overlay/Dialog.svelte';
	import { presenceState, isSessionWaitingInput } from '$frontend/stores/core/presence.svelte';
	import { isSessionUnread } from '$frontend/stores/core/app.svelte';
	import { userStore } from '$frontend/stores/features/user.svelte';
	import { debug } from '$shared/utils/logger';
	import { modelStore } from '$frontend/stores/features/models.svelte';

	interface Props {
		isOpen: boolean;
		onClose: () => void;
	}

	let { isOpen = $bindable(), onClose }: Props = $props();

	// Use real session data from session store - filtered by current project
	const sessions = $derived(
		sessionState.sessions.filter(s => s.project_id === projectState.currentProject?.id)
	);

	// Helper to get relative time (last active)
	function getRelativeTime(dateString: string): string {
		const now = Date.now();
		const date = new Date(dateString).getTime();
		const diffMs = now - date;
		const diffMins = Math.floor(diffMs / 1000 / 60);
		const diffHours = Math.floor(diffMins / 60);
		const diffDays = Math.floor(diffHours / 24);

		if (diffMins < 1) return 'Just now';
		if (diffMins < 60) return `${diffMins}m ago`;
		if (diffHours < 24) return `${diffHours}h ago`;
		if (diffDays < 7) return `${diffDays}d ago`;
		if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
		if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`;
		return `${Math.floor(diffDays / 365)}y ago`;
	}

	// Helper to get last activity timestamp
	function getLastActive(session: ChatSession): string {
		const timestamp = session.last_message_at || session.ended_at || session.started_at;
		return getRelativeTime(timestamp);
	}

	// Get the engine/model display for a session
	function getSessionModel(session: ChatSession): string {
		if (session.model_id) {
			return session.model_name || modelStore.getById(session.model_id)?.engine.model.name || session.model_id;
		}
		return '';
	}

	// Get users in a specific chat session (excluding self)
	function getSessionUsers(chatSessionId: string): { userId: string; userName: string }[] {
		const projectId = projectState.currentProject?.id;
		const currentUserId = userStore.currentUser?.id;
		if (!projectId) return [];
		const status = presenceState.statuses.get(projectId);
		if (!status?.chatSessionUsers) return [];
		const users = status.chatSessionUsers[chatSessionId] || [];
		return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
	}

	// Check if a session has an active stream
	function isSessionStreaming(chatSessionId: string): boolean {
		const projectId = projectState.currentProject?.id;
		if (!projectId) return false;
		const status = presenceState.statuses.get(projectId);
		if (!status?.streams) return false;
		return status.streams.some(
			(s: any) => s.status === 'active' && s.chatSessionId === chatSessionId
		);
	}

	// Resolve a session's visual status (matches the colored dot in the list)
	type SessionStatus = 'needs_input' | 'running' | 'unread' | 'idle';
	function getSessionStatus(session: ChatSession): SessionStatus {
		if (isSessionStreaming(session.id)) {
			return isSessionWaitingInput(session.id, projectState.currentProject?.id)
				? 'needs_input'
				: 'running';
		}
		if (isSessionUnread(session.id) && session.id !== sessionState.currentSession?.id) {
			return 'unread';
		}
		return 'idle';
	}

	// Status filter state
	type StatusFilter = 'all' | 'needs_input' | 'unread' | 'running';
	let statusFilter = $state<StatusFilter>('all');

	// Search state
	let searchQuery = $state('');
	let deepSearchResults = $state<Set<string> | null>(null);
	let deepSearching = $state(false);
	let searchDebounceTimer: ReturnType<typeof setTimeout> | undefined;

	// Deep search: query backend for message-level search
	function triggerDeepSearch(query: string) {
		clearTimeout(searchDebounceTimer);
		if (!query.trim()) {
			deepSearchResults = null;
			deepSearching = false;
			return;
		}
		deepSearching = true;
		searchDebounceTimer = setTimeout(async () => {
			try {
				const result = await ws.http('sessions:search', { query: query.trim() });
				deepSearchResults = new Set(result.sessionIds);
			} catch (err) {
				debug.error('session', 'Deep search failed:', err);
				deepSearchResults = null;
			} finally {
				deepSearching = false;
			}
		}, 300);
	}

	// Trigger deep search when query changes
	$effect(() => {
		triggerDeepSearch(searchQuery);
	});

	// Sessions visible regardless of the active status filter (used for counts)
	const statusEligibleSessions = $derived(
		sessions.filter(session => {
			const messageCount = session.message_count ?? 0;
			// Show sessions that are currently streaming even if 0 messages yet
			return messageCount > 0 || isSessionStreaming(session.id);
		})
	);

	// Count of sessions per status (for the filter badges)
	const statusCounts = $derived.by(() => {
		const counts = { all: 0, needs_input: 0, unread: 0, running: 0 };
		for (const session of statusEligibleSessions) {
			counts.all++;
			const status = getSessionStatus(session);
			if (status === 'needs_input') counts.needs_input++;
			else if (status === 'unread') counts.unread++;
			else if (status === 'running') counts.running++;
		}
		return counts;
	});

	const filteredSessions = $derived(
		sessions
			.filter(session => {
				const messageCount = session.message_count ?? 0;

				// Show sessions that are currently streaming even if 0 messages yet
				if (messageCount === 0 && !isSessionStreaming(session.id)) {
					return false;
				}

				// Status filter
				if (statusFilter !== 'all' && getSessionStatus(session) !== statusFilter) {
					return false;
				}

				if (!searchQuery.trim()) return true;

				// Local match on session-level fields
				const title = session.title || session.head_title || 'New Conversation';
				const summary = session.head_summary || '';
				const localMatch =
					title.toLowerCase().includes(searchQuery.toLowerCase()) ||
					summary.toLowerCase().includes(searchQuery.toLowerCase());

				// Deep search match (message-level)
				const deepMatch = deepSearchResults?.has(session.id) ?? false;

				return localMatch || deepMatch;
			})
			.sort((a, b) => {
				const aTime = a.last_message_at || a.started_at;
				const bTime = b.last_message_at || b.started_at;
				return new Date(bTime).getTime() - new Date(aTime).getTime();
			})
	);

	// Refresh sessions from server each time the modal opens.
	$effect(() => {
		if (isOpen) {
			untrack(() => {
				reloadSessionsForProject();
			});
		}
	});

	function isActiveSession(session: ChatSession): boolean {
		return sessionState.currentSession?.id === session.id;
	}

	async function resumeSession(session: ChatSession | null) {
		if (!session) return;

		try {
			let targetSession = session;
			if (session.ended_at) {
				const reactivatedSession = await ws.http('sessions:update', { id: session.id, reactivate: true });
				if (reactivatedSession) {
					const sessionIndex = sessionState.sessions.findIndex(s => s.id === session.id);
					if (sessionIndex !== -1) {
						sessionState.sessions[sessionIndex] = reactivatedSession;
					}
					targetSession = reactivatedSession;
				}
			}

			await setCurrentSession(targetSession);
			onClose();
		} catch (error) {
			debug.error('session', 'Error resuming session:', error);
			addNotification({
				type: 'error',
				title: 'Resume Failed',
				message: 'Failed to resume session',
				duration: 5000
			});
		}
	}

	// Delete single session state
	let showDeleteDialog = $state(false);
	let sessionToDelete = $state<ChatSession | null>(null);

	function handleDeleteClick(session: ChatSession, event: MouseEvent) {
		event.stopPropagation();
		sessionToDelete = session;
		showDeleteDialog = true;
	}

	async function confirmDeleteSession() {
		if (!sessionToDelete) return;
		const deleteId = sessionToDelete.id;

		try {
			await ws.http('sessions:delete', { id: deleteId });
			removeSession(deleteId);

			addNotification({
				type: 'success',
				title: 'Session Deleted',
				message: 'Chat session and related data have been deleted',
				duration: 3000
			});

			showDeleteDialog = false;
			sessionToDelete = null;
		} catch (error) {
			debug.error('session', 'Failed to delete session:', error);
			addNotification({
				type: 'error',
				title: 'Error',
				message: 'Failed to delete session',
				duration: 5000
			});
		}
	}

	function closeDeleteDialog() {
		showDeleteDialog = false;
		sessionToDelete = null;
	}

	// Delete all sessions state
	let showDeleteAllDialog = $state(false);
	let deletingAll = $state(false);

	async function confirmDeleteAllSessions() {
		deletingAll = true;
		try {
			const result = await ws.http('sessions:delete-all', {});

			// Remove all project sessions from local state
			const projectId = projectState.currentProject?.id;
			if (projectId) {
				const toRemove = sessionState.sessions
					.filter(s => s.project_id === projectId)
					.map(s => s.id);
				for (const id of toRemove) {
					removeSession(id);
				}
			}

			addNotification({
				type: 'success',
				title: 'All Sessions Deleted',
				message: `${result.deletedCount} sessions and related data have been deleted`,
				duration: 3000
			});

			showDeleteAllDialog = false;
		} catch (error) {
			debug.error('session', 'Failed to delete all sessions:', error);
			addNotification({
				type: 'error',
				title: 'Error',
				message: 'Failed to delete all sessions',
				duration: 5000
			});
		} finally {
			deletingAll = false;
		}
	}

	function closeDeleteAllDialog() {
		showDeleteAllDialog = false;
	}

	function closeModal() {
		searchQuery = '';
		deepSearchResults = null;
		statusFilter = 'all';
		onClose();
	}

	// Infinite scroll
	const PAGE_SIZE = 20;
	let displayCount = $state(PAGE_SIZE);
	let sentinel = $state<HTMLElement | null>(null);

	const visibleSessions = $derived(filteredSessions.slice(0, displayCount));
	const hasMore = $derived(filteredSessions.length > displayCount);

	$effect(() => {
		if (isOpen) displayCount = PAGE_SIZE;
	});

	$effect(() => {
		searchQuery;
		statusFilter;
		untrack(() => {
			displayCount = PAGE_SIZE;
		});
	});

	$effect(() => {
		if (!sentinel || !hasMore) return;
		const observer = new IntersectionObserver((entries) => {
			if (entries[0]?.isIntersecting) {
				displayCount = Math.min(displayCount + PAGE_SIZE, filteredSessions.length);
			}
		}, { rootMargin: '100px' });
		observer.observe(sentinel);
		return () => observer.disconnect();
	});
</script>

<Modal bind:isOpen onClose={closeModal} size="md">
	{#snippet header()}
		<div class="flex items-center justify-between px-4 py-3 md:px-6 md:py-4">
			<h2 class="text-base md:text-lg font-bold text-slate-900 dark:text-slate-100">Sessions</h2>
			<div class="flex items-center gap-2">
				{#if filteredSessions.length > 0}
					<button
						type="button"
						class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium text-red-600 dark:text-red-400 hover:bg-red-500/10 transition-colors"
						onclick={() => (showDeleteAllDialog = true)}
						aria-label="Delete all sessions"
					>
						<Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
						<span class="hidden sm:inline">Delete All</span>
					</button>
				{/if}
				<button
					type="button"
					class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
					onclick={closeModal}
					aria-label="Close modal"
				>
					<svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
						<path
							stroke-linecap="round"
							stroke-linejoin="round"
							stroke-width="2"
							d="M6 18L18 6M6 6l12 12"
						/>
					</svg>
				</button>
			</div>
		</div>
	{/snippet}

	{#snippet children()}
		<!-- Search Box -->
		{#if sessions.length > 0}
			<div class="mb-4">
				<div
					class="flex items-center gap-2 py-2.5 px-3.5 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-lg"
				>
					<Icon name="lucide:search" class="w-4 h-4 text-slate-500 dark:text-slate-400 shrink-0" />
					<input
						type="text"
						bind:value={searchQuery}
						placeholder="Search sessions..."
						class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-500 dark:placeholder:text-slate-400"
					/>
					{#if deepSearching}
						<div class="w-3.5 h-3.5 border-2 border-slate-300 border-t-violet-500 rounded-full animate-spin shrink-0"></div>
					{/if}
					{#if searchQuery}
						<button
							type="button"
							class="flex items-center justify-center w-5 h-5 bg-transparent border-none rounded text-slate-400 cursor-pointer transition-all duration-150 hover:text-slate-600 dark:hover:text-slate-300"
							onclick={() => (searchQuery = '')}
							aria-label="Clear search"
						>
							<Icon name="lucide:x" class="w-3.5 h-3.5" />
						</button>
					{/if}
				</div>
			</div>
		{/if}

		<!-- Status Filter -->
		{#if statusEligibleSessions.length > 0}
			{@const filters = [
				{ value: 'all', label: 'All', count: statusCounts.all, dot: '' },
				{ value: 'needs_input', label: 'Needs input', count: statusCounts.needs_input, dot: 'bg-amber-500' },
				{ value: 'unread', label: 'Unread', count: statusCounts.unread, dot: 'bg-blue-500' },
				{ value: 'running', label: 'Running', count: statusCounts.running, dot: 'bg-emerald-500' }
			] as const}
			<div class="flex items-center gap-1.5 mb-4 overflow-x-auto pb-0.5">
				{#each filters as filter}
					{@const isSelected = statusFilter === filter.value}
					<button
						type="button"
						class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap transition-colors shrink-0
							{isSelected
							? 'bg-violet-500/15 text-violet-700 dark:text-violet-300 border border-violet-300 dark:border-violet-700'
							: 'bg-slate-100/80 dark:bg-slate-800/80 text-slate-600 dark:text-slate-400 border border-transparent hover:bg-slate-200/80 dark:hover:bg-slate-700/80'}"
						onclick={() => (statusFilter = filter.value)}
					>
						{#if filter.dot}
							<span class="w-2 h-2 rounded-full {filter.dot} shrink-0"></span>
						{/if}
						<span>{filter.label}</span>
						<span
							class="px-1.5 rounded-full text-[10px] font-semibold {isSelected
								? 'bg-violet-500/20 text-violet-700 dark:text-violet-300'
								: 'bg-slate-200/80 dark:bg-slate-700/80 text-slate-500 dark:text-slate-400'}"
						>
							{filter.count}
						</span>
					</button>
				{/each}
			</div>
		{/if}

		<!-- Sessions List -->
		{#if filteredSessions.length === 0}
			<div class="flex flex-col items-center gap-3 py-8 text-slate-600 dark:text-slate-500 text-sm">
				<Icon name="lucide:message-square-off" class="w-12 h-12 text-slate-400 opacity-40" />
				<p class="font-medium">No sessions found</p>
				<p class="text-xs text-slate-500 dark:text-slate-500">
					{searchQuery
						? 'Try adjusting your search'
						: statusFilter !== 'all'
							? 'No sessions match this status'
							: 'Start a new chat to see it here'}
				</p>
				{#if searchQuery || statusFilter !== 'all'}
					<button
						type="button"
						class="text-xs text-violet-600 dark:text-violet-400 underline cursor-pointer hover:text-violet-700 dark:hover:text-violet-300"
						onclick={() => {
							searchQuery = '';
							statusFilter = 'all';
						}}
					>
						{searchQuery ? 'Clear search' : 'Clear filter'}
					</button>
				{/if}
			</div>
		{:else}
			<div class="space-y-2">
				{#each visibleSessions as session (session.id)}
					{@const isActive = isActiveSession(session)}
					{@const sessionUsers = getSessionUsers(session.id)}
					{@const streaming = isSessionStreaming(session.id)}
					{@const modelName = getSessionModel(session)}
					{@const title = session.title || session.head_title || 'New Conversation'}
					{@const summary = session.head_summary || 'No messages yet'}
					{@const userCount = session.user_count ?? 0}
					<div
						class="flex items-center gap-2 w-full p-3 bg-transparent border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 text-sm text-left transition-all duration-150
							{isActive
							? 'border-violet-300 dark:border-violet-600 bg-violet-50 dark:bg-violet-900/10'
							: ''}"
					>
						<button
							type="button"
							class="flex items-center gap-3 flex-1 min-w-0 bg-transparent border-none cursor-pointer text-left"
							onclick={() => resumeSession(session)}
						>
							<div
								class="relative w-8 h-8 {isActive
									? 'bg-violet-200 dark:bg-violet-800/30'
									: 'bg-violet-100 dark:bg-violet-900/20'} rounded-lg flex items-center justify-center flex-shrink-0"
							>
								<Icon name="lucide:message-square" class="text-violet-600 dark:text-violet-400 w-4 h-4" />
								{#if streaming}
									<span
										class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 {isSessionWaitingInput(session.id, projectState.currentProject?.id) ? 'bg-amber-500' : 'bg-emerald-500'}"
									></span>
								{:else if isSessionUnread(session.id) && session.id !== sessionState.currentSession?.id}
									<span
										class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-blue-500"
									></span>
								{:else}
									<span
										class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-slate-300 dark:bg-slate-600"
									></span>
								{/if}
							</div>
							<div class="flex-1 min-w-0">
								<div class="flex items-center gap-2">
									<p class="font-semibold text-slate-900 dark:text-slate-100 truncate text-sm">
										{title}
									</p>
									{#if isActive}
										<span
											class="inline-flex items-center gap-1 px-2 py-0.5 bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-300 text-xs font-medium rounded-full shrink-0"
										>
											<Icon name="lucide:circle-check" class="w-3 h-3" />
											Active
										</span>
									{/if}
								</div>
								<div class="flex items-center gap-2 mt-0.5 text-xs text-slate-500 dark:text-slate-400">
									<span class="flex items-center gap-1 flex-none">
										<Icon name="lucide:messages-square" class="w-3 h-3" />
										{userCount}
									</span>
									<span>·</span>
									<span class="flex items-center gap-1 flex-none">
										<Icon name="lucide:clock" class="w-3 h-3" />
										{getLastActive(session)}
									</span>
									{#if modelName}
										<span>·</span>
										<span class="truncate">{modelName}</span>
									{/if}
								</div>
								{#if streaming}
								{#if isSessionWaitingInput(session.id, projectState.currentProject?.id)}
									<p class="text-xs text-amber-500 dark:text-amber-400 mt-0.5 flex items-center gap-1.5">
										<Icon name="lucide:message-circle-question-mark" class="w-3 h-3 shrink-0" />
										Waiting for input...
									</p>
								{:else}
									<p class="text-xs text-violet-500 dark:text-violet-400 mt-0.5 flex items-center gap-1.5">
										<span class="inline-block w-3 h-3 border-2 border-violet-400 border-t-transparent rounded-full animate-spin shrink-0"></span>
										Processing...
									</p>
								{/if}
							{:else}
								<p class="text-xs text-slate-400 dark:text-slate-500 truncate mt-0.5">
									{summary}
								</p>
							{/if}
							</div>
						</button>
						{#if sessionUsers.length > 0}
							<div class="flex items-center -space-x-1 shrink-0">
								{#each sessionUsers.slice(0, 2) as user}
									<AvatarBubble {user} size="sm" />
								{/each}
								{#if sessionUsers.length > 2}
									<span class="w-5 h-5 rounded-full bg-gradient-to-br from-slate-500 to-slate-600 text-white text-4xs font-bold flex items-center justify-center border-2 border-white dark:border-slate-900 z-10">
										+{sessionUsers.length - 2}
									</span>
								{/if}
							</div>
						{/if}
						<button
							type="button"
							class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg text-slate-400 dark:text-slate-500 cursor-pointer transition-all duration-150 hover:bg-red-500/15 hover:text-red-500 shrink-0"
							onclick={(e) => handleDeleteClick(session, e)}
							aria-label="Delete session"
							title="Delete"
						>
							<Icon name="lucide:trash-2" class="w-4 h-4" />
						</button>
					</div>
				{/each}
				{#if hasMore}
					<div bind:this={sentinel} class="h-4"></div>
				{/if}
			</div>
		{/if}
	{/snippet}
</Modal>

<!-- Delete Single Session Confirmation Dialog -->
<Dialog
	bind:isOpen={showDeleteDialog}
	onClose={closeDeleteDialog}
	type="error"
	title="Delete Session"
	message={sessionToDelete && isSessionStreaming(sessionToDelete.id)
		? 'This session is currently running. Deleting it will stop the active chat and permanently remove all messages, snapshots, and related data.'
		: 'Are you sure you want to delete this session? All messages, snapshots, and related data will be permanently removed.'}
	confirmText="Delete"
	cancelText="Cancel"
	onConfirm={confirmDeleteSession}
/>

<!-- Delete All Sessions Confirmation Dialog -->
<Dialog
	bind:isOpen={showDeleteAllDialog}
	onClose={closeDeleteAllDialog}
	type="error"
	title="Delete All Sessions"
	message={`Are you sure you want to delete all ${filteredSessions.length} sessions in this project? All messages, snapshots, and related data will be permanently removed. This only affects the current project.`}
	confirmText={deletingAll ? 'Deleting...' : 'Delete All'}
	cancelText="Cancel"
	onConfirm={confirmDeleteAllSessions}
/>
