export interface MarkupContext {
	/** `owner/repo` */
	repo: string;
	ref: string;
	dir?: string;
	host?: string;
	camo?: boolean;
}

const ABSOLUTE = /^[a-z][a-z0-9+.-]*:|^\/\//i;

export const isAbsoluteUrl = (url: string): boolean => ABSOLUTE.test(url);

export const isRepoRelative = (url: string): boolean =>
	url !== "" && !isAbsoluteUrl(url) && !url.startsWith("#");

// `.` and `..` have to collapse here instead of reaching the url
const normalize = (path: string): string => {
	const parts: string[] = [];
	for (const part of path.split("/")) {
		if (part === "" || part === ".") continue;
		if (part === "..") parts.pop();
		else parts.push(part);
	}
	return parts.join("/");
};

const withinRepo = (path: string, ctx: MarkupContext): string =>
	normalize(path.startsWith("/") ? path : `${ctx.dir ?? ""}/${path}`);

const splitSuffix = (url: string): [string, string] => {
	const at = url.search(/[?#]/);
	return at === -1 ? [url, ""] : [url.slice(0, at), url.slice(at)];
};

// markdown-it percent-encodes destinations before we ever see them, so only the
// ref still needs encoding
const repoUrl = (kind: string, url: string, ctx: MarkupContext): string => {
	const [path, suffix] = splitSuffix(url);
	if (path === "") return url;
	const ref = encodeURIComponent(ctx.ref);
	return `/${ctx.repo}/${kind}/${ref}/${withinRepo(path, ctx)}${suffix}`;
};

export const treeUrl = (url: string, ctx: MarkupContext): string => repoUrl("tree", url, ctx);

export const rawUrl = (url: string, ctx: MarkupContext): string => repoUrl("raw", url, ctx);

const hostOf = (url: string): string | null => {
	try {
		// the base only matters for protocol relative urls, which have a host anyway
		return new URL(url, "https://invalid.").host;
	} catch {
		return null;
	}
};

// camo wants the target hex encoded
const toHex = (value: string): string =>
	Array.from(new TextEncoder().encode(value), (byte) => byte.toString(16).padStart(2, "0")).join(
		""
	);

export const mediaUrl = (url: string, ctx: MarkupContext): string => {
	if (isRepoRelative(url)) return rawUrl(url, ctx);
	if (!ctx.camo || !isAbsoluteUrl(url) || hostOf(url) === ctx.host) return url;
	return `/camo/${toHex(url)}`;
};

export const mediaSrcset = (srcset: string, ctx: MarkupContext): string =>
	srcset
		.split(",")
		.map((candidate) => {
			const [url, ...descriptors] = candidate.trim().split(/\s+/);
			return [mediaUrl(url, ctx), ...descriptors].join(" ");
		})
		.join(", ");
