import { simpleGit } from "simple-git";
import { getLogger, getErrorMeta } from "../util/logger.js";
import { GitAuthError, GitConflictError, GitMergeError, GitNonFastForwardError, GitPermissionError, GitRemoteError, } from "./errors.js";
export { GitAuthError, GitConflictError, GitMergeError, GitNonFastForwardError, GitPermissionError, GitRemoteError, };
export function createGitService(cwd) {
    const git = simpleGit(cwd);
    const logger = getLogger();
    async function getConflictedFiles() {
        const result = await git.diff(["--name-only", "--diff-filter=U"]);
        if (!result.trim())
            return [];
        return result.trimEnd().split("\n");
    }
    return Object.assign(git, {
        workDir: cwd,
        configure: async (options) => {
            await git.addConfig("user.name", options?.userName || "Superblocks", false, "global");
            await git.addConfig("user.email", options?.userEmail || "noreply@superblocks.com", false, "global");
            await git.addConfig("init.defaultBranch", "main", false, "global");
            if (options?.credentials) {
                const helper = `!f() { echo username=${options.credentials.username}; ` +
                    `echo password=${options.credentials.token}; }; f`;
                await git.addConfig("credential.helper", helper, false, "global");
            }
        },
        mergeRef: async (ref) => {
            try {
                const summary = await git.merge([ref, "--no-edit"]);
                const alreadyUpToDate = summary.result === "already up-to-date" ||
                    summary.result === "up to date";
                return {
                    alreadyUpToDate,
                };
            }
            catch (error) {
                const classified = classifyGitError(error);
                if (classified instanceof GitAuthError ||
                    classified instanceof GitRemoteError) {
                    logger.error(`[git] merge failed: auth or remote error while merging ${ref}`, getErrorMeta(classified));
                    throw classified;
                }
                const msg = extractMessage(error);
                if (msg.includes("CONFLICT") || msg.includes("Merge conflict")) {
                    let files = [];
                    try {
                        files = await getConflictedFiles();
                    }
                    catch {
                        // Best effort
                    }
                    logger.warn("[git] merge conflict detected", {
                        gitCategory: "pull",
                        gitOperation: "merge",
                        gitOutcome: "conflict",
                        ref,
                        workDir: cwd,
                        conflictedFileCount: files.length,
                        conflictedFiles: files,
                    });
                    throw new GitConflictError(files, msg);
                }
                logger.error(`[git] merge failed: unexpected error while merging ${ref}`, getErrorMeta(error));
                throw new GitMergeError(msg);
            }
        },
        stashPop: async () => {
            try {
                await git.stash(["pop"]);
                return { clean: true };
            }
            catch (error) {
                const classified = classifyGitError(error);
                if (classified instanceof GitAuthError ||
                    classified instanceof GitRemoteError) {
                    logger.error("[git] stash pop failed: auth or remote error", getErrorMeta(classified));
                    throw classified;
                }
                const msg = extractMessage(error);
                if (msg.includes("CONFLICT") || msg.includes("Merge conflict")) {
                    let files = [];
                    try {
                        files = await getConflictedFiles();
                    }
                    catch {
                        // Best effort
                    }
                    logger.warn("[git] stash pop conflict detected", {
                        gitCategory: "pull",
                        gitOperation: "stash-pop",
                        gitOutcome: "conflict",
                        workDir: cwd,
                        conflictedFileCount: files.length,
                        conflictedFiles: files,
                    });
                    throw new GitConflictError(files, msg);
                }
                logger.error("[git] stash pop failed: unexpected error", getErrorMeta(error));
                throw error instanceof Error ? error : new Error(String(error));
            }
        },
        revList: async (left, right) => {
            const result = await git.raw([
                "rev-list",
                "--left-right",
                "--count",
                `${left}...${right}`,
            ]);
            const parts = result.trim().split("\t");
            return { ahead: Number(parts[0]), behind: Number(parts[1]) };
        },
        checkoutOrCreate: async (branch, startPoint) => {
            try {
                await git.checkout(branch);
            }
            catch {
                const args = ["-B", branch];
                if (startPoint)
                    args.push(startPoint);
                await git.checkout(args);
            }
        },
        getConflictedFiles,
        getDefaultBranch: async () => {
            const result = await git.raw(["ls-remote", "--symref", "origin", "HEAD"]);
            const match = result.match(/^ref: refs\/heads\/(.+)\t/);
            if (!match?.[1]) {
                throw new Error("Failed to resolve default branch from remote HEAD");
            }
            const discoveredDefaultBranch = match[1];
            await git
                .raw([
                "config",
                "--local",
                "superblocks.defaultBranch",
                discoveredDefaultBranch,
            ])
                .catch(() => {
                // Best effort: status flow can still proceed without local persistence.
            });
            return discoveredDefaultBranch;
        },
    });
}
export function classifyGitError(error) {
    const message = extractMessage(error);
    const lower = message.toLowerCase();
    if (lower.includes("authentication failed") ||
        lower.includes("could not read username") ||
        lower.includes("invalid credentials")) {
        getLogger().warn("[git] auth failure classified", {
            gitCategory: "setup",
            gitOperation: "classify-error",
            gitOutcome: "failed",
            gitErrorKind: "auth",
            message,
        });
        return new GitAuthError(message);
    }
    if (lower.includes("denied to") ||
        lower.includes("permission to") ||
        lower.includes("the requested url returned error: 403")) {
        getLogger().warn("[git] permission error classified", {
            gitCategory: "commit",
            gitOperation: "classify-error",
            gitOutcome: "failed",
            gitErrorKind: "permission",
            message,
        });
        return new GitPermissionError(message);
    }
    if (lower.includes("could not resolve host") ||
        (lower.includes("repository") && lower.includes("not found")) ||
        lower.includes("fatal: unable to access")) {
        getLogger().warn("[git] remote error classified", {
            gitCategory: "setup",
            gitOperation: "classify-error",
            gitOutcome: "failed",
            gitErrorKind: "remote",
            message,
        });
        return new GitRemoteError(message);
    }
    if (lower.includes("non-fast-forward") ||
        lower.includes("updates were rejected") ||
        lower.includes("fetch first") ||
        lower.includes("cannot be resolved to branch")) {
        getLogger().warn("[git] non-fast-forward classified", {
            gitCategory: "commit",
            gitOperation: "classify-error",
            gitOutcome: "failed",
            gitErrorKind: "non_fast_forward",
            message,
        });
        return new GitNonFastForwardError(message);
    }
    return error instanceof Error ? error : new Error(String(error));
}
function extractMessage(error) {
    if (error !== null && typeof error === "object" && "message" in error) {
        return String(error.message);
    }
    return String(error);
}
//# sourceMappingURL=index.js.map