import { createBobbinClient, type BobbinContext } from "./client";
import {
	branches as knotMirrorBranches,
	blob as knotMirrorBlob,
	blobRawUrl as knotMirrorBlobRawUrl,
	createKnotMirrorClient,
	languages as knotMirrorLanguages,
	log as knotMirrorLog,
	getTag as knotMirrorGetTag,
	tags as knotMirrorTags,
	tree as knotMirrorTree
} from "./knotmirror";
import {
	blob as knotBlob,
	blobRawUrl as knotBlobRawUrl,
	getDefaultBranch,
	languages as knotLanguages,
	tree as knotTree
} from "./knot";
import { branchesFor, logFor, tagFor, tagsFor } from "./repo";
import type { Did, ResourceUri } from "@atcute/lexicons";

export interface GitServiceConfig {
	bobbinUrl: string;
	knotMirrorUrl: string;
}

export interface GitTarget {
	ctx: BobbinContext;
	repo: Did | ResourceUri;
	via: "mirror" | "bobbin";
}

export const gitTarget = (
	config: GitServiceConfig,
	repo: { uri: string; repoDid?: string },
	fetch: typeof globalThis.fetch
): GitTarget =>
	config.knotMirrorUrl && repo.repoDid
		? {
				ctx: createKnotMirrorClient(config.knotMirrorUrl, fetch),
				repo: repo.repoDid as Did,
				via: "mirror"
			}
		: {
				ctx: createBobbinClient({ serviceUrl: config.bobbinUrl, fetch }),
				repo: repo.uri as ResourceUri,
				via: "bobbin"
			};

// the mirror has no getDefaultBranch, the default falls out of the branch
// list's is_default flag instead
export const resolveDefaultBranch = (
	target: GitTarget,
	init?: { signal?: AbortSignal }
): Promise<string | null> =>
	target.via === "mirror"
		? (async () => {
				for (let offset = 0; ; offset += 100) {
					const response = await knotMirrorBranches(
						target.ctx,
						{ repo: target.repo, limit: 100, cursor: offset ? `${offset}` : undefined },
						init
					);
					const list = response.branches ?? [];
					const found = list.find((branch) => branch.is_default)?.reference.name;
					if (found) return found;
					if (list.length < 100) return null;
				}
			})().catch(() => null)
		: getDefaultBranch(target.ctx, { repo: target.repo })
				.then((branch) => branch.name)
				.catch(() => null);

export const tree = (target: GitTarget, params: { ref: string; path?: string }) =>
	target.via === "mirror"
		? knotMirrorTree(target.ctx, { repo: target.repo, ...params })
		: knotTree(target.ctx, { repo: target.repo, ...params });

export const log = (target: GitTarget, params: { ref: string; limit: number; cursor?: string }) =>
	target.via === "mirror"
		? knotMirrorLog(target.ctx, { repo: target.repo, ...params })
		: logFor(target.ctx, target.repo, params.ref, params.limit, params.cursor);

export const branches = (target: GitTarget, limit: number, cursor?: string) =>
	target.via === "mirror"
		? knotMirrorBranches(target.ctx, { repo: target.repo, limit, cursor })
		: branchesFor(target.ctx, target.repo, limit, cursor);

export const tags = (target: GitTarget, limit: number, cursor?: string) =>
	target.via === "mirror"
		? knotMirrorTags(target.ctx, { repo: target.repo, limit, cursor })
		: tagsFor(target.ctx, target.repo, limit, cursor);

export const tag = (target: GitTarget, name: string) =>
	target.via === "mirror"
		? knotMirrorGetTag(target.ctx, { repo: target.repo, tag: name })
		: tagFor(target.ctx, target.repo, name);

export const languages = (
	target: GitTarget,
	ref: string
): Promise<{ languages?: { name: string; size: number }[] }> =>
	target.via === "mirror"
		? knotMirrorLanguages(target.ctx, { repo: target.repo, ref })
		: knotLanguages(target.ctx, { repo: target.repo, ref });

// only the mirror needs the inline cap, the knot enforces its own and 413s
// past it
export const blob = (target: GitTarget, params: { ref: string; path: string }, maxBytes: number) =>
	target.via === "mirror"
		? knotMirrorBlob(target.ctx, { repo: target.repo, ...params }, maxBytes)
		: knotBlob(target.ctx, { repo: target.repo as ResourceUri, ...params });

export const blobRawUrl = (target: GitTarget, params: { ref: string; path: string }): string =>
	target.via === "mirror"
		? knotMirrorBlobRawUrl(target.ctx.serviceUrl, { repo: target.repo, ...params })
		: knotBlobRawUrl(target.ctx.serviceUrl, { repo: target.repo, ...params });
