import { createBobbinClient } from "$lib/api/client";
import { languages as knotLanguages, tree as knotTree } from "$lib/api/knot";
import { parallel } from "$lib/api/load";
import {
	branchesFor,
	logFor,
	sortTreeEntries,
	tagsByCommitHash,
	tagsFor,
	toBranchSummary,
	toCommitSummary,
	toTagSummary,
	toTreeEntrySummary
} from "$lib/api/repo";
import { renderDocument } from "$lib/markup";
import type { LanguageSlice } from "$lib/components/repo/types";
import type { PageLoad } from "./$types";

const COMMIT_LIMIT = 10;
const BRANCH_LIMIT = 5;
const TAG_LIMIT = 5;
// knots cap ref listings at 100, so a total is really an "at least"
const REF_LIMIT = 100;

const orNull = <T>(promise: Promise<T>): Promise<T | null> => promise.catch(() => null);

const toLanguageSlices = (languages: { name: string; size: number }[]): LanguageSlice[] => {
	const sized = languages.filter((language) => language.size > 0);
	const total = sized.reduce((sum, language) => sum + language.size, 0);
	if (total === 0) return [];

	const slices = sized.map((language) => {
		const share = (language.size / total) * 100;
		return { name: language.name, share, percentage: Math.floor(share) };
	});

	const short = 100 - slices.reduce((sum, slice) => sum + slice.percentage, 0);
	[...slices]
		.sort((a, b) => (b.share % 1) - (a.share % 1) || b.share - a.share)
		.slice(0, Math.max(0, short))
		.forEach((slice) => {
			slice.percentage += 1;
		});

	return slices.sort((a, b) => b.share - a.share);
};

export const load: PageLoad = async (event) => {
	const parent = await event.parent();
	const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });
	const repo = parent.repo.uri;
	const ref = parent.repo.defaultBranch;

	// every list falls back on its own so a partial page still renders
	const results = await parallel({
		tree: orNull(knotTree(ctx, { repo, ref })),
		log: orNull(logFor(ctx, repo, ref, COMMIT_LIMIT)),
		branches: orNull(branchesFor(ctx, repo, REF_LIMIT)),
		tags: orNull(tagsFor(ctx, repo, REF_LIMIT)),
		languages: orNull(knotLanguages(ctx, { repo, ref }))
	});

	const branches = (results.branches?.branches ?? []).map(toBranchSummary);
	const tags = (results.tags?.tags ?? []).map(toTagSummary);
	const commits = (results.log?.commits ?? []).map(toCommitSummary);
	const files = sortTreeEntries((results.tree?.files ?? []).map(toTreeEntrySummary));

	const languages = toLanguageSlices(results.languages?.languages ?? []);

	const readme = results.tree?.readme ?? null;
	const readmeHtml = readme
		? await renderDocument(readme.filename, readme.contents, {
				repo: `${parent.repo.ownerHandle}/${parent.repo.name}`,
				ref,
				host: event.url.host
			})
		: null;

	// nothing answered, so the knot is down or doesn't know this repo
	const knotUnreachable =
		results.tree === null && results.log === null && results.branches === null;
	// the knot answered but there is nothing there, so it was never pushed to
	const isEmpty = !knotUnreachable && files.length === 0 && branches.length === 0;

	return {
		ref,
		isEmpty,
		knotUnreachable,
		files,
		readme,
		readmeHtml,
		commits,
		tagsByCommit: tagsByCommitHash(commits, tags),
		totalCommits: results.log?.total ?? commits.length,
		branches: branches.slice(0, BRANCH_LIMIT),
		totalBranches: branches.length,
		tags: tags.slice(0, TAG_LIMIT),
		totalTags: tags.length,
		// the switcher needs every ref, not just the visible slice
		refs: {
			branches: branches.map((branch) => branch.name),
			tags: tags.map((tag) => tag.name),
			capped: branches.length >= REF_LIMIT || tags.length >= REF_LIMIT
		},
		languages
	};
};
