/**
 * Branch Manager
 *
 * Handles git branch operations for autofix PRs.
 * Uses cross-spawn with array args per CLAUDE.md guidelines.
 *
 * @module autofix/branch-manager
 */
import spawn from "cross-spawn";
import { logger } from "../logger.js";
/**
 * Execute a git command and return the result
 */
export async function git(args, options = { cwd: process.cwd() }) {
    return new Promise((resolve) => {
        const child = spawn("git", args, {
            cwd: options.cwd,
            timeout: options.timeout ?? 30000,
        });
        let stdout = "";
        let stderr = "";
        child.stdout?.on("data", (data) => {
            stdout += data.toString();
        });
        child.stderr?.on("data", (data) => {
            stderr += data.toString();
        });
        child.on("close", (exitCode) => {
            const success = exitCode === 0;
            if (!success) {
                logger.debug("git.command_failed", {
                    args: args.join(" "),
                    exitCode,
                    stderr: stderr.slice(0, 500),
                });
            }
            resolve({
                success,
                stdout: stdout.trim(),
                stderr: stderr.trim(),
                exitCode: exitCode ?? undefined,
            });
        });
        child.on("error", (error) => {
            logger.error("git.spawn_error", {
                args: args.join(" "),
                error: String(error),
            });
            resolve({
                success: false,
                stdout,
                stderr,
                error: String(error),
            });
        });
    });
}
/**
 * Check if git is available
 */
export async function isGitAvailable(cwd) {
    const result = await git(["--version"], { cwd, timeout: 5000 });
    return result.success;
}
/**
 * Check if directory is a git repository
 */
export async function isGitRepo(cwd) {
    const result = await git(["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 5000 });
    return result.success && result.stdout === "true";
}
/**
 * Get the current branch name
 */
export async function getCurrentBranch(cwd) {
    const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
    return result.success ? result.stdout : undefined;
}
/**
 * Get the default branch name (main or master)
 */
export async function getDefaultBranch(cwd) {
    // Try to get from remote HEAD reference
    const remoteResult = await git(["symbolic-ref", "refs/remotes/origin/HEAD", "--short"], { cwd });
    if (remoteResult.success && remoteResult.stdout) {
        // Returns something like "origin/main", extract just "main"
        const parts = remoteResult.stdout.split("/");
        return parts[parts.length - 1];
    }
    // Fallback: check if main exists
    const mainResult = await git(["show-ref", "--verify", "--quiet", "refs/heads/main"], { cwd });
    if (mainResult.success) {
        return "main";
    }
    // Final fallback
    return "master";
}
/**
 * Check if a branch exists locally
 */
export async function branchExists(cwd, branchName) {
    const result = await git(["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`], { cwd });
    return result.success;
}
/**
 * Check if a branch exists on remote
 */
export async function remoteBranchExists(cwd, branchName, remote = "origin") {
    const result = await git(["ls-remote", "--heads", remote, branchName], { cwd, timeout: 30000 });
    return result.success && !!result.stdout?.includes(branchName);
}
/**
 * Create a new branch from a base
 */
export async function createBranch(cwd, branchName, baseBranch) {
    logger.info("git.create_branch", { branchName, baseBranch });
    // First ensure we have latest base branch
    await git(["fetch", "origin", baseBranch], { cwd });
    // Create branch from remote base
    return git(["checkout", "-b", branchName, `origin/${baseBranch}`], { cwd });
}
/**
 * Switch to an existing branch
 */
export async function checkoutBranch(cwd, branchName) {
    logger.debug("git.checkout", { branchName });
    return git(["checkout", branchName], { cwd });
}
/**
 * Delete a local branch
 */
export async function deleteBranch(cwd, branchName, force = false) {
    logger.info("git.delete_branch", { branchName, force });
    const flag = force ? "-D" : "-d";
    return git(["branch", flag, branchName], { cwd });
}
/**
 * Stage specific files
 */
export async function stageFiles(cwd, files) {
    if (files.length === 0) {
        return { success: true, stdout: "", stderr: "" };
    }
    logger.debug("git.stage_files", { count: files.length });
    return git(["add", ...files], { cwd });
}
/**
 * Get list of modified files (staged and unstaged)
 */
export async function getModifiedFiles(cwd) {
    const result = await git(["status", "--porcelain"], { cwd });
    if (!result.success) {
        return [];
    }
    return (result.stdout ?? "")
        .split("\n")
        .filter((line) => line.length > 0)
        .map((line) => line.slice(3).trim());
}
/**
 * Get list of staged files
 */
export async function getStagedFiles(cwd) {
    const result = await git(["diff", "--cached", "--name-only"], { cwd });
    if (!result.success) {
        return [];
    }
    return (result.stdout ?? "").split("\n").filter((line) => line.length > 0);
}
/**
 * Check if working tree is clean
 */
export async function isWorkingTreeClean(cwd) {
    const result = await git(["status", "--porcelain"], { cwd });
    return result.success && result.stdout === "";
}
/**
 * Push branch to remote
 */
export async function pushBranch(cwd, branchName, remote = "origin", setUpstream = true) {
    logger.info("git.push", { branchName, remote, setUpstream });
    const args = ["push"];
    if (setUpstream) {
        args.push("-u");
    }
    args.push(remote, branchName);
    return git(args, { cwd, timeout: 60000 });
}
/**
 * Stash current changes
 */
export async function stash(cwd, message) {
    const args = ["stash", "push"];
    if (message) {
        args.push("-m", message);
    }
    return git(args, { cwd });
}
/**
 * Pop stashed changes
 */
export async function stashPop(cwd) {
    return git(["stash", "pop"], { cwd });
}
/**
 * Get branch info including tracking
 */
export async function getBranchInfo(cwd, branchName) {
    const name = branchName ?? (await getCurrentBranch(cwd));
    if (!name) {
        return undefined;
    }
    const current = (await getCurrentBranch(cwd)) === name;
    // Get tracking info
    const trackingResult = await git(["rev-parse", "--abbrev-ref", `${name}@{upstream}`], { cwd });
    const tracking = trackingResult.success ? trackingResult.stdout : undefined;
    // Get ahead/behind counts
    let ahead = 0;
    let behind = 0;
    if (tracking) {
        const countResult = await git(["rev-list", "--left-right", "--count", `${name}...${tracking}`], { cwd });
        if (countResult.success && countResult.stdout) {
            const [a, b] = countResult.stdout.split("\t").map(Number);
            ahead = a || 0;
            behind = b || 0;
        }
    }
    return {
        name,
        current,
        remote: tracking ? tracking.split("/")[0] : undefined,
        tracking,
        ahead,
        behind,
    };
}
/**
 * Reset branch to a specific commit
 */
export async function resetToCommit(cwd, commitish, mode = "mixed") {
    logger.warn("git.reset", { commitish, mode });
    return git(["reset", `--${mode}`, commitish], { cwd });
}
/**
 * Get the remote URL for origin
 */
export async function getRemoteUrl(cwd, remote = "origin") {
    const result = await git(["remote", "get-url", remote], { cwd });
    return result.success ? result.stdout : undefined;
}
/**
 * Parse GitHub owner/repo from remote URL
 */
export function parseGitHubRemote(url) {
    // SSH format: git@github.com:owner/repo.git
    const sshMatch = url.match(/git@github\.com:([^/]+)\/(.+?)(\.git)?$/);
    if (sshMatch) {
        return { owner: sshMatch[1], repo: sshMatch[2] };
    }
    // HTTPS format: https://github.com/owner/repo.git
    const httpsMatch = url.match(/https:\/\/github\.com\/([^/]+)\/(.+?)(\.git)?$/);
    if (httpsMatch) {
        return { owner: httpsMatch[1], repo: httpsMatch[2] };
    }
    return undefined;
}
/**
 * Generate a unique branch name
 */
export function generateBranchName(prefix, identifier) {
    const timestamp = Date.now().toString(36);
    const sanitized = identifier
        .toLowerCase()
        .replace(/[^a-z0-9-]/g, "-")
        .replace(/-+/g, "-")
        .slice(0, 30);
    return `${prefix}/${sanitized}-${timestamp}`;
}
/**
 * Ensure we're on a clean working tree before operations
 */
export async function ensureCleanWorkingTree(cwd, stashIfDirty = true) {
    const clean = await isWorkingTreeClean(cwd);
    if (clean) {
        return { clean: true, stashed: false };
    }
    if (stashIfDirty) {
        const result = await stash(cwd, "vaspera-autofix: temporary stash");
        if (result.success) {
            logger.info("git.stashed_changes", { message: "Working tree had changes, stashed" });
            return { clean: true, stashed: true };
        }
    }
    return { clean: false, stashed: false };
}
/**
 * Restore original branch state after operations
 */
export async function restoreOriginalState(cwd, originalBranch, wasStashed) {
    // Switch back to original branch
    await checkoutBranch(cwd, originalBranch);
    // Pop stash if we stashed changes
    if (wasStashed) {
        await stashPop(cwd);
        logger.info("git.restored_stash", { branch: originalBranch });
    }
}
//# sourceMappingURL=branch-manager.js.map