<script lang="ts">
	import { Button } from '$lib/components/ui/button';
	import { Textarea } from '$lib/components/ui/textarea';
	import { cn } from '$lib/utils';
	import type { BookComment } from '$lib/types';
	import * as m from '$lib/paraglide/messages.js';
	let {
		slug,
		chapter = 0,
		isLoggedIn = false,
		currentUserId = ''
	}: {
		slug: string;
		chapter?: number;   // 0 = book-level, N = chapter N
		isLoggedIn?: boolean;
		currentUserId?: string;
	} = $props();

	// ── State ─────────────────────────────────────────────────────────────────
	let comments = $state<BookComment[]>([]);
	let myVotes = $state<Record<string, 'up' | 'down'>>({});
	let avatarUrls = $state<Record<string, string>>({});
	let loading = $state(true);
	let loadError = $state('');

	// Top-level new comment
	let newBody = $state('');
	let posting = $state(false);
	let postError = $state('');

	// Sort
	let sort = $state<'new' | 'top'>('top');

	// Reply state: which comment is being replied to
	let replyingTo = $state<string | null>(null); // comment id
	let replyBody = $state('');
	let replyPosting = $state(false);
	let replyError = $state('');

	// Delete in-flight set
	let deletingIds = $state(new Set<string>());

	// Per-comment vote inflight set (prevents double-clicks)
	let votingIds = $state(new Set<string>());

	// ── Load comments ─────────────────────────────────────────────────────────
	async function loadComments() {
		loading = true;
		loadError = '';
		try {
			const res = await fetch(
				`/api/comments/${encodeURIComponent(slug)}?sort=${sort}${chapter > 0 ? `&chapter=${chapter}` : ''}`
			);
			if (!res.ok) throw new Error(`${res.status}`);
			const data = await res.json();
			comments = data.comments ?? [];
			myVotes = data.myVotes ?? {};
			avatarUrls = data.avatarUrls ?? {};
		} catch (e) {
			loadError = 'Failed to load comments.';
		} finally {
			loading = false;
		}
	}

	$effect(() => {
		loadComments();
	});

	// Re-load when sort changes (after initial mount)
	let firstLoad = true;
	$effect(() => {
		// Read sort to create a dependency
		const _ = sort;
		if (firstLoad) { firstLoad = false; return; }
		loadComments();
	});

	// ── Post top-level comment ────────────────────────────────────────────────
	async function postComment() {
		const text = newBody.trim();
		if (!text || posting) return;
		if (text.length > 2000) { postError = 'Comment is too long (max 2000 characters).'; return; }
		posting = true;
		postError = '';
		try {
			const res = await fetch(`/api/comments/${encodeURIComponent(slug)}`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ body: text, ...(chapter > 0 ? { chapter } : {}) })
			});
			if (res.status === 401) { postError = 'You must be logged in to comment.'; return; }
			if (!res.ok) {
				const err = await res.json().catch(() => ({}));
				postError = err.message ?? 'Failed to post comment.';
				return;
			}
			const created: BookComment = await res.json();
			created.replies = [];
			// Prepend for 'new', or re-sort for 'top'
			if (sort === 'new') {
				comments = [created, ...comments];
			} else {
				comments = [created, ...comments]; // new comment has 0 score, goes to end after sort would happen
			}
			newBody = '';
		} catch {
			postError = 'Failed to post comment.';
		} finally {
			posting = false;
		}
	}

	// ── Post reply ────────────────────────────────────────────────────────────
	async function postReply(parentId: string) {
		const text = replyBody.trim();
		if (!text || replyPosting) return;
		if (text.length > 2000) { replyError = 'Reply is too long (max 2000 characters).'; return; }
		replyPosting = true;
		replyError = '';
		try {
			const res = await fetch(`/api/comments/${encodeURIComponent(slug)}`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ body: text, parent_id: parentId })
			});
			if (res.status === 401) { replyError = 'You must be logged in to reply.'; return; }
			if (!res.ok) {
				const err = await res.json().catch(() => ({}));
				replyError = err.message ?? 'Failed to post reply.';
				return;
			}
			const created: BookComment = await res.json();
			// Append to the parent's replies list
			comments = comments.map((c) => {
				if (c.id !== parentId) return c;
				return { ...c, replies: [...(c.replies ?? []), created] };
			});
			replyBody = '';
			replyingTo = null;
		} catch {
			replyError = 'Failed to post reply.';
		} finally {
			replyPosting = false;
		}
	}

	// ── Delete ────────────────────────────────────────────────────────────────
	async function deleteComment(commentId: string, parentId?: string) {
		if (deletingIds.has(commentId)) return;
		deletingIds = new Set([...deletingIds, commentId]);
		try {
			const res = await fetch(`/api/comment/${commentId}`, { method: 'DELETE' });
			if (!res.ok) return;
			if (parentId) {
				// Remove reply from parent
				comments = comments.map((c) => {
					if (c.id !== parentId) return c;
					return { ...c, replies: (c.replies ?? []).filter((r) => r.id !== commentId) };
				});
			} else {
				// Remove top-level comment
				comments = comments.filter((c) => c.id !== commentId);
			}
		} finally {
			const next = new Set(deletingIds);
			next.delete(commentId);
			deletingIds = next;
		}
	}

	// ── Vote ──────────────────────────────────────────────────────────────────
	async function vote(commentId: string, v: 'up' | 'down', parentId?: string) {
		if (votingIds.has(commentId)) return;
		votingIds = new Set([...votingIds, commentId]);
		try {
			const res = await fetch(`/api/comment/${commentId}/vote`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ vote: v })
			});
			if (!res.ok) return;
			const updated: BookComment = await res.json();
			// Update comment in list (handle both top-level and replies)
			if (parentId) {
				comments = comments.map((c) => {
					if (c.id !== parentId) return c;
					return {
						...c,
						replies: (c.replies ?? []).map((r) => (r.id === commentId ? updated : r))
					};
				});
			} else {
				comments = comments.map((c) => (c.id === commentId ? { ...updated, replies: c.replies } : c));
			}
			// Update myVotes: toggle off if same, else set new vote
			const prev = myVotes[commentId];
			if (prev === v) {
				const next = { ...myVotes };
				delete next[commentId];
				myVotes = next;
			} else {
				myVotes = { ...myVotes, [commentId]: v };
			}
		} finally {
			const next = new Set(votingIds);
			next.delete(commentId);
			votingIds = next;
		}
	}

	// ── Helpers ───────────────────────────────────────────────────────────────
	function initials(username: string): string {
		const name = username.trim() || '?';
		return name.slice(0, 2).toUpperCase();
	}

	function formatDate(iso: string): string {
		try {
			const date = new Date(iso);
			const now = Date.now();
			const diffMs = now - date.getTime();
			const diffMins = Math.floor(diffMs / 60_000);
			if (diffMins < 1) return 'just now';
			if (diffMins < 60) return `${diffMins}m ago`;
			const diffHours = Math.floor(diffMins / 60);
			if (diffHours < 24) return `${diffHours}h ago`;
			const diffDays = Math.floor(diffHours / 24);
			if (diffDays < 30) return `${diffDays}d ago`;
			return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
		} catch {
			return iso;
		}
	}

	const charCount = $derived(newBody.length);
	const charOver = $derived(charCount > 2000);
	const replyCharCount = $derived(replyBody.length);
	const replyCharOver = $derived(replyCharCount > 2000);

	const totalCount = $derived(
		comments.reduce((n, c) => n + 1 + (c.replies?.length ?? 0), 0)
	);

	// ── Collapsed state ───────────────────────────────────────────────────────
	// Hidden by default when there are no comments; expand on user tap.
	let expanded = $state(false);
	const hasComments = $derived(!loading && comments.length > 0);
	// Auto-expand once comments load in
	$effect(() => {
		if (hasComments) expanded = true;
	});
</script>

<div class="mt-10">
	{#if !expanded && !hasComments && !loading}
		<!-- Collapsed: just a subtle link — no wasted real-estate for empty chapters -->
		<button
			type="button"
			onclick={() => (expanded = true)}
			class="flex items-center gap-1.5 text-sm text-(--color-muted) hover:text-(--color-text) transition-colors"
		>
			<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5">
				<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"/>
			</svg>
			{m.comments_heading()}
		</button>
	{:else}
		<!-- Expanded: full comments section -->

		<!-- Header + sort controls -->
		<div class="flex items-center justify-between gap-3 mb-4 flex-wrap">
			<h2 class="text-base font-semibold text-(--color-text)">
				{m.comments_heading()}
				{#if !loading && totalCount > 0}
					<span class="text-(--color-muted) font-normal text-sm ml-1">({totalCount})</span>
				{/if}
			</h2>
			{#if !loading && comments.length > 0}
				<div class="flex items-center gap-1 text-xs rounded-lg bg-(--color-surface-2)/60 p-1">
					<Button
						variant="ghost"
						size="sm"
						class={cn('px-2.5 py-1 h-auto text-xs rounded-md', sort === 'top' ? 'bg-(--color-surface-3) text-(--color-text) hover:bg-(--color-surface-3)' : 'text-(--color-muted) hover:text-(--color-text)')}
						onclick={() => (sort = 'top')}
					>{m.comments_top()}</Button>
					<Button
						variant="ghost"
						size="sm"
						class={cn('px-2.5 py-1 h-auto text-xs rounded-md', sort === 'new' ? 'bg-(--color-surface-3) text-(--color-text) hover:bg-(--color-surface-3)' : 'text-(--color-muted) hover:text-(--color-text)')}
						onclick={() => (sort = 'new')}
					>{m.comments_new()}</Button>
				</div>
			{/if}
		</div>

		<!-- Post form -->
		<div class="mb-6">
			{#if isLoggedIn}
				<div class="flex flex-col gap-2">
					<Textarea
						bind:value={newBody}
						placeholder={m.comments_placeholder()}
						rows={3}
					/>
					<div class="flex items-center justify-between gap-3">
						<span class={cn('text-xs tabular-nums', charOver ? 'text-(--color-danger)' : 'text-(--color-muted) opacity-60')}>
							{charCount}/2000
						</span>
						<div class="flex items-center gap-3">
							{#if postError}
								<span class="text-xs text-(--color-danger)">{postError}</span>
							{/if}
							<Button
								variant="default"
								size="sm"
								disabled={posting || !newBody.trim() || charOver}
								onclick={postComment}
							>
								{posting ? m.comments_posting() : m.comments_submit()}
							</Button>
						</div>
					</div>
				</div>
			{:else}
				<p class="text-sm text-(--color-muted)">
					<a href="/login" class="text-(--color-brand) hover:text-(--color-brand-dim) transition-colors">{m.comments_login_link()}</a>
					{m.comments_login_suffix()}
				</p>
			{/if}
		</div>

		<!-- Comment list -->
		{#if loading}
			<div class="flex flex-col gap-3">
				{#each Array(3) as _}
					<div class="rounded-lg bg-(--color-surface-2)/50 p-4 animate-pulse">
						<div class="h-3 w-24 bg-(--color-surface-3) rounded mb-3"></div>
						<div class="h-3 w-full bg-(--color-surface-3)/60 rounded mb-2"></div>
						<div class="h-3 w-3/4 bg-(--color-surface-3)/60 rounded"></div>
					</div>
				{/each}
			</div>
		{:else if loadError}
			<p class="text-sm text-(--color-danger)">{loadError}</p>
		{:else}
			<div class="flex flex-col gap-3">
				{#each comments as comment (comment.id)}
					{@const myVote = myVotes[comment.id]}
					{@const voting = votingIds.has(comment.id)}
					{@const deleting = deletingIds.has(comment.id)}
					{@const isOwner = isLoggedIn && currentUserId === comment.user_id}

					<div class={cn('rounded-lg bg-(--color-surface-2)/50 border border-(--color-border)/50 px-4 py-3 flex flex-col gap-2', deleting && 'opacity-50')}>
						<!-- Header -->
						<div class="flex items-center gap-2 flex-wrap">
							{#if avatarUrls[comment.user_id]}
								<img src={avatarUrls[comment.user_id]} alt={comment.username} class="w-6 h-6 rounded-full object-cover flex-shrink-0" />
							{:else}
								<div class="w-6 h-6 rounded-full bg-(--color-surface-3) flex items-center justify-center flex-shrink-0">
									<span class="text-[9px] font-semibold text-(--color-text) leading-none">{initials(comment.username)}</span>
								</div>
							{/if}
							{#if comment.username}
								<a href="/users/{comment.username}" class="text-sm font-medium text-(--color-text) hover:text-(--color-brand) transition-colors">{comment.username}</a>
							{:else}
								<span class="text-sm font-medium text-(--color-muted)">{m.comments_anonymous()}</span>
							{/if}
							<span class="text-(--color-muted) opacity-60 text-xs">&middot;</span>
							<span class="text-xs text-(--color-muted)">{formatDate(comment.created)}</span>
						</div>

						<!-- Body -->
						<p class="text-sm text-(--color-text) leading-relaxed whitespace-pre-wrap break-words">{comment.body}</p>

						<!-- Actions row: votes + reply + delete -->
						<div class="flex items-center gap-3 pt-1 flex-wrap">
							<Button
								variant="ghost"
								size="sm"
								class={cn('h-auto px-1 py-0 gap-1 text-xs', myVote === 'up' ? 'text-(--color-brand)' : 'text-(--color-muted) hover:text-(--color-text)')}
								disabled={voting}
								onclick={() => vote(comment.id, 'up')}
								title={m.comments_vote_up()}
							>
								<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
									<path stroke-linecap="round" stroke-linejoin="round" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/>
								</svg>
								<span class="tabular-nums">{comment.upvotes ?? 0}</span>
							</Button>

							<Button
								variant="ghost"
								size="sm"
								class={cn('h-auto px-1 py-0 gap-1 text-xs', myVote === 'down' ? 'text-(--color-danger)' : 'text-(--color-muted) hover:text-(--color-text)')}
								disabled={voting}
								onclick={() => vote(comment.id, 'down')}
								title={m.comments_vote_down()}
							>
								<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
									<path stroke-linecap="round" stroke-linejoin="round" d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5"/>
								</svg>
								<span class="tabular-nums">{comment.downvotes ?? 0}</span>
							</Button>

							{#if isLoggedIn}
								<Button
									variant="ghost"
									size="sm"
									class={cn('h-auto px-1 py-0 gap-1 text-xs', replyingTo === comment.id ? 'text-(--color-brand)' : 'text-(--color-muted) hover:text-(--color-text)')}
									onclick={() => {
										if (replyingTo === comment.id) {
											replyingTo = null; replyBody = ''; replyError = '';
										} else {
											replyingTo = comment.id; replyBody = ''; replyError = '';
										}
									}}
								>
									<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
										<path stroke-linecap="round" stroke-linejoin="round" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
									</svg>
									{m.comments_reply()}
								</Button>
							{/if}

							{#if isOwner}
								<Button
									variant="ghost"
									size="sm"
									class="h-auto px-1 py-0 gap-1 text-xs text-(--color-muted) hover:text-(--color-danger) ml-auto"
									disabled={deleting}
									onclick={() => deleteComment(comment.id)}
									title="Delete comment"
								>
									<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
										<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
									</svg>
									{m.comments_delete()}
								</Button>
							{/if}
						</div>

						<!-- Inline reply form -->
						{#if replyingTo === comment.id}
							<div class="mt-1 flex flex-col gap-2 pl-2 border-l-2 border-(--color-border)">
								<Textarea
									bind:value={replyBody}
									placeholder={m.comments_placeholder()}
									rows={2}
								/>
								<div class="flex items-center justify-between gap-2">
									<span class={cn('text-xs tabular-nums', replyCharOver ? 'text-(--color-danger)' : 'text-(--color-muted) opacity-60')}>
										{replyCharCount}/2000
									</span>
									<div class="flex items-center gap-2">
										{#if replyError}
											<span class="text-xs text-(--color-danger)">{replyError}</span>
										{/if}
										<Button
											variant="ghost"
											size="sm"
											class="text-(--color-muted) hover:text-(--color-text)"
											onclick={() => { replyingTo = null; replyBody = ''; replyError = ''; }}
										>{m.common_cancel()}</Button>
										<Button
											variant="default"
											size="sm"
											disabled={replyPosting || !replyBody.trim() || replyCharOver}
											onclick={() => postReply(comment.id)}
										>
											{replyPosting ? m.comments_posting() : m.comments_reply()}
										</Button>
									</div>
								</div>
							</div>
						{/if}

						<!-- Replies -->
						{#if comment.replies && comment.replies.length > 0}
							<div class="mt-1 flex flex-col gap-2 pl-3 border-l-2 border-(--color-border)/60">
								{#each comment.replies as reply (reply.id)}
									{@const replyVote = myVotes[reply.id]}
									{@const replyVoting = votingIds.has(reply.id)}
									{@const replyDeleting = deletingIds.has(reply.id)}
									{@const replyIsOwner = isLoggedIn && currentUserId === reply.user_id}

									<div class={cn('rounded-md bg-(--color-surface-2)/30 px-3 py-2.5 flex flex-col gap-1.5', replyDeleting && 'opacity-50')}>
										<div class="flex items-center gap-2 flex-wrap">
											{#if avatarUrls[reply.user_id]}
												<img src={avatarUrls[reply.user_id]} alt={reply.username} class="w-5 h-5 rounded-full object-cover flex-shrink-0" />
											{:else}
												<div class="w-5 h-5 rounded-full bg-(--color-surface-3) flex items-center justify-center flex-shrink-0">
													<span class="text-[8px] font-semibold text-(--color-text) leading-none">{initials(reply.username)}</span>
												</div>
											{/if}
											{#if reply.username}
												<a href="/users/{reply.username}" class="text-xs font-medium text-(--color-text) hover:text-(--color-brand) transition-colors">{reply.username}</a>
											{:else}
												<span class="text-xs font-medium text-(--color-muted)">{m.comments_anonymous()}</span>
											{/if}
											<span class="text-(--color-muted) opacity-60 text-xs">&middot;</span>
											<span class="text-xs text-(--color-muted)">{formatDate(reply.created)}</span>
										</div>

										<p class="text-sm text-(--color-text) leading-relaxed whitespace-pre-wrap break-words">{reply.body}</p>

										<div class="flex items-center gap-3 pt-0.5">
											<Button
												variant="ghost"
												size="sm"
												class={cn('h-auto px-1 py-0 gap-1 text-xs', replyVote === 'up' ? 'text-(--color-brand)' : 'text-(--color-muted) hover:text-(--color-text)')}
												disabled={replyVoting}
												onclick={() => vote(reply.id, 'up', comment.id)}
												title={m.comments_vote_up()}
											>
												<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
													<path stroke-linecap="round" stroke-linejoin="round" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/>
												</svg>
												<span class="tabular-nums">{reply.upvotes ?? 0}</span>
											</Button>

											<Button
												variant="ghost"
												size="sm"
												class={cn('h-auto px-1 py-0 gap-1 text-xs', replyVote === 'down' ? 'text-(--color-danger)' : 'text-(--color-muted) hover:text-(--color-text)')}
												disabled={replyVoting}
												onclick={() => vote(reply.id, 'down', comment.id)}
												title={m.comments_vote_down()}
											>
												<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
													<path stroke-linecap="round" stroke-linejoin="round" d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.736 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.096c.5 0 .905-.405.905-.904 0-.715.211-1.413.608-2.008L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5"/>
												</svg>
												<span class="tabular-nums">{reply.downvotes ?? 0}</span>
											</Button>

											{#if replyIsOwner}
												<Button
													variant="ghost"
													size="sm"
													class="h-auto px-1 py-0 gap-1 text-xs text-(--color-muted) hover:text-(--color-danger) ml-auto"
													disabled={replyDeleting}
													onclick={() => deleteComment(reply.id, comment.id)}
													title="Delete reply"
												>
													<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
														<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
													</svg>
													{m.comments_delete()}
												</Button>
											{/if}
										</div>
									</div>
								{/each}
							</div>
						{/if}
					</div>
				{/each}
			</div>
		{/if}
	{/if}
</div>
