import { simpleGit } from "simple-git";
import { logger } from "./logger.js";
/**
 * Extract every file path mentioned in a unified-diff `diff --git` header.
 * Always uses the `b/` form so renames return the new path.
 */
export function parseChangedFilesFromDiff(rawDiff) {
    const out = [];
    const re = /^diff --git a\/\S+ b\/(\S+)/gm;
    let m;
    while ((m = re.exec(rawDiff)) !== null) {
        out.push(m[1]);
    }
    return out;
}
/** Parse diff headers to find newly created and deleted files. */
function parseNewAndDeletedFiles(rawDiff) {
    const newFiles = [];
    const deletedFiles = [];
    const lines = rawDiff.split("\n");
    let currentFile = "";
    for (const line of lines) {
        const fileMatch = line.match(/^diff --git a\/.+ b\/(.+)$/);
        if (fileMatch) {
            currentFile = fileMatch[1];
            continue;
        }
        if (line === "--- /dev/null" && currentFile) {
            newFiles.push(currentFile);
        }
        else if (line === "+++ /dev/null" && currentFile) {
            deletedFiles.push(currentFile);
        }
    }
    return { newFiles, deletedFiles };
}
async function tryDiff(git, ref) {
    try {
        const changedFilesRaw = await git.diff([`${ref}...HEAD`, "--name-only"]);
        const changedFiles = changedFilesRaw
            .split("\n")
            .map((f) => f.trim())
            .filter((f) => f.length > 0);
        const diffStat = await git.diff([`${ref}...HEAD`, "--stat"]);
        const fullDiff = await git.diff([`${ref}...HEAD`]);
        const { newFiles, deletedFiles } = parseNewAndDeletedFiles(fullDiff);
        return { changedFiles, diffContent: fullDiff, diffStat, newFiles, deletedFiles };
    }
    catch (err) {
        logger.debug(`tryDiff against ${ref} failed`, { error: err instanceof Error ? err.message : String(err) });
        return undefined;
    }
}
export async function computeBranchDiff(repositoryPath, providedBaseBranch) {
    const git = simpleGit(repositoryPath);
    const isRepo = await git.checkIsRepo();
    if (!isRepo) {
        throw new Error(`"${repositoryPath}" is not a Git repository.`);
    }
    const branchInfo = await git.branch();
    const currentBranch = branchInfo.current || "HEAD";
    // Build candidate base refs in priority order:
    // 1. Provided branch name (most specific)
    // 2. Remote default branch auto-detected from available remotes
    // 3. FETCH_HEAD — present in base_commit repos (git fetch --depth 1 origin <sha>)
    //    where no remote tracking branch is created
    // 4. HEAD~1 — last resort for repos with no remote at all
    const candidates = [];
    if (providedBaseBranch) {
        candidates.push(`origin/${providedBaseBranch}`);
    }
    // Auto-detect remote default branch
    try {
        const remoteBranches = await git.branch(["-r"]);
        if (remoteBranches.all.some((b) => b.endsWith("/main"))) {
            if (!candidates.includes("origin/main"))
                candidates.push("origin/main");
        }
        else if (remoteBranches.all.some((b) => b.endsWith("/master"))) {
            if (!candidates.includes("origin/master"))
                candidates.push("origin/master");
        }
    }
    catch {
        logger.debug("Could not list remote branches");
    }
    // FETCH_HEAD covers base_commit repos (appsmith, etc.)
    candidates.push("FETCH_HEAD");
    // Final fallback
    candidates.push("HEAD~1");
    for (const ref of candidates) {
        const result = await tryDiff(git, ref);
        if (result) {
            logger.info("Branch diff computed", {
                currentBranch,
                baseBranch: ref,
                changedFiles: result.changedFiles.length,
            });
            return { currentBranch, baseBranch: ref, ...result };
        }
        logger.debug(`Diff against ${ref} failed, trying next candidate`);
    }
    throw new Error(`Could not compute branch diff: all candidates failed (${candidates.join(", ")})`);
}
