"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isProtectedBranch = isProtectedBranch;
exports.getCurrentBranch = getCurrentBranch;
exports.remoteBranchExists = remoteBranchExists;
exports.stashChanges = stashChanges;
exports.fetchAndCheckout = fetchAndCheckout;
exports.fetchAndCheckoutBranch = fetchAndCheckoutBranch;
exports.createBranch = createBranch;
exports.createBranchFromRemote = createBranchFromRemote;
exports.hasChanges = hasChanges;
exports.hasCommitsAhead = hasCommitsAhead;
exports.addAll = addAll;
exports.commit = commit;
exports.push = push;
exports.deleteBranch = deleteBranch;
exports.detectRemote = detectRemote;
exports.checkConflictsWithBase = checkConflictsWithBase;
exports.mergeBaseBranch = mergeBaseBranch;
exports.abortMerge = abortMerge;
exports.getConflictDetails = getConflictDetails;
exports.commitMerge = commitMerge;
exports.slugify = slugify;
const node_child_process_1 = require("node:child_process");
const logger_1 = require("./logger");
function git(args, cwd) {
    const result = (0, node_child_process_1.spawnSync)('git', args, {
        cwd: cwd || process.cwd(),
        encoding: 'utf8',
    });
    return {
        stdout: result.stdout || '',
        stderr: result.stderr || '',
        status: result.status ?? 1,
    };
}
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'production']);
function isProtectedBranch(branch) {
    return PROTECTED_BRANCHES.has(branch.toLowerCase());
}
function getCurrentBranch() {
    const result = git(['rev-parse', '--abbrev-ref', 'HEAD']);
    if (result.status !== 0)
        return null;
    return result.stdout.trim() || null;
}
function remoteBranchExists(remote, branch) {
    const result = git(['ls-remote', '--heads', remote, branch]);
    return result.status === 0 && result.stdout.trim().length > 0;
}
function stashChanges() {
    if (!hasChanges())
        return true;
    logger_1.logger.debug('git stash push -u -m aidev-autostash');
    const result = git(['stash', 'push', '-u', '-m', 'aidev-autostash']);
    if (result.status !== 0) {
        logger_1.logger.warn(`git stash failed: ${result.stderr}`);
        return false;
    }
    return true;
}
function fetchAndCheckout(remote, baseBranch) {
    stashChanges();
    logger_1.logger.debug(`git fetch ${remote}`);
    const fetch = git(['fetch', remote]);
    if (fetch.status !== 0) {
        logger_1.logger.error(`git fetch failed: ${fetch.stderr}`);
        return false;
    }
    logger_1.logger.debug(`git checkout ${baseBranch}`);
    const checkout = git(['checkout', baseBranch]);
    if (checkout.status !== 0) {
        logger_1.logger.error(`git checkout ${baseBranch} failed: ${checkout.stderr}`);
        return false;
    }
    logger_1.logger.debug(`git pull ${remote} ${baseBranch}`);
    const pull = git(['pull', remote, baseBranch]);
    if (pull.status !== 0) {
        logger_1.logger.error(`git pull failed: ${pull.stderr}`);
        return false;
    }
    const localRev = git(['rev-parse', 'HEAD']);
    const remoteRev = git(['rev-parse', `${remote}/${baseBranch}`]);
    if (localRev.status === 0 && remoteRev.status === 0 &&
        localRev.stdout.trim() !== remoteRev.stdout.trim()) {
        logger_1.logger.error(`Local ${baseBranch} is out of sync with ${remote}/${baseBranch} after pull — ` +
            'local branch may have diverged. Please resolve manually.');
        return false;
    }
    return true;
}
function fetchAndCheckoutBranch(remote, branch) {
    stashChanges();
    logger_1.logger.debug(`git fetch ${remote}`);
    const fetchResult = git(['fetch', remote]);
    if (fetchResult.status !== 0) {
        logger_1.logger.error(`git fetch failed: ${fetchResult.stderr}`);
        return false;
    }
    logger_1.logger.debug(`git checkout ${branch}`);
    const checkout = git(['checkout', branch]);
    if (checkout.status === 0) {
        logger_1.logger.debug(`git pull ${remote} ${branch}`);
        const pull = git(['pull', remote, branch]);
        if (pull.status !== 0) {
            logger_1.logger.warn(`git pull failed: ${pull.stderr} — continuing with local state`);
        }
        return true;
    }
    logger_1.logger.debug(`git checkout --track ${remote}/${branch}`);
    const track = git(['checkout', '--track', `${remote}/${branch}`]);
    if (track.status !== 0) {
        logger_1.logger.error(`git checkout --track failed: ${track.stderr}`);
        return false;
    }
    return true;
}
function createBranch(branch, expectedBase) {
    if (expectedBase) {
        const current = getCurrentBranch();
        if (current !== expectedBase) {
            logger_1.logger.error(`Cannot create branch "${branch}": expected to be on "${expectedBase}" but currently on "${current}"`);
            return false;
        }
    }
    logger_1.logger.debug(`git checkout -b ${branch}`);
    const result = git(['checkout', '-b', branch]);
    if (result.status !== 0) {
        logger_1.logger.error(`git checkout -b ${branch} failed: ${result.stderr}`);
        return false;
    }
    return true;
}
/**
 * Creates a new branch based on the latest remote base branch.
 * Stashes any local changes, fetches the remote, then branches directly
 * from the remote tracking ref (e.g. origin/main) — avoiding the need
 * to checkout or sync the local base branch.
 */
function createBranchFromRemote(remote, baseBranch, branch) {
    stashChanges();
    logger_1.logger.debug(`git fetch ${remote}`);
    const fetchResult = git(['fetch', remote]);
    if (fetchResult.status !== 0) {
        logger_1.logger.error(`git fetch failed: ${fetchResult.stderr}`);
        return false;
    }
    const startPoint = `${remote}/${baseBranch}`;
    logger_1.logger.debug(`git checkout -b ${branch} ${startPoint}`);
    const result = git(['checkout', '-b', branch, startPoint]);
    if (result.status !== 0) {
        logger_1.logger.warn(`git checkout -b ${branch} ${startPoint} failed: ${result.stderr} — trying existing branch`);
        return fetchAndCheckoutBranch(remote, branch);
    }
    return true;
}
function hasChanges() {
    const result = git(['status', '--porcelain']);
    return result.status === 0 && result.stdout.trim().length > 0;
}
/**
 * Returns true if the current branch has commits ahead of the remote base branch.
 */
function hasCommitsAhead(remote, baseBranch) {
    const result = git(['rev-list', '--count', `${remote}/${baseBranch}..HEAD`]);
    if (result.status !== 0)
        return false;
    const count = parseInt(result.stdout.trim(), 10) || 0;
    return count > 0;
}
function addAll() {
    const result = git(['add', '-A']);
    return result.status === 0;
}
function commit(message, expectedBranch) {
    if (expectedBranch) {
        const current = getCurrentBranch();
        if (current !== expectedBranch) {
            logger_1.logger.error(`Refusing to commit: expected branch "${expectedBranch}" but currently on "${current}"`);
            return false;
        }
        if (isProtectedBranch(current)) {
            logger_1.logger.error(`Refusing to commit directly to protected branch "${current}"`);
            return false;
        }
    }
    logger_1.logger.debug(`git commit -m "${message}"`);
    const result = git(['commit', '-m', message]);
    if (result.status !== 0) {
        logger_1.logger.error(`git commit failed: ${result.stderr}`);
        return false;
    }
    return true;
}
function push(remote, branch) {
    const current = getCurrentBranch();
    if (current !== branch) {
        logger_1.logger.error(`Refusing to push: current branch "${current}" does not match target branch "${branch}"`);
        return false;
    }
    if (isProtectedBranch(branch)) {
        logger_1.logger.error(`Refusing to push directly to protected branch "${branch}"`);
        return false;
    }
    logger_1.logger.debug(`git push ${remote} HEAD:refs/heads/${branch}`);
    const result = git(['push', remote, `HEAD:refs/heads/${branch}`]);
    if (result.status !== 0) {
        logger_1.logger.error(`git push failed: ${result.stderr}`);
        return false;
    }
    return true;
}
function deleteBranch(branch) {
    git(['checkout', '-']);
    git(['branch', '-D', branch]);
}
/** Returns the name of the first usable remote (prefers origin). */
function detectRemote() {
    // Verify origin exists first
    const originCheck = git(['remote', 'get-url', 'origin']);
    if (originCheck.status === 0)
        return 'origin';
    // Fall back to first listed remote
    const list = git(['remote']);
    const first = list.stdout.trim().split('\n')[0]?.trim();
    return first || null;
}
/**
 * Checks whether the current branch can merge cleanly with the remote base
 * branch. Performs a trial merge and aborts it regardless of the outcome.
 */
function checkConflictsWithBase(remote, baseBranch) {
    const logResult = git(['rev-list', '--count', `HEAD..${remote}/${baseBranch}`]);
    const behindCommits = logResult.status === 0 ? parseInt(logResult.stdout.trim(), 10) || 0 : 0;
    if (behindCommits === 0) {
        return { clean: true, conflictFiles: [], behindCommits: 0 };
    }
    const merge = git(['merge', '--no-commit', '--no-ff', `${remote}/${baseBranch}`]);
    if (merge.status === 0) {
        git(['merge', '--abort']);
        return { clean: true, conflictFiles: [], behindCommits };
    }
    const diffResult = git(['diff', '--name-only', '--diff-filter=U']);
    const files = diffResult.stdout.trim().split('\n').filter(Boolean);
    git(['merge', '--abort']);
    return { clean: false, conflictFiles: files, behindCommits };
}
/**
 * Starts a real merge of the remote base branch into the current branch.
 * Returns true if the merge completed without conflicts.
 */
function mergeBaseBranch(remote, baseBranch) {
    const result = git(['merge', `${remote}/${baseBranch}`, '--no-edit']);
    return result.status === 0;
}
function abortMerge() {
    git(['merge', '--abort']);
}
/**
 * Returns the content of files with conflict markers for AI resolution context.
 */
function getConflictDetails() {
    const diffResult = git(['diff', '--name-only', '--diff-filter=U']);
    const files = diffResult.stdout.trim().split('\n').filter(Boolean);
    const details = [];
    for (const file of files) {
        const content = git(['show', `:2:${file}`]);
        const theirs = git(['show', `:3:${file}`]);
        details.push(`### ${file}\n\n` +
            `**Our version (task branch):**\n\`\`\`\n${content.stdout.slice(0, 3000)}\n\`\`\`\n\n` +
            `**Their version (base branch):**\n\`\`\`\n${theirs.stdout.slice(0, 3000)}\n\`\`\``);
    }
    return details.join('\n\n');
}
/** Completes a merge after conflicts have been manually resolved. */
function commitMerge(message) {
    const add = git(['add', '-A']);
    if (add.status !== 0) {
        logger_1.logger.error(`git add failed during merge resolution: ${add.stderr}`);
        return false;
    }
    const result = git(['commit', '-m', message]);
    if (result.status !== 0) {
        logger_1.logger.error(`git merge commit failed: ${result.stderr}`);
        return false;
    }
    return true;
}
function slugify(text) {
    return text
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-|-$/g, '')
        .slice(0, 50);
}
//# sourceMappingURL=git.js.map