---
// fetch all commits for just this page's path
type Props = {
	path: string;
};
const { path } = Astro.props as Props;

const resolvedPath = `docs/${path}`;
const commitsURL = `https://github.com/manzt/anywidget/commits/main/${resolvedPath}`;

async function ghFetch(endpoint: string) {
	let url = new URL(endpoint, "https://api.github.com");

	const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? "hello";

	if (!token) {
		throw new Error(
			'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'
		);
	}

	const auth = `Basic ${Buffer.from(token, "binary").toString("base64")}`;

	const res = await fetch(url, {
		method: "GET",
		headers: {
			Authorization: auth,
			"User-Agent": "astro-docs/1.0",
		},
	});

	const data = await res.json();

	if (!res.ok) {
		throw new Error(
			`GitHub request for ${url.pathname} failed. Reason: ${res.statusText} Message: ${data.message}`
		);
	}

	return data;
}

type User = {
	id: string;
	login: string;
};

type Commit = {
	commit: {
		author: {
			name: string;
			email: string;
		};
		message: string;
	};
	author: User;
};

async function searchUsers(email: string): Promise<User[]> {
	return ghFetch(`/search/users?q=${email}`)
		.then((data) => data.items as User[])
		.catch((e) => {
			console.warn(`[error]  /src/components/AvatarList.astro 
		${(e as any)?.message ?? e}`);
			return [];
		});
}

async function getCommits({
	user,
	repo,
	path,
}: {
	user: string;
	repo: string;
	path: string;
}): Promise<Commit[]> {
	return ghFetch(`/repos/${user}/${repo}/commits?path=${path}`)
		.then((data) => data as Commit[])
		.catch((e) => {
			console.warn(`[error]  /src/components/AvatarList.astro 
    ${(e as any)?.message ?? e}`);
			return [] as Commit[];
		});
}

async function removeDupsAndFindCoAuthors(arr: Commit[]) {
	let map = new Map<string, User>();

	// Collect co-author emails while iterating through commits.
	// If they haven't authored a commit on this file,
	// then we will need to look them up by email.
	let coAuthorEmails = new Set<string>();

	for (let item of arr) {
		let author = { ...item.author, email: item.commit.author.email };

		// identify our authors by email
		map.set(author.email, { login: author.login, id: author.id });

		// parse commit message for co-authors
		for (let line of item.commit.message.split("\n")) {
			let maybeEmail = line.match(/^Co-authored-by:.*\u003c(.*)\u003e/)?.[1];
			if (maybeEmail) coAuthorEmails.add(maybeEmail);
		}
	}

	for (let email of coAuthorEmails) {
		let users = await searchUsers(email);
		users.forEach((user) => map.set(email, user));
	}

	// finally dedupe by user id (incase separate emails for same user)
	let deduped = new Map<string, User>();
	for (let [_, user] of map) {
		deduped.set(user.id, user);
	}

	return [...deduped.values()];
}

const data = await getCommits({
	repo: "anywidget",
	user: "manzt",
	path: resolvedPath,
});
const unique = await removeDupsAndFindCoAuthors(data);
const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors
const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors
---

<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
<div class="contributors">
	<ul
		class="avatar-list"
		style={`--avatar-count: ${recentContributors.length}`}
	>
		{
			recentContributors.map((item) => (
				<li>
					<a href={`https://github.com/${item.login}`}>
						<img
							alt={`Contributor ${item.login}`}
							title={`Contributor ${item.login}`}
							width="64"
							height="64"
							src={`https://avatars.githubusercontent.com/u/${item.id}`}
						/>
					</a>
				</li>
			))
		}
	</ul>
	{
		additionalContributors > 0 && (
			<span>
				<a
					href={commitsURL}
				>{`and ${additionalContributors} additional contributor${
					additionalContributors > 1 ? "s" : ""
				}.`}</a>
			</span>
		)
	}
	{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
</div>

<style>
	.avatar-list {
		--avatar-size: 2.5rem;
		--avatar-count: 3;

		display: grid;
		list-style: none;
		/* Default to displaying most of the avatar to
  enable easier access on touch devices, ensuring
  the WCAG touch target size is met or exceeded */
		grid-template-columns: repeat(
			var(--avatar-count),
			max(44px, calc(var(--avatar-size) / 1.15))
		);
		/* `padding` matches added visual dimensions of
  the `box-shadow` to help create a more accurate
  computed component size */
		padding: 0.08em;
		font-size: var(--avatar-size);
	}

	@media (any-hover: hover) and (any-pointer: fine) {
		.avatar-list {
			/* We create 1 extra cell to enable the computed 
    width to match the final visual width */
			grid-template-columns: repeat(
				calc(var(--avatar-count) + 1),
				calc(var(--avatar-size) / 1.75)
			);
		}
	}

	.avatar-list li {
		width: var(--avatar-size);
		height: var(--avatar-size);
	}

	.avatar-list li:hover ~ li a,
	.avatar-list li:focus-within ~ li a {
		transform: translateX(33%);
	}

	.avatar-list img,
	.avatar-list a {
		display: block;
		border-radius: 50%;
	}

	.avatar-list a {
		transition: transform 180ms ease-in-out;
	}

	.avatar-list img {
		width: 100%;
		height: 100%;
		object-fit: cover;
		background-color: #fff;
		box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
	}

	.avatar-list a:focus {
		outline: 2px solid transparent;
		/* Double-layer trick to work for dark and light backgrounds */
		box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
	}

	.contributors {
		display: flex;
		align-items: center;
	}

	.contributors > * + * {
		margin-left: 0.75rem;
	}
</style>
