"use strict";
/**
 * Git Diff Parser
 * Parses unified diff format to extract changed line ranges
 * Used for incremental scanning to only flag findings on changed lines
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseDiff = parseDiff;
exports.parseChangedFileList = parseChangedFileList;
exports.isOnChangedLine = isOnChangedLine;
exports.isNearChangedLine = isNearChangedLine;
exports.getChangedFilePaths = getChangedFilePaths;
exports.filterToChangedLines = filterToChangedLines;
/**
 * Parse a unified diff output
 *
 * @param diffOutput - The raw git diff output
 * @param contextWindow - Lines around changes to consider "affected" (default: 5)
 * @returns Map of file path to FileDiff
 */
function parseDiff(diffOutput, contextWindow = 5) {
    const files = new Map();
    if (!diffOutput || diffOutput.trim() === '') {
        return files;
    }
    // Split into file sections (each starts with "diff --git")
    const fileSections = diffOutput.split(/^diff --git /gm).filter(s => s.trim());
    for (const section of fileSections) {
        const fileDiff = parseFileSection('diff --git ' + section, contextWindow);
        if (fileDiff) {
            files.set(fileDiff.path, fileDiff);
        }
    }
    return files;
}
/**
 * Parse a single file's diff section
 */
function parseFileSection(section, contextWindow) {
    const lines = section.split('\n');
    // Extract file paths from header
    // Format: diff --git a/path/to/file b/path/to/file
    const headerMatch = lines[0].match(/diff --git a\/(.+) b\/(.+)/);
    if (!headerMatch)
        return null;
    const oldPath = headerMatch[1];
    const newPath = headerMatch[2];
    // Detect file status
    let isNew = false;
    let isDeleted = false;
    let isRenamed = oldPath !== newPath;
    for (const line of lines.slice(0, 10)) {
        if (line.startsWith('new file mode'))
            isNew = true;
        if (line.startsWith('deleted file mode'))
            isDeleted = true;
        if (line.startsWith('rename from'))
            isRenamed = true;
    }
    // Parse hunks
    const hunks = [];
    const changedLines = new Set();
    const affectedLines = new Set();
    // Find hunk headers: @@ -oldStart,oldLines +newStart,newLines @@
    const hunkRegex = /@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/g;
    let match;
    const sectionContent = section;
    while ((match = hunkRegex.exec(sectionContent)) !== null) {
        const oldStart = parseInt(match[1], 10);
        const oldLines = parseInt(match[2] || '1', 10);
        const newStart = parseInt(match[3], 10);
        const newLines = parseInt(match[4] || '1', 10);
        // Find the content of this hunk (until next @@ or end)
        const hunkStartIndex = match.index + match[0].length;
        const nextHunkMatch = /@@ -\d+/.exec(sectionContent.slice(hunkStartIndex + 1));
        const hunkEndIndex = nextHunkMatch
            ? hunkStartIndex + 1 + nextHunkMatch.index
            : sectionContent.length;
        const hunkContent = sectionContent.slice(hunkStartIndex, hunkEndIndex);
        const hunkLines = hunkContent.split('\n');
        const addedLines = [];
        const removedLines = [];
        const contextLines = [];
        let newLineNum = newStart;
        let oldLineNum = oldStart;
        for (const line of hunkLines) {
            if (line.startsWith('+') && !line.startsWith('+++')) {
                // Added line
                addedLines.push(newLineNum);
                changedLines.add(newLineNum);
                newLineNum++;
            }
            else if (line.startsWith('-') && !line.startsWith('---')) {
                // Removed line
                removedLines.push(oldLineNum);
                oldLineNum++;
            }
            else if (line.startsWith(' ') || line === '') {
                // Context line
                contextLines.push(newLineNum);
                newLineNum++;
                oldLineNum++;
            }
        }
        hunks.push({
            oldStart,
            oldLines,
            newStart,
            newLines,
            addedLines,
            removedLines,
            contextLines,
        });
    }
    // Calculate affected lines (changed lines + context window)
    for (const line of changedLines) {
        for (let i = Math.max(1, line - contextWindow); i <= line + contextWindow; i++) {
            affectedLines.add(i);
        }
    }
    return {
        path: newPath,
        oldPath: isRenamed ? oldPath : undefined,
        isNew,
        isDeleted,
        isRenamed,
        hunks,
        changedLines,
        affectedLines,
    };
}
/**
 * Parse a simple list of changed file paths
 * Used when full diff isn't available (e.g., GitHub API file list)
 */
function parseChangedFileList(files) {
    const result = new Map();
    for (const path of files) {
        result.set(path, {
            path,
            isNew: false,
            isDeleted: false,
            isRenamed: false,
            hunks: [],
            // Without diff content, consider all lines as changed
            changedLines: new Set(),
            affectedLines: new Set(),
        });
    }
    return result;
}
/**
 * Check if a finding is on a changed line
 */
function isOnChangedLine(filePath, lineNumber, diffs) {
    const fileDiff = diffs.get(filePath);
    if (!fileDiff)
        return false;
    // If no specific changed lines (file list only), consider all findings relevant
    if (fileDiff.changedLines.size === 0)
        return true;
    return fileDiff.changedLines.has(lineNumber);
}
/**
 * Check if a finding is near a changed line (within context window)
 */
function isNearChangedLine(filePath, lineNumber, diffs) {
    const fileDiff = diffs.get(filePath);
    if (!fileDiff)
        return false;
    // If no specific affected lines, consider all findings relevant
    if (fileDiff.affectedLines.size === 0)
        return true;
    return fileDiff.affectedLines.has(lineNumber);
}
/**
 * Get all changed file paths from diffs
 */
function getChangedFilePaths(diffs) {
    return Array.from(diffs.keys()).filter(path => {
        const diff = diffs.get(path);
        return diff && !diff.isDeleted;
    });
}
/**
 * Filter vulnerabilities to only those on/near changed lines
 */
function filterToChangedLines(findings, diffs, options = {}) {
    const { strictMode = false } = options;
    return findings.filter(finding => {
        // If file not in diff, it wasn't changed - exclude
        if (!diffs.has(finding.filePath))
            return false;
        // In strict mode, only include findings on exactly changed lines
        if (strictMode) {
            return isOnChangedLine(finding.filePath, finding.lineNumber, diffs);
        }
        // In normal mode, include findings near changed lines
        return isNearChangedLine(finding.filePath, finding.lineNumber, diffs);
    });
}
//# sourceMappingURL=diff-parser.js.map