<script lang="ts">
import { onMount, tick, untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import type { Comment, CommentReply } from '$lib/types';
import MentionEditor from '$lib/components/MentionEditor.svelte';
import { renderBody, extractContent } from '$lib/utils/mention';
import type { MentionDTO } from '$lib/types';

type Props = {
	documentId: string;
	annotationId?: string | null;
	initialComments?: Comment[];
	loadOnMount?: boolean;
	canComment: boolean;
	currentUserId: string | null;
	canAdmin: boolean;
	targetCommentId?: string | null;
	onCountChange?: (count: number) => void;
};

let {
	documentId,
	annotationId = null,
	initialComments = [],
	loadOnMount = false,
	canComment,
	currentUserId,
	canAdmin,
	targetCommentId = null,
	onCountChange
}: Props = $props();

let comments: Comment[] = $state(untrack(() => [...initialComments]));
let highlightedCommentId: string | null = $state(untrack(() => targetCommentId ?? null));
let newText: string = $state('');
let replyingTo: string | null = $state(null);
let replyText: string = $state('');
let editingId: string | null = $state(null);
let editText: string = $state('');
let posting: boolean = $state(false);
let newMentionCandidates: MentionDTO[] = $state([]);
let replyMentionCandidates: MentionDTO[] = $state([]);
let editMentionCandidates: MentionDTO[] = $state([]);

const commentsBase = $derived(
	annotationId
		? `/api/documents/${documentId}/annotations/${annotationId}/comments`
		: `/api/documents/${documentId}/comments`
);

function timeAgo(iso: string): string {
	const diff = Date.now() - new Date(iso).getTime();
	const minutes = Math.floor(diff / 60000);
	if (minutes < 1) return 'gerade eben';
	if (minutes < 60) return `vor ${minutes} Minute${minutes === 1 ? '' : 'n'}`;
	const hours = Math.floor(minutes / 60);
	if (hours < 24) return `vor ${hours} Stunde${hours === 1 ? '' : 'n'}`;
	const days = Math.floor(hours / 24);
	return `vor ${days} Tag${days === 1 ? '' : 'en'}`;
}

function wasEdited(c: { createdAt: string; updatedAt: string }): boolean {
	return c.updatedAt > c.createdAt;
}

function canModify(c: { authorId: string | null }): boolean {
	return (currentUserId != null && c.authorId === currentUserId) || canAdmin;
}

async function reload() {
	try {
		const res = await fetch(commentsBase);
		if (res.ok) {
			comments = await res.json();
			const total = comments.reduce((s, c) => s + 1 + c.replies.length, 0);
			onCountChange?.(total);
		}
	} catch {
		/* ignore */
	}
}

async function postComment() {
	const text = newText.trim();
	if (!text || posting) return;
	posting = true;
	try {
		const { content, mentionedUserIds } = extractContent(text, newMentionCandidates);
		const res = await fetch(commentsBase, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ content, mentionedUserIds })
		});
		if (res.ok) {
			newText = '';
			newMentionCandidates = [];
			await reload();
		}
	} finally {
		posting = false;
	}
}

async function postReply(threadId: string) {
	const text = replyText.trim();
	if (!text || posting) return;
	posting = true;
	try {
		const { content, mentionedUserIds } = extractContent(text, replyMentionCandidates);
		const res = await fetch(`${commentsBase}/${threadId}/replies`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ content, mentionedUserIds })
		});
		if (res.ok) {
			replyText = '';
			replyMentionCandidates = [];
			replyingTo = null;
			await reload();
		}
	} finally {
		posting = false;
	}
}

async function saveEdit(commentId: string) {
	const text = editText.trim();
	if (!text || posting) return;
	posting = true;
	try {
		const { content, mentionedUserIds } = extractContent(text, editMentionCandidates);
		const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, {
			method: 'PATCH',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ content, mentionedUserIds })
		});
		if (res.ok) {
			editingId = null;
			editMentionCandidates = [];
			await reload();
		}
	} finally {
		posting = false;
	}
}

async function deleteComment(commentId: string) {
	if (posting) return;
	posting = true;
	try {
		const res = await fetch(`/api/documents/${documentId}/comments/${commentId}`, {
			method: 'DELETE'
		});
		if (res.ok) {
			await reload();
		}
	} finally {
		posting = false;
	}
}

function startEdit(comment: Comment | CommentReply) {
	editingId = comment.id;
	editText = comment.content;
	editMentionCandidates = [];
}

function cancelEdit() {
	editingId = null;
	editText = '';
}

function startReply(threadId: string) {
	replyingTo = threadId;
	replyText = '';
}

function cancelReply() {
	replyingTo = null;
	replyText = '';
}

onMount(async () => {
	if (loadOnMount) {
		reload();
	} else {
		const total = initialComments.reduce((s, c) => s + 1 + c.replies.length, 0);
		onCountChange?.(total);
	}

	if (targetCommentId) {
		await tick();
		requestAnimationFrame(() => {
			const el = document.querySelector(`[data-comment-id="${targetCommentId}"]`);
			el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
		});

		// Remove highlight on first user interaction
		const clearHighlight = () => {
			highlightedCommentId = null;
			document.removeEventListener('click', clearHighlight, true);
			document.removeEventListener('keydown', clearHighlight, true);
			document.removeEventListener('scroll', clearHighlight, true);
		};
		document.addEventListener('click', clearHighlight, true);
		document.addEventListener('keydown', clearHighlight, true);
		document.addEventListener('scroll', clearHighlight, true);
	}
});
</script>

<!--
  Renders a single comment or reply entry.
  showReplyButton: whether the "Reply" button appears (only on last item in a thread).
-->
{#snippet commentEntry(comment: Comment | CommentReply, threadId: string, showReplyButton: boolean)}
	{#if editingId === comment.id}
		<div class="flex flex-col gap-2">
			<MentionEditor
				bind:value={editText}
				bind:mentionCandidates={editMentionCandidates}
				rows={3}
				disabled={posting}
				onsubmit={() => saveEdit(comment.id)}
			/>
			<div class="flex items-center gap-3">
				<button
					class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
					disabled={posting}
					onclick={() => saveEdit(comment.id)}
				>
					{m.btn_save()}
				</button>
				<button
					class="font-sans text-xs text-ink-3 transition-colors hover:text-ink"
					onclick={cancelEdit}
				>
					{m.btn_cancel()}
				</button>
			</div>
		</div>
	{:else}
		<div class="flex items-start justify-between gap-2">
			<div class="min-w-0 flex-1">
				<div class="flex flex-wrap items-center gap-2">
					<span class="font-sans text-xs font-semibold text-ink">{comment.authorName}</span>
					<span class="font-sans text-xs text-ink-3">{timeAgo(comment.createdAt)}</span>
					{#if wasEdited(comment)}
						<span class="font-sans text-xs text-ink-3">
							{m.comment_edited_label()}
							{timeAgo(comment.updatedAt)}
						</span>
					{/if}
				</div>
				<p class="mt-1 font-serif text-sm leading-relaxed text-ink-2">
					<!-- eslint-disable-next-line svelte/no-at-html-tags -- renderBody escapes all HTML before injecting mention links -->
					{@html renderBody(comment.content, comment.mentionDTOs ?? [])}
				</p>
			</div>
			{#if canModify(comment)}
				<div class="flex shrink-0 items-center gap-2">
					<button
						class="font-sans text-xs text-ink-3 transition-colors hover:text-ink"
						onclick={() => startEdit(comment)}
					>
						{m.btn_edit()}
					</button>
					<button
						class="font-sans text-xs text-ink-3 transition-colors hover:text-ink"
						onclick={() => deleteComment(comment.id)}
					>
						{m.btn_delete()}
					</button>
				</div>
			{/if}
		</div>
		{#if showReplyButton && canComment}
			<div class="mt-1">
				<button
					class="font-sans text-xs font-medium text-primary transition-colors hover:text-ink-2"
					onclick={() => startReply(threadId)}
				>
					{m.comment_btn_reply()}
				</button>
			</div>
		{/if}
	{/if}
{/snippet}

<div class="space-y-4">
	{#if comments.length === 0}
		<div class="flex flex-col items-center gap-3 py-8 text-center">
			<svg
				class="h-10 w-10 text-ink-3"
				viewBox="0 0 24 24"
				fill="none"
				stroke="currentColor"
				stroke-width="1.5"
			>
				<path
					stroke-linecap="round"
					stroke-linejoin="round"
					d="M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.076-4.076a1.526 1.526 0 0 1 1.037-.443 48.282 48.282 0 0 0 5.68-.494c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"
				/>
			</svg>
			<p class="font-sans text-sm text-ink-3">{m.comment_empty_hint()}</p>
		</div>
	{/if}
	{#each comments as thread, ti (thread.id)}
		<div class={ti > 0 ? 'border-t border-line pt-4' : ''}>
			<!-- Root comment -->
			<div
				data-comment-id={thread.id}
				class={highlightedCommentId === thread.id
					? 'rounded outline-2 outline-offset-1 outline-accent transition-shadow outline-dotted'
					: ''}
			>
				{@render commentEntry(thread, thread.id, thread.replies.length === 0)}
			</div>

			<!-- Replies -->
			{#each thread.replies as reply, ri (reply.id)}
				<div
					data-comment-id={reply.id}
					class="mt-3 ml-6 border-l-2 border-line pl-4 {highlightedCommentId === reply.id
						? 'rounded outline-2 outline-offset-1 outline-accent transition-shadow outline-dotted'
						: ''}"
				>
					{@render commentEntry(reply, thread.id, ri === thread.replies.length - 1)}
				</div>
			{/each}

			<!-- Reply compose box -->
			{#if replyingTo === thread.id}
				<div class="mt-3 ml-6 flex flex-col gap-2">
					<MentionEditor
						bind:value={replyText}
						bind:mentionCandidates={replyMentionCandidates}
						rows={3}
						placeholder={m.comment_placeholder()}
						disabled={posting}
						onsubmit={() => postReply(thread.id)}
					/>
					<div class="flex items-center gap-3">
						<button
							class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
							disabled={posting}
							onclick={() => postReply(thread.id)}
						>
							{m.comment_btn_post()}
						</button>
						<button
							class="font-sans text-xs text-ink-3 transition-colors hover:text-ink"
							onclick={cancelReply}
						>
							{m.btn_cancel()}
						</button>
					</div>
				</div>
			{/if}
		</div>
	{/each}

	<!-- New top-level comment -->
	{#if canComment}
		<div class={comments.length > 0 ? 'border-t border-line pt-4' : ''}>
			<div class="flex flex-col gap-2">
				<MentionEditor
					bind:value={newText}
					bind:mentionCandidates={newMentionCandidates}
					rows={3}
					placeholder={m.comment_placeholder()}
					disabled={posting}
					onsubmit={postComment}
				/>
				<div>
					<button
						class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
						disabled={posting || !newText.trim()}
						onclick={postComment}
					>
						{m.comment_btn_post()}
					</button>
				</div>
			</div>
		</div>
	{/if}
</div>
