<script lang="ts">
	// Scrollbarer Container fuer alle Messages.
	// Smart-Sticky-Scroll: Wenn der User selbst gescrollt hat, springt das
	// Auto-Scroll nicht mehr zum Ende. Stattdessen erscheint ein
	// Back-to-Bottom-Button.
	//
	// Phase 9.2: MutationObserver statt ResizeObserver — feuert bei jeder
	// DOM-Aenderung (neue Messages, WorkingIndicator, Tool-Cards, Markdown).
	// $effect-Tracker scrollt nach tick() damit DOM schon gerendert ist.

	import { messages, isProcessing, type Message as ChatMessage } from '$lib/stores';
	import MessageItem from './Message.svelte';
	import WorkingIndicator from './WorkingIndicator.svelte';
	import { onMount, onDestroy, tick } from 'svelte';

	interface Props {
		sessionId?: string | null;
		streamingMessageId?: string | null;
		onEdit?: (id: string) => void;
		onRegenerate?: (id: string) => void;
		onRemember?: (m: ChatMessage) => void;
		onRewind?: (id: string) => void;
	}
	let {
		sessionId = null,
		streamingMessageId = null,
		onEdit,
		onRegenerate,
		onRemember,
		onRewind,
	}: Props = $props();

	let container: HTMLDivElement | null = null;
	let userScrolledUp = $state(false);
	let autoScrolling = false;  // Guard: ignoriert onscroll waehrend wir selbst scrollen
	let autoScrollTimer: ReturnType<typeof setTimeout> | null = null;
	let mutationObs: MutationObserver | null = null;

	// Working-Indicator: zeigen wenn Claude verarbeitet, aber noch keine
	// Assistant-Tokens da sind (vor erstem Stream + waehrend Tool-Calls).
	const showWorking = $derived.by(() => {
		if (!$isProcessing) return false;
		const last = $messages[$messages.length - 1];
		if (!last) return true;
		if (last.role !== 'assistant') return true;
		return !last.content?.trim();
	});

	function checkScroll() {
		if (!container || autoScrolling) return;
		const distance = container.scrollHeight - container.scrollTop - container.clientHeight;
		// 60 px Threshold — kleiner als vorher (100), damit der User schneller
		// wieder "drangeklebt" wird wenn er nur kurz zuruecksteht.
		const next = distance > 60;
		if (next !== userScrolledUp) userScrolledUp = next;
	}

	function releaseAutoScroll() {
		if (autoScrollTimer) {
			clearTimeout(autoScrollTimer);
			autoScrollTimer = null;
		}
		container?.removeEventListener('scrollend', releaseAutoScroll);
		autoScrolling = false;
	}

	function scrollToBottom(force = false) {
		if (!container) return;
		if (!force && userScrolledUp) return;
		// Vorigen Lock aufraeumen, sonst stapeln sich Timer/Listener
		releaseAutoScroll();
		autoScrolling = true;
		// Smooth nur bei kleinen Distanzen — bei grossem Stream-Catch-up wuerde
		// das die Anzeige ausbremsen, also dort instant.
		const distance = container.scrollHeight - container.scrollTop - container.clientHeight;
		const useSmooth = distance < 240 && !force;
		if (useSmooth) {
			container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
			// Smooth-Scroll dauert 200-500ms — den Guard erst loesen wenn der
			// Browser fertig ist. 'scrollend' deckt den Normalfall ab,
			// setTimeout ist Fallback fuer Edge-Cases (Tab im Hintergrund,
			// Container vor Ablauf neu gemountet, scrollend wird verschluckt).
			container.addEventListener('scrollend', releaseAutoScroll, { once: true });
			autoScrollTimer = setTimeout(releaseAutoScroll, 700);
		} else {
			container.scrollTop = container.scrollHeight;
			// Instant-Scroll: Guard sofort nach einem rAF loesen.
			// Zwei rAFs waren zu langsam bei schnellem Streaming — der naechste
			// Token kam bevor der Guard aufgehoben war.
			requestAnimationFrame(releaseAutoScroll);
		}
	}

	function snapToBottom() {
		userScrolledUp = false;
		scrollToBottom(true);
	}

	// Reactive-Tracker: deckt Messages, Tool-Calls, Parts und Processing ab.
	// Nach tick() scrollen, damit DOM-Aenderungen (WorkingIndicator, neue
	// Messages) schon gerendert sind bevor scrollHeight gelesen wird.
	$effect(() => {
		const last = $messages[$messages.length - 1];
		const _trackers = [
			$messages.length,
			$isProcessing,
			last?.content?.length ?? 0,
			last?.parts?.length ?? 0,
			(last?.parts && last.parts.length > 0)
				? last.parts[last.parts.length - 1]?.content?.length ?? 0
				: 0,
			last?.toolCalls?.length ?? 0,
			last?.toolCalls?.map((t) => t.status).join(',') ?? '',
		];
		void _trackers;
		tick().then(() => scrollToBottom());
	});

	// Wenn der User eine neue Message schreibt und die Antwort ankommt, soll
	// Auto-Scroll wieder anspringen — auch wenn der User vorher hochgescrollt
	// war. Reset bei Wechsel von "letzter ist user" → "letzter ist assistant".
	let lastRole: string | null = null;
	$effect(() => {
		const last = $messages[$messages.length - 1];
		const role = last?.role ?? null;
		if (role && role !== lastRole) {
			// Beim Rollenwechsel (user→assistant oder assistant→user):
			// Immer wieder ans Ende kleben. Ohne das bleibt userScrolledUp=true
			// wenn checkScroll zwischen User-Send und Assistant-Antwort feuert,
			// und der Auto-Scroll fuer die gesamte Antwort ist tot.
			userScrolledUp = false;
			requestAnimationFrame(() => scrollToBottom(true));
			lastRole = role;
		}
	});

	// Session-Wechsel: Scroll-State zuruecksetzen damit neue Session
	// immer am Ende angezeigt wird (nicht vom alten userScrolledUp blockiert)
	$effect(() => {
		if (sessionId) {
			userScrolledUp = false;
			lastRole = null;
			requestAnimationFrame(() => scrollToBottom(true));
		}
	});

	onMount(() => {
		// MutationObserver: feuert bei JEDER DOM-Aenderung im Container —
		// neue Messages, WorkingIndicator ein/aus, Tool-Card-Expansion,
		// Markdown-Rendering, Diff-Aufklappen. Robuster als ResizeObserver
		// (Container-Groesse ist bei overflow:auto konstant, nur scrollHeight
		// waechst — ResizeObserver feuert da nicht).
		if (container) {
			mutationObs = new MutationObserver(() => {
				if (!userScrolledUp) scrollToBottom();
			});
			mutationObs.observe(container, {
				childList: true,
				subtree: true,
				characterData: true,
			});
		}
	});

	onDestroy(() => {
		if (mutationObs) {
			mutationObs.disconnect();
			mutationObs = null;
		}
		releaseAutoScroll();
	});
</script>

<div class="message-list" bind:this={container} onscroll={checkScroll}>
	{#each $messages as msg, i (msg.id)}
		<MessageItem
			message={msg}
			isLast={i === $messages.length - 1}
			isStreaming={msg.id === streamingMessageId}
			{onEdit}
			{onRegenerate}
			{onRemember}
			{onRewind}
		/>
	{/each}

	{#if showWorking}
		<WorkingIndicator />
	{/if}

	{#if $messages.length === 0}
		<div class="empty">
			<p class="empty-title">✱ Claude Code</p>
			<p class="empty-hint">Stelle eine Frage, lass Code analysieren, oder tippe <code>/</code> fuer Befehle.</p>
		</div>
	{/if}
</div>

{#if userScrolledUp}
	<button class="scroll-bottom" onclick={snapToBottom} title="Zum Ende scrollen">
		↓ Neue Nachrichten
	</button>
{/if}

<style>
	.message-list {
		flex: 1;
		overflow-y: auto;
		overflow-x: hidden;
		background: var(--vscode-editor-background);
		padding: 4px 0;
		scroll-behavior: auto;  /* smooth wird per JS gesteuert */
	}

	.empty {
		padding: 60px 24px;
		text-align: center;
		color: var(--vscode-descriptionForeground);
	}
	.empty-title {
		font-size: 18px;
		color: var(--vscode-button-background);
		margin-bottom: 6px;
	}
	.empty-hint {
		font-size: 13px;
	}
	.empty code {
		background: var(--vscode-input-background);
		padding: 1px 5px;
		border-radius: 2px;
		font-family: var(--font-mono);
	}

	.scroll-bottom {
		position: absolute;
		right: 16px;
		bottom: 90px;
		background: var(--vscode-button-background);
		color: var(--vscode-button-foreground);
		font-size: 11.5px;
		padding: 5px 10px;
		border-radius: 12px;
		box-shadow: 0 2px 8px var(--vscode-widget-shadow);
		z-index: 5;
		animation: bounce-in 220ms ease-out;
	}
	.scroll-bottom:hover { background: var(--vscode-button-hoverBackground); }

	@keyframes bounce-in {
		from { opacity: 0; transform: translateY(8px) scale(0.9); }
		to   { opacity: 1; transform: translateY(0)   scale(1); }
	}
</style>
