<script lang="ts">
	import { resolve } from "$app/paths";
	import Pencil from "$icon/pencil";
	import Trash2 from "$icon/trash-2";
	import { deleteComment } from "$lib/api/comment";
	import { getAuth } from "$lib/auth.svelte";
	import { type MarkupContext } from "$lib/markup";
	import ErrorAlert from "$lib/components/ui/Error.svelte";
	import ReactionPicker from "$lib/components/reaction/ReactionPicker.svelte";
	import {
		upsertViewerReaction,
		withoutViewerReaction,
		type ReactionGroup,
		type ReactionKind
	} from "$lib/components/reaction/reactions";
	import Comment from "./Comment.svelte";
	import CommentBox from "./CommentBox.svelte";
	import CommentEditor from "./CommentEditor.svelte";
	import type { CommentThread, CommentView, ThreadInput } from "./comments";
	import { createAction } from "$lib/action.svelte";

	interface Props {
		thread: CommentThread;
		subjectUri: string;
		subjectCid?: string;
		markup: MarkupContext;
		onsubmitted?: (submitted: ThreadInput) => void;
		onedited?: (edited: ThreadInput) => void;
		ondeleted?: (uri: string) => void;
	}

	let { thread, subjectUri, subjectCid, markup, onsubmitted, onedited, ondeleted }: Props =
		$props();

	const auth = getAuth();
	const currentUser = $derived(auth?.currentUser ?? null);

	let replying = $state(false);
	let editingUri = $state<string | null>(null);

	let reactionsByUri = $derived.by(() => {
		const map: Record<string, ReactionGroup[]> = {};
		map[thread.self.uri] = thread.self.reactions ?? [];
		for (const reply of thread.replies) map[reply.uri] = reply.reactions ?? [];
		return map;
	});

	const setReactions = (uri: string, next: ReactionGroup[]) => {
		reactionsByUri = { ...reactionsByUri, [uri]: next };
	};

	// kinds the viewer reacted with, mapped to their record's rkey so the picker can toggle
	const reactedMap = (uri: string): Map<ReactionKind, string> =>
		new Map(
			(reactionsByUri[uri] ?? [])
				.filter((g) => g.isReacted && g.viewerRkey)
				.map((g) => [g.kind, g.viewerRkey!] as [ReactionKind, string])
		);

	const onReacted = (uri: string) => (kind: ReactionKind, rkey: string) => {
		const viewer = currentUser;
		if (!viewer) return;
		setReactions(uri, upsertViewerReaction(reactionsByUri[uri] ?? [], kind, viewer.handle, rkey));
	};

	const onUnreacted = (uri: string) => (kind: ReactionKind) => {
		const viewer = currentUser;
		if (!viewer) return;
		setReactions(
			uri,
			(reactionsByUri[uri] ?? [])
				.map((g) => (g.kind === kind ? withoutViewerReaction(g, viewer.handle) : g))
				.filter((g) => g.count > 0)
		);
	};

	const deleteSelected = createAction(async (comment: CommentView) => {
		const agent = auth?.agent;
		if (!agent) return;
		await deleteComment(agent, comment.rkey);
		ondeleted?.(comment.uri);
	});

	const handleDelete = (comment: CommentView) => {
		if (!auth?.agent || deleteSelected.loading) return;
		if (!confirm("Delete this comment? This cannot be undone.")) return;
		void deleteSelected.run(comment);
	};
</script>

{#snippet commentActions(comment: CommentView)}
	{#if currentUser}
		<div
			class="absolute top-4 right-4 flex items-center gap-2 opacity-0 transition group-hover:opacity-100 focus-within:opacity-100"
		>
			<ReactionPicker
				subjectUri={comment.uri}
				reacted={reactedMap(comment.uri)}
				onreacted={onReacted(comment.uri)}
				onunreacted={onUnreacted(comment.uri)}
			/>
			{#if currentUser.did === comment.authorDid}
				<button
					type="button"
					aria-label="Edit comment"
					class="cursor-pointer text-foreground-subtle hover:text-foreground-default"
					onclick={() => (editingUri = comment.uri)}
				>
					<Pencil class="size-3" />
				</button>
				<button
					type="button"
					aria-label="Delete comment"
					disabled={deleteSelected.loading && deleteSelected.args?.[0].uri === comment.uri}
					class="cursor-pointer text-foreground-danger hover:text-foreground-danger-strong disabled:opacity-50"
					onclick={() => handleDelete(comment)}
				>
					<Trash2 class="size-3" />
				</button>
			{/if}
		</div>
	{/if}
{/snippet}

{#snippet commentEditor(
	comment: CommentView,
	replyUri: string | undefined,
	replyCid: string | undefined
)}
	<CommentEditor
		{subjectUri}
		{subjectCid}
		replyToUri={replyUri}
		replyToCid={replyCid}
		authorDid={comment.authorDid}
		authorHandle={comment.authorHandle}
		rkey={comment.rkey}
		createdAt={comment.createdAt}
		body={comment.body}
		{markup}
		rows={4}
		placeholder="Edit your comment. Markdown is supported."
		submitLabel="Save"
		submitIcon={Pencil}
		autofocus
		onsubmitted={(edited) => {
			editingUri = null;
			onedited?.(edited);
		}}
		oncancel={() => (editingUri = null)}
	/>
{/snippet}

<div
	class="overflow-hidden rounded-sm border border-border-default bg-background-canvas bg-background-default/50 drop-shadow-xs"
>
	{#snippet selfEditor()}
		{@render commentEditor(thread.self, undefined, undefined)}
	{/snippet}
	<div class="group relative">
		<Comment
			authorHandle={thread.self.authorHandle}
			authorDid={thread.self.authorDid}
			createdAt={thread.self.createdAt}
			body={thread.self.body}
			bodyHtml={thread.self.bodyHtml}
			variant="top"
			deleted={thread.self.deleted}
			reactions={reactionsByUri[thread.self.uri]}
			subjectUri={thread.self.uri}
			onreactionschange={(next) => setReactions(thread.self.uri, next)}
			editor={editingUri === thread.self.uri ? selfEditor : undefined}
		/>
		{#if editingUri !== thread.self.uri && !thread.self.deleted}
			{@render commentActions(thread.self)}
		{/if}
	</div>

	{#if thread.replies.length}
		<div class="ml-10">
			{#each thread.replies as reply, i (reply.uri)}
				{#snippet replyEditor()}
					{@render commentEditor(reply, thread.self.uri, thread.self.cid)}
				{/snippet}
				<div class="group relative isolate -ml-4">
					<!-- thread connector; on the last reply it stops at the avatar's centre (h-8) -->
					<div
						class={`pointer-events-none absolute top-0 left-4 -z-10 w-0.5 -translate-x-1/2 bg-border-default ${i === thread.replies.length - 1 ? "h-8" : "bottom-0"}`}
					></div>
					<Comment
						authorHandle={reply.authorHandle}
						authorDid={reply.authorDid}
						createdAt={reply.createdAt}
						body={reply.body}
						bodyHtml={reply.bodyHtml}
						variant="reply"
						reactions={reactionsByUri[reply.uri]}
						subjectUri={reply.uri}
						onreactionschange={(next) => setReactions(reply.uri, next)}
						editor={editingUri === reply.uri ? replyEditor : undefined}
					/>
					{#if editingUri !== reply.uri}
						{@render commentActions(reply)}
					{/if}
				</div>
			{/each}
		</div>
	{/if}

	{#if deleteSelected.error}
		<div class="border-t border-border-default px-6 py-2">
			<ErrorAlert label={deleteSelected.error} />
		</div>
	{/if}

	{#if thread.self.deleted && !thread.self.cid}
		<!-- deleted parent with no recoverable strongRef; nothing to reply against -->
	{:else if replying && currentUser}
		<div class={thread.replies.length ? "border-t border-border-default" : ""}>
			<CommentBox
				variant="thread"
				{subjectUri}
				{subjectCid}
				replyToUri={thread.self.uri}
				replyToCid={thread.self.cid}
				authorDid={currentUser.did}
				authorHandle={currentUser.handle}
				{markup}
				autofocus
				onsubmitted={(submitted) => {
					replying = false;
					onsubmitted?.(submitted);
				}}
				oncancel={() => (replying = false)}
			/>
		</div>
	{:else}
		<div
			class={`flex items-center gap-2 bg-background-default/50 px-6 py-2 ${thread.replies.length ? "border-t border-border-default" : ""}`}
		>
			{#if currentUser}
				<button
					type="button"
					class="w-full cursor-text text-left text-foreground-subtle focus:outline-none"
					onclick={() => (replying = true)}
				>
					Leave a reply...
				</button>
			{:else}
				<span class="text-foreground-subtle">
					<a href={resolve("/login")} class="underline">Login</a> to leave a reply
				</span>
			{/if}
		</div>
	{/if}
</div>
