import { error } from "@sveltejs/kit";
import { createBobbinClient } from "$lib/api/client";
import { enrich, miniDocOf, TYPE_MINIDOC, type LinkSource, type Sidecar } from "$lib/api/enrich";
import { IdentityCache, type MiniDoc } from "$lib/api/identity";
import {
	listIssueStates,
	type CommentListPage,
	type IssueRecord,
	type ReactionListPage,
	type RecordView,
	type ReactionRecord
} from "$lib/api/records";
import { didFromUri, rkeyFromUri } from "$lib/api/uri";
import { renderMarkup } from "$lib/markup";
import { buildCommentThreads, type ThreadInput } from "$lib/components/comment/comments";
import { buildReactions, type ReactionGroup } from "$lib/components/reaction/reactions";
import type { PageLoad } from "./$types";

export const load: PageLoad = async (event) => {
	const parent = await event.parent();
	const uri = decodeURIComponent(event.params.aturi);

	if (!uri.startsWith("at://")) error(404, "Issue not found");

	const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });

	const issuePage = await enrich<RecordView<IssueRecord>>(ctx, {
		xrpc: "sh.tangled.repo.getIssue",
		params: { issue: uri },
		enrich: [{ source: "sh.tangled.repo.issue:.repo", type: TYPE_MINIDOC }]
	}).catch(() => null);
	if (!issuePage) error(404, "Issue not found");

	const record = issuePage.output;
	const authorDid = didFromUri(record.uri);
	const identities = new IdentityCache(ctx);
	const docOrResolve = (
		data: Sidecar,
		source: LinkSource,
		did: string
	): Promise<MiniDoc | null> => {
		const doc = miniDocOf(data, did, source);
		return doc ? Promise.resolve(doc) : identities.resolve(did).catch(() => null);
	};
	const markupOpts = {
		repo: `${parent.repo.ownerHandle}/${parent.repo.name}`,
		ref: parent.repo.defaultBranch,
		host: event.url.host
	};

	const [author, states, comments] = await Promise.all([
		docOrResolve(issuePage.data, "sh.tangled.repo.issue:.repo", authorDid),
		listIssueStates(ctx, record.uri, { limit: 1, order: "desc" }).catch(() => null),
		enrich<CommentListPage>(ctx, {
			xrpc: "sh.tangled.feed.listComments",
			params: { subject: record.uri, order: "asc", limit: 100 },
			enrich: [{ source: "sh.tangled.feed.comment:.repo", type: TYPE_MINIDOC }]
		}).catch(() => null)
	]);

	const latest = states?.items[0]?.value.state;
	const state = latest?.endsWith(".closed") ? ("closed" as const) : ("open" as const);
	const body = record.value.body ?? "";
	const bodyHtml = body ? await renderMarkup(body, markupOpts) : null;
	const viewerDid = parent.auth?.did;
	const commentItems = comments?.output.items ?? [];

	// reactions: one listReactions per subject (the issue itself + each comment).
	// bobbin has no batch-by-subject query and enrich can't break counts down by
	// emoji, so fan out and group client-side. reactor docs ride along in the
	// same enrich calls
	const subjects = [record.uri, ...commentItems.map((item) => item.uri)];
	const reactionPages = await Promise.all(
		subjects.map((subject) =>
			enrich<ReactionListPage>(ctx, {
				xrpc: "sh.tangled.feed.listReactions",
				params: { subject, order: "asc", limit: 100 },
				enrich: [{ source: "sh.tangled.feed.reaction:.repo", type: TYPE_MINIDOC }]
			}).catch(() => null)
		)
	);
	const reactionsBySubject = new Map<string, RecordView<ReactionRecord>[]>();
	subjects.forEach((subject, i) =>
		reactionsBySubject.set(subject, reactionPages[i]?.output.items ?? [])
	);

	const reactorDids = new Set<string>();
	for (const items of reactionsBySubject.values()) {
		for (const item of items) reactorDids.add(didFromUri(item.uri));
	}
	// a reactor's doc can land in any subject's sidecar
	const reactorDoc = (did: string): MiniDoc | undefined => {
		for (const page of reactionPages) {
			const doc = page && miniDocOf(page.data, did, "sh.tangled.feed.reaction:.repo");
			if (doc) return doc;
		}
		return undefined;
	};
	const reactorHandles = new Map<string, string>();
	await Promise.all(
		[...reactorDids].map(async (did) => {
			const doc = reactorDoc(did) ?? (await identities.resolve(did).catch(() => null));
			reactorHandles.set(did, doc?.handle ?? did);
		})
	);
	const reactionsFor = (subject: string): ReactionGroup[] =>
		buildReactions(
			reactionsBySubject.get(subject) ?? [],
			viewerDid,
			(did) => reactorHandles.get(did) ?? did
		);

	const threadInputs: ThreadInput[] = await Promise.all(
		commentItems.map(async (item): Promise<ThreadInput> => {
			const commentDid = didFromUri(item.uri);
			const commentBody = item.value.body?.text ?? "";
			const [doc, commentBodyHtml] = await Promise.all([
				docOrResolve(comments?.data ?? {}, "sh.tangled.feed.comment:.repo", commentDid),
				commentBody ? renderMarkup(commentBody, markupOpts) : Promise.resolve(null)
			]);
			return {
				comment: {
					uri: item.uri,
					cid: item.cid,
					rkey: rkeyFromUri(item.uri),
					authorDid: commentDid,
					authorHandle: doc?.handle ?? commentDid,
					createdAt: item.value.createdAt,
					body: commentBody,
					bodyHtml: commentBodyHtml,
					reactions: reactionsFor(item.uri)
				},
				replyTo: item.value.replyTo?.uri ?? null,
				replyToCid: item.value.replyTo?.cid
			};
		})
	);

	return {
		issue: {
			uri: record.uri,
			cid: record.cid,
			rkey: rkeyFromUri(record.uri),
			title: record.value.title,
			body,
			bodyHtml,
			state,
			authorDid,
			authorHandle: author?.handle ?? authorDid,
			createdAt: record.value.createdAt,
			reactions: reactionsFor(record.uri)
		},
		comments: buildCommentThreads(threadInputs),
		markup: markupOpts
	};
};
