import { error, redirect } from "@sveltejs/kit";
import { createBobbinClient } from "$lib/api/client";
import { count } from "$lib/api/count";
import { getStarRkey } from "$lib/api/graph";
import { IdentityCache, resolveMiniDoc } from "$lib/api/identity";
import { getDefaultBranch } from "$lib/api/knot";
import { parallel, toHttpError } from "$lib/api/load";
import { getRepo } from "$lib/api/records";
import { repoNameOf, resolveRepoByName } from "$lib/api/repo";
import { didFromUri, rkeyFromUri } from "$lib/api/uri";
import type { BobbinContext } from "$lib/api/client";
import type { RepoCounts, RepoInfo, RepoSource } from "$lib/components/repo/types";
import type { LayoutLoad } from "./$types";

const FALLBACK_BRANCH = "main";

const resolveSource = async (
	ctx: BobbinContext,
	uri: string | undefined
): Promise<RepoSource | null> => {
	if (!uri?.startsWith("at://")) return null;
	try {
		const view = await getRepo(ctx, uri);
		const ownerDid = didFromUri(view.uri);
		const owner = await new IdentityCache(ctx).resolve(ownerDid).catch(() => null);
		return { ownerHandle: owner?.handle ?? ownerDid, name: repoNameOf(view) };
	} catch {
		// a fork whose source is gone still renders, just without the attribution
		return null;
	}
};

// the layout reset also cuts us off from `[handle]/+layout.ts`, so identity gets
// resolved again here
export const load: LayoutLoad = async (event) => {
	const parent = await event.parent();
	const identifier = decodeURIComponent(event.params.handle);
	const name = decodeURIComponent(event.params.repo);

	// rejects bare words so unrelated paths 404 instead of resolving as actors
	if (!identifier.startsWith("did:") && !identifier.includes(".")) {
		error(404, "Not found");
	}

	const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });
	const doc = await resolveMiniDoc(ctx, identifier).catch((cause) =>
		toHttpError(cause, "Could not resolve user")
	);

	// redirects dids and stale handles to the canonical handle url
	const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null;
	if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) {
		redirect(307, `/${canonical}/${event.params.repo}${event.url.search}`);
	}

	const view = await resolveRepoByName(ctx, doc.did, name).catch((cause) =>
		toHttpError(cause, "Could not load repository")
	);
	if (!view) error(404, `${doc.handle}/${name} does not exist`);

	const record = view.value;
	const repoDid = record.repoDid;
	const viewerDid = parent.auth?.did;

	const stats = await parallel({
		// only the knot knows the default branch. a knot that is down or still
		// syncing shouldn't take out the whole layout
		defaultBranch: getDefaultBranch(ctx, { repo: view.uri })
			.then((branch) => branch.name)
			.catch(() => null),
		stars: repoDid
			? count(ctx, "sh.tangled.feed.countStars", repoDid).catch(() => null)
			: Promise.resolve(null),
		// the tabs count what is still open, closed ones matter on their own pages
		issues: repoDid
			? count(ctx, "sh.tangled.repo.countIssues", repoDid, { state: "open" }).catch(() => null)
			: Promise.resolve(null),
		pulls: repoDid
			? count(ctx, "sh.tangled.repo.countPulls", repoDid, { status: "open" }).catch(() => null)
			: Promise.resolve(null),
		forks: repoDid
			? count(ctx, "sh.tangled.repo.countForks", repoDid).catch(() => null)
			: Promise.resolve(null),
		viewerStarRkey:
			viewerDid && repoDid
				? getStarRkey(ctx, viewerDid, repoDid).catch(() => null)
				: Promise.resolve(null),
		source: resolveSource(ctx, record.source)
	});

	const repo: RepoInfo = {
		uri: view.uri,
		rkey: rkeyFromUri(view.uri),
		name: repoNameOf(view),
		ownerDid: doc.did,
		ownerHandle: doc.handle,
		ownerAvatar: doc.avatar,
		repoDid,
		knot: record.knot,
		spindle: record.spindle,
		description: record.description,
		website: record.website,
		topics: record.topics,
		source: stats.source ?? undefined,
		defaultBranch: stats.defaultBranch ?? FALLBACK_BRANCH
	};

	const counts: RepoCounts = {
		stars: stats.stars?.count ?? 0,
		issues: stats.issues?.count ?? 0,
		pulls: stats.pulls?.count ?? 0,
		forks: stats.forks?.count ?? 0
	};

	return { repo, counts, viewerStarRkey: stats.viewerStarRkey };
};
