/**
 * Commit Generator
 *
 * Generates conventional commit messages for autofix PRs.
 *
 * @module autofix/commit-generator
 */
import { git, getStagedFiles } from "./branch-manager.js";
import { logger } from "../logger.js";
/**
 * Map severity to commit urgency indicators
 */
const SEVERITY_INDICATORS = {
    critical: "🚨",
    high: "⚠️",
    medium: "🔧",
    low: "📝",
    info: "ℹ️",
};
/**
 * Map pattern categories to commit types
 */
function getCommitType(patternId) {
    if (patternId.startsWith("sec-")) {
        return "security";
    }
    if (patternId.startsWith("perf-")) {
        return "perf";
    }
    if (patternId.startsWith("type-") || patternId.startsWith("qual-")) {
        return "refactor";
    }
    return "fix";
}
/**
 * Generate a commit message for a batch of fixes
 */
export function generateCommitMessage(fixes, severity, options) {
    const { includeEmoji = false, includeSeverity = true, maxLength = 72, } = options ?? {};
    const appliedFixes = fixes.filter((f) => f.applied || f.diff);
    if (appliedFixes.length === 0) {
        return "chore: attempted autofix (no changes applied)";
    }
    // Group by pattern type
    const patterns = new Map();
    const files = new Set();
    for (const fix of appliedFixes) {
        files.add(fix.file);
        // Extract pattern from findingId (e.g., "sec-001" -> "sec")
        const parts = fix.findingId.split("-");
        if (parts.length >= 1) {
            const prefix = parts[0];
            patterns.set(prefix, (patterns.get(prefix) || 0) + 1);
        }
    }
    // Determine primary commit type
    const patternEntries = Array.from(patterns.entries());
    const primaryPattern = patternEntries.sort((a, b) => b[1] - a[1])[0]?.[0] ?? "fix";
    const commitType = primaryPattern === "sec" ? "security" : "fix";
    // Build message parts
    const parts = [];
    // Emoji prefix
    if (includeEmoji && severity) {
        parts.push(SEVERITY_INDICATORS[severity]);
    }
    // Commit type and scope
    const scope = commitType === "security" ? "security" : undefined;
    const typeScope = scope ? `fix(${scope})` : "fix";
    parts.push(`${typeScope}:`);
    // Summary
    if (appliedFixes.length === 1) {
        parts.push(`Apply autofix for ${appliedFixes[0].findingId}`);
    }
    else {
        parts.push(`Apply ${appliedFixes.length} automated fixes`);
    }
    // Severity tag
    if (includeSeverity && severity) {
        parts.push(`[${severity}]`);
    }
    let message = parts.join(" ");
    // Truncate if needed
    if (message.length > maxLength) {
        message = message.slice(0, maxLength - 3) + "...";
    }
    return message;
}
/**
 * Generate extended commit body with details
 */
export function generateCommitBody(fixes, severity) {
    const lines = [];
    const appliedFixes = fixes.filter((f) => f.applied || f.diff);
    if (appliedFixes.length === 0) {
        return "";
    }
    // Summary
    lines.push("## Automated Security Fixes\n");
    if (severity) {
        lines.push(`Severity: **${severity.toUpperCase()}**\n`);
    }
    // Group fixes by file
    const byFile = new Map();
    for (const fix of appliedFixes) {
        const existing = byFile.get(fix.file) || [];
        existing.push(fix);
        byFile.set(fix.file, existing);
    }
    lines.push("### Changes\n");
    for (const [file, fileFixes] of byFile) {
        lines.push(`- **${file}**`);
        for (const fix of fileFixes) {
            lines.push(`  - ${fix.findingId}: Line ${fix.diff?.lineNumber || "unknown"}`);
        }
    }
    lines.push("\n---");
    lines.push("Generated by Vaspera Hardening MCP");
    return lines.join("\n");
}
/**
 * Create a commit with the staged changes
 */
export async function createCommit(cwd, message, body, options) {
    const stagedFiles = await getStagedFiles(cwd);
    if (stagedFiles.length === 0) {
        return { error: "No staged files to commit" };
    }
    // Build full commit message
    const fullMessage = body ? `${message}\n\n${body}` : message;
    // Add co-authors
    let finalMessage = fullMessage;
    const coAuthors = options?.coAuthors ?? ["Claude <noreply@anthropic.com>"];
    if (coAuthors.length > 0) {
        const coAuthorLines = coAuthors
            .map((author) => `Co-Authored-By: ${author}`)
            .join("\n");
        finalMessage = `${finalMessage}\n\n${coAuthorLines}`;
    }
    // Build commit args
    const args = ["commit", "-m", finalMessage];
    if (options?.signOff) {
        args.push("--signoff");
    }
    logger.info("git.commit", {
        files: stagedFiles.length,
        messagePreview: message.slice(0, 50),
    });
    const result = await git(args, { cwd });
    if (!result.success) {
        logger.error("git.commit_failed", {
            error: result.stderr,
            exitCode: result.exitCode,
        });
        return { error: result.stderr || "Commit failed" };
    }
    // Get the commit SHA
    const shaResult = await git(["rev-parse", "HEAD"], { cwd });
    const sha = shaResult.success ? shaResult.stdout : undefined;
    logger.info("git.commit_success", { sha, files: stagedFiles.length });
    return {
        message,
        sha,
        files: stagedFiles,
        author: "vaspera-hardening",
        coAuthors,
        signed: false,
    };
}
/**
 * Generate PR title based on fixes
 */
export function generatePRTitle(fixes, severity, template) {
    const appliedFixes = fixes.filter((f) => f.applied || f.diff);
    const count = appliedFixes.length;
    const files = new Set(appliedFixes.map((f) => f.file)).size;
    if (template) {
        return template
            .replace("{{count}}", String(count))
            .replace("{{severity}}", severity ?? "mixed")
            .replace("{{files}}", String(files));
    }
    if (severity) {
        return `fix(security): Apply ${count} ${severity}-severity autofix${count !== 1 ? "es" : ""}`;
    }
    return `fix(security): Apply ${count} automated security fix${count !== 1 ? "es" : ""}`;
}
/**
 * Generate PR body/description
 */
export function generatePRBody(fixes, severity, options) {
    const lines = [];
    const appliedFixes = fixes.filter((f) => f.applied || f.diff);
    lines.push("## Summary\n");
    lines.push("This PR contains automated security fixes generated by Vaspera Hardening MCP.\n");
    if (severity) {
        lines.push(`**Severity Level:** ${severity.toUpperCase()}\n`);
    }
    if (options?.certificationId) {
        lines.push(`**Certification ID:** \`${options.certificationId}\`\n`);
    }
    lines.push("## Fixes Applied\n");
    // Group by file
    const byFile = new Map();
    for (const fix of appliedFixes) {
        const existing = byFile.get(fix.file) || [];
        existing.push(fix);
        byFile.set(fix.file, existing);
    }
    for (const [file, fileFixes] of byFile) {
        lines.push(`### \`${file}\`\n`);
        for (const fix of fileFixes) {
            lines.push(`- **${fix.findingId}** (line ${fix.diff?.lineNumber || "?"})`);
            if (options?.includeBeforeAfter && fix.diff) {
                lines.push("");
                lines.push("<details>");
                lines.push("<summary>View changes</summary>\n");
                lines.push("**Before:**");
                lines.push("```");
                lines.push(fix.diff.before);
                lines.push("```\n");
                lines.push("**After:**");
                lines.push("```");
                lines.push(fix.diff.after);
                lines.push("```");
                lines.push("</details>\n");
            }
        }
    }
    lines.push("\n## Review Checklist\n");
    lines.push("- [ ] Changes are semantically correct");
    lines.push("- [ ] No regressions introduced");
    lines.push("- [ ] Tests pass");
    lines.push("\n---");
    lines.push("🤖 Generated with [Vaspera Hardening MCP](https://github.com/RCOLKITT/hardening-mcp)");
    return lines.join("\n");
}
/**
 * Categorize fixes by severity
 */
export function groupFixesBySeverity(fixes, findingSeverities) {
    const groups = new Map();
    for (const fix of fixes) {
        const severity = findingSeverities.get(fix.findingId) ?? "medium";
        const existing = groups.get(severity) || [];
        existing.push(fix);
        groups.set(severity, existing);
    }
    return groups;
}
/**
 * Categorize fixes by file
 */
export function groupFixesByFile(fixes) {
    const groups = new Map();
    for (const fix of fixes) {
        const existing = groups.get(fix.file) || [];
        existing.push(fix);
        groups.set(fix.file, existing);
    }
    return groups;
}
/**
 * Categorize fixes by pattern
 */
export function groupFixesByPattern(fixes) {
    const groups = new Map();
    for (const fix of fixes) {
        // Extract pattern from findingId (e.g., "sec-hardcoded-secret-001" -> "sec-hardcoded-secret")
        const parts = fix.findingId.split("-");
        const patternId = parts.slice(0, -1).join("-") || fix.findingId;
        const existing = groups.get(patternId) || [];
        existing.push(fix);
        groups.set(patternId, existing);
    }
    return groups;
}
//# sourceMappingURL=commit-generator.js.map