import { createHash } from "node:crypto";
import { access, mkdir, rm } from "node:fs/promises";
import path from "node:path";
import { simpleGit } from "simple-git";
export async function cloneTarget(input) {
    throwIfAborted(input.signal);
    const destExisted = await exists(input.dest);
    try {
        const cacheDir = input.cacheDir;
        if (cacheDir == null) {
            await cloneDirect(input);
        }
        else {
            await cloneFromCache({ ...input, cacheDir });
        }
        return {
            resolvedSha: (await git(input.dest, input.signal).revparse(["HEAD"])).trim()
        };
    }
    catch (error) {
        if (!destExisted) {
            await rm(input.dest, { recursive: true, force: true });
        }
        throw error;
    }
}
async function cloneDirect(input) {
    await git(undefined, input.signal).clone(input.repo, input.dest, [
        "--depth",
        "1",
        "--branch",
        input.ref
    ]);
}
async function cloneFromCache(input) {
    await mkdir(input.cacheDir, { recursive: true });
    const cachedRepo = path.join(input.cacheDir, `${hashRepo(input.repo)}.git`);
    if (await exists(cachedRepo)) {
        await git(cachedRepo, input.signal).raw([
            "fetch",
            "origin",
            "--prune",
            "+refs/heads/*:refs/heads/*",
            "+refs/tags/*:refs/tags/*"
        ]);
    }
    else {
        await git(undefined, input.signal).clone(input.repo, cachedRepo, ["--bare"]);
    }
    await git(cachedRepo, input.signal).raw(["worktree", "prune"]);
    await git(cachedRepo, input.signal).raw(["worktree", "add", "--detach", input.dest, input.ref]);
}
function git(baseDir, signal) {
    return simpleGit(baseDir ?? process.cwd(), { abort: signal });
}
function hashRepo(repo) {
    return createHash("sha256").update(repo).digest("hex");
}
async function exists(target) {
    try {
        await access(target);
        return true;
    }
    catch {
        return false;
    }
}
function throwIfAborted(signal) {
    if (!signal?.aborted) {
        return;
    }
    throw signal.reason instanceof Error ? signal.reason : new Error("cloneTarget aborted.");
}
