import { execFileSync } from 'node:child_process';
// ── Noise Filters ──
// Files excluded from diff stats (still included in the diff itself for context)
const EXCLUDED_PATTERNS = [
    /\.(lock|lockb)$/, // lock files
    /\.min\.(js|css)$/, // minified
    /\.(png|jpg|jpeg|gif|svg|ico|webp)$/, // images
    /\.(woff|woff2|ttf|eot)$/, // fonts
    /node_modules\//, // dependencies
    /\.map$/, // source maps
];
const MAX_DIFF_SIZE = 100_000; // 100KB max for context window
// ── Git Helpers ──
function execGit(args) {
    try {
        return execFileSync('git', args, {
            encoding: 'utf-8',
            maxBuffer: 5 * 1024 * 1024,
            stdio: ['pipe', 'pipe', 'pipe'],
        });
    }
    catch (err) {
        const stderr = err && typeof err === 'object' && 'stderr' in err ? String(err.stderr) : '';
        const msg = err instanceof Error ? err.message : String(err);
        if (process.env['AGENTUITY_DEBUG']) {
            console.error(`[agentuity-coder] git ${args.join(' ')} failed: ${stderr || msg}`);
        }
        return '';
    }
}
function execGitDiff(diffArgs) {
    return execGit(['diff', ...diffArgs]);
}
function execGitShow(hash) {
    return execGit(['show', hash]);
}
// ── Diff Parsing ──
function isExcluded(filePath) {
    return EXCLUDED_PATTERNS.some((pattern) => pattern.test(filePath));
}
function parseDiffStats(diff) {
    if (!diff.trim()) {
        return { files: 0, added: 0, removed: 0, excludedFiles: [] };
    }
    // Split on "diff --git" boundaries
    const fileDiffs = diff.split(/^diff --git /m).filter(Boolean);
    let files = 0;
    let added = 0;
    let removed = 0;
    const excludedFiles = [];
    for (const fileDiff of fileDiffs) {
        // Extract file path from "a/path b/path" header
        const headerMatch = fileDiff.match(/^a\/(.+?) b\//);
        if (!headerMatch)
            continue;
        const filePath = headerMatch[1] ?? '';
        if (isExcluded(filePath)) {
            excludedFiles.push(filePath);
            continue;
        }
        files++;
        // Count added/removed lines (lines starting with + or - but not headers)
        const lines = fileDiff.split('\n');
        for (const line of lines) {
            if (line.startsWith('+') && !line.startsWith('+++')) {
                added++;
            }
            else if (line.startsWith('-') && !line.startsWith('---')) {
                removed++;
            }
        }
    }
    return { files, added, removed, excludedFiles };
}
// ── Review Prompt Builder ──
function buildReviewPrompt(context, diff, stats) {
    const header = `## Code Review Request\n\n${context}\n\n**Stats:** ${stats.files} files changed, +${stats.added}/-${stats.removed} lines`;
    const excludedNote = stats.excludedFiles.length > 0
        ? `\n\n_Excluded from stats: ${stats.excludedFiles.length} noise files (lock files, images, etc.)_`
        : '';
    // Truncate diff if too large
    let diffContent = diff;
    if (diff.length > MAX_DIFF_SIZE) {
        diffContent = diff.slice(0, MAX_DIFF_SIZE) + '\n\n[Diff truncated - too large for review]';
    }
    return `${header}${excludedNote}\n\n\`\`\`diff\n${diffContent}\n\`\`\`\n\nPlease review these changes for:\n1. Correctness and potential bugs\n2. Security concerns\n3. Code quality and best practices\n4. Performance implications`;
}
// ── Main Handler ──
export async function handleReview(_args, ctx, pi) {
    // Non-interactive fallback
    if (!ctx.hasUI) {
        pi.sendUserMessage('@reviewer Review recent code changes', { deliverAs: 'followUp' });
        return;
    }
    // Show mode selector
    const mode = await ctx.ui.select('Review Mode', [
        'Review against a base branch (PR Style)',
        'Review uncommitted changes',
        'Review a specific commit',
        'Custom review instructions',
    ]);
    if (!mode)
        return;
    let diff = '';
    let context = '';
    if (mode.includes('base branch')) {
        const branch = await ctx.ui.input('Base branch', 'main');
        if (!branch)
            return;
        diff = execGitDiff([`${branch}...HEAD`]);
        if (!diff.trim()) {
            ctx.ui.notify(`No changes found between ${branch} and HEAD`, 'warning');
            return;
        }
        context = `Reviewing changes from ${branch} to HEAD`;
    }
    else if (mode.includes('uncommitted')) {
        const staged = execGitDiff(['--cached']);
        const unstaged = execGitDiff([]);
        diff = staged + '\n' + unstaged;
        if (!diff.trim()) {
            ctx.ui.notify('No uncommitted changes found', 'warning');
            return;
        }
        context = 'Reviewing uncommitted changes (staged + unstaged)';
    }
    else if (mode.includes('specific commit')) {
        const hash = await ctx.ui.input('Commit hash', '');
        if (!hash)
            return;
        diff = execGitShow(hash);
        if (!diff.trim()) {
            ctx.ui.notify(`No diff found for commit ${hash}`, 'warning');
            return;
        }
        context = `Reviewing commit ${hash}`;
    }
    else {
        // Custom instructions — no diff
        const instructions = await ctx.ui.input('Review instructions', '');
        if (!instructions)
            return;
        pi.sendUserMessage(`@reviewer ${instructions}`, { deliverAs: 'followUp' });
        return;
    }
    // Parse diff stats
    const stats = parseDiffStats(diff);
    // Show summary notification
    ctx.ui.notify(`${context}: ${stats.files} files, +${stats.added}/-${stats.removed}`, 'info');
    // Compose and send review message
    const reviewPrompt = buildReviewPrompt(context, diff, stats);
    pi.sendUserMessage(`@reviewer ${reviewPrompt}`, { deliverAs: 'followUp' });
}
//# sourceMappingURL=review.js.map