<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import * as m from '$lib/paraglide/messages.js';
import { notificationStore, type NotificationItem } from '$lib/stores/notifications.svelte';
import ChronikFuerDichBox from '$lib/components/chronik/ChronikFuerDichBox.svelte';
import ChronikFilterPills from '$lib/components/chronik/ChronikFilterPills.svelte';
import ChronikTimeline from '$lib/components/chronik/ChronikTimeline.svelte';
import ChronikEmptyState from '$lib/components/chronik/ChronikEmptyState.svelte';
import ChronikErrorCard from '$lib/components/chronik/ChronikErrorCard.svelte';
import type { components } from '$lib/generated/api';
import type { FilterValue } from './+page.server';

type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];

interface Props {
	data: {
		filter: FilterValue;
		activityFeed: ActivityFeedItemDTO[];
		unreadNotifications: components['schemas']['NotificationDTO'][];
		loadError: string | null;
	};
}

const { data }: Props = $props();

// Mirror the current filter into a local state we can update on pill change.
// The effect syncs whenever the server-loaded filter changes (e.g. after goto).
// eslint-disable-next-line svelte/prefer-writable-derived -- we need this mutable for onFilterChange optimism before goto() resolves
let activeFilter = $state<FilterValue>('alle');
$effect(() => {
	activeFilter = data.filter;
});

// Prefer the live SSE singleton for unread items so newly arriving mentions
// prepend without a reload. On first mount, seed from the server-loaded unread
// set if the singleton hasn't populated yet.
onMount(() => {
	notificationStore.init();
});

onDestroy(() => {
	notificationStore.destroy();
});

const liveUnread = $derived<NotificationItem[]>(
	notificationStore.notifications.filter((n) => !n.read)
);

const seedUnread = $derived<NotificationItem[]>(
	data.unreadNotifications
		.filter((n): n is typeof n & { documentId: string; referenceId: string } =>
			Boolean(n.documentId && n.referenceId)
		)
		.map((n) => ({
			id: n.id,
			type: n.type,
			documentId: n.documentId,
			documentTitle: n.documentTitle ?? null,
			referenceId: n.referenceId,
			annotationId: n.annotationId ?? null,
			read: n.read,
			createdAt: n.createdAt,
			actorName: n.actorName ?? ''
		}))
);

// If the singleton has any data (including zero after mark-all), trust it;
// otherwise fall back to the SSR-seeded unread set.
const unread = $derived<NotificationItem[]>(
	notificationStore.notifications.length > 0 ? liveUnread : seedUnread
);

async function onFilterChange(v: FilterValue) {
	activeFilter = v;
	const url = new URL(page.url);
	if (v === 'alle') url.searchParams.delete('filter');
	else url.searchParams.set('filter', v);
	await goto(`${url.pathname}${url.search}`, {
		keepFocus: true,
		noScroll: true,
		replaceState: true
	});
}

async function onMarkRead(n: NotificationItem) {
	await notificationStore.markRead(n);
}

async function onMarkAllRead() {
	await notificationStore.markAllRead();
}

const displayFeed = $derived<ActivityFeedItemDTO[]>(
	(() => {
		const merged = data.activityFeed;
		switch (activeFilter) {
			case 'alle':
				return merged;
			case 'fuer-dich':
				return merged.filter((i) => i.kind === 'MENTION_CREATED' || i.youMentioned);
			case 'hochgeladen':
				return merged.filter((i) => i.kind === 'FILE_UPLOADED');
			case 'transkription':
				return merged.filter(
					(i) =>
						i.kind === 'TEXT_SAVED' ||
						i.kind === 'BLOCK_REVIEWED' ||
						i.kind === 'ANNOTATION_CREATED'
				);
			case 'kommentare':
				return merged.filter((i) => i.kind === 'COMMENT_ADDED' || i.kind === 'MENTION_CREATED');
		}
	})()
);

const isEmpty = $derived(displayFeed.length === 0);
const emptyVariant = $derived<'first-run' | 'filter-empty' | 'inbox-zero'>(
	data.activityFeed.length === 0 ? 'first-run' : 'filter-empty'
);

function retry() {
	location.reload();
}
</script>

<svelte:head>
	<title>{m.chronik_page_title()}</title>
</svelte:head>

<main class="mx-auto max-w-3xl px-4 py-8 sm:px-6 lg:px-8">
	<header class="mb-6 flex items-baseline justify-between">
		<h1 class="font-serif text-2xl text-ink">{m.chronik_page_title()}</h1>
	</header>

	{#if data.loadError === 'activity'}
		<ChronikErrorCard onRetry={retry} />
	{:else}
		<ChronikFuerDichBox unread={unread} onMarkRead={onMarkRead} onMarkAllRead={onMarkAllRead} />

		<div class="mt-6">
			<ChronikFilterPills value={activeFilter} onChange={onFilterChange} />
		</div>

		{#if isEmpty}
			<div class="mt-8">
				<ChronikEmptyState variant={emptyVariant} />
			</div>
		{:else}
			<ChronikTimeline items={displayFeed} />
		{/if}
	{/if}
</main>
