"use strict";
/**
 * Suppression Manager
 * Central class for managing all suppression logic
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SuppressionManager = void 0;
const minimatch_1 = require("minimatch");
const config_loader_1 = require("./config-loader");
const inline_parser_1 = require("./inline-parser");
const hash_1 = require("./hash");
/**
 * SuppressionManager handles all suppression logic
 *
 * Priority order for suppressions:
 * 1. Inline comment suppressions (highest priority)
 * 2. Config file finding suppressions (by hash)
 * 3. Config file rule suppressions (by category)
 */
class SuppressionManager {
    constructor(options) {
        // Cache for inline suppressions by file path
        this.inlineSuppressionCache = new Map();
        this.projectPath = options.projectPath;
        this.includeExpired = options.includeExpired ?? false;
        if (options.config) {
            this.config = options.config;
            this.configPath = undefined;
            this.configErrors = [];
        }
        else {
            const result = (0, config_loader_1.loadSuppressionConfig)(options.projectPath);
            this.config = result.config;
            this.configPath = result.configPath;
            this.configErrors = result.errors;
        }
    }
    /**
     * Get configuration errors (if any)
     */
    getConfigErrors() {
        return this.configErrors;
    }
    /**
     * Get the path to the config file (if found)
     */
    getConfigPath() {
        return this.configPath;
    }
    /**
     * Check if a file path should be ignored entirely
     */
    isPathIgnored(filePath) {
        if (!this.config.ignore || this.config.ignore.length === 0) {
            return false;
        }
        const normalizedPath = (0, hash_1.normalizePathForHash)(filePath);
        return this.config.ignore.some(pattern => (0, minimatch_1.minimatch)(normalizedPath, pattern, { dot: true }));
    }
    /**
     * Parse inline suppressions for a file
     */
    getInlineSuppressions(filePath, content) {
        // Check cache first
        const cached = this.inlineSuppressionCache.get(filePath);
        if (cached) {
            return cached;
        }
        // Parse and cache
        const suppressions = (0, inline_parser_1.parseInlineSuppressions)(content);
        this.inlineSuppressionCache.set(filePath, suppressions);
        return suppressions;
    }
    /**
     * Check if a finding is suppressed
     *
     * Priority:
     * 1. Inline comment (highest)
     * 2. Config finding suppression (by hash)
     * 3. Config rule suppression (by category)
     */
    isFindingSuppressed(finding, fileContent) {
        const hash = (0, hash_1.computeFindingHash)(finding);
        const normalizedPath = (0, hash_1.normalizePathForHash)(finding.filePath);
        // 1. Check inline suppressions (if file content provided)
        if (fileContent) {
            const inlineSuppressions = this.getInlineSuppressions(finding.filePath, fileContent);
            const inlineSuppression = (0, inline_parser_1.isLineSuppressed)(inlineSuppressions, finding.lineNumber, finding.category);
            if (inlineSuppression) {
                return {
                    suppressed: true,
                    match: {
                        type: 'inline',
                        reason: inlineSuppression.reason,
                    },
                    hash,
                };
            }
        }
        // 2. Check config finding suppressions (by hash)
        const findingSuppressions = this.config.suppressions?.findings || [];
        const findingSuppression = findingSuppressions.find(s => s.hash === hash);
        if (findingSuppression) {
            const expired = (0, config_loader_1.isExpired)(findingSuppression.expires);
            if (expired && !this.includeExpired) {
                // Expired - not suppressed
                return {
                    suppressed: false,
                    match: {
                        type: 'config-finding',
                        reason: findingSuppression.reason,
                        expires: findingSuppression.expires,
                        expired: true,
                    },
                    hash,
                };
            }
            return {
                suppressed: true,
                match: {
                    type: 'config-finding',
                    reason: findingSuppression.reason,
                    expires: findingSuppression.expires,
                    expired,
                },
                hash,
            };
        }
        // 3. Check config rule suppressions (by category)
        const ruleSuppressions = this.config.suppressions?.rules || [];
        const ruleSuppression = ruleSuppressions.find(s => {
            // Must match category
            if (s.category !== finding.category) {
                return false;
            }
            // If paths specified, must match one of them
            if (s.paths && s.paths.length > 0) {
                const matchesPath = s.paths.some(pattern => (0, minimatch_1.minimatch)(normalizedPath, pattern, { dot: true }));
                if (!matchesPath) {
                    return false;
                }
            }
            return true;
        });
        if (ruleSuppression) {
            const expired = (0, config_loader_1.isExpired)(ruleSuppression.expires);
            if (expired && !this.includeExpired) {
                // Expired - not suppressed
                return {
                    suppressed: false,
                    match: {
                        type: 'config-rule',
                        reason: ruleSuppression.reason,
                        expires: ruleSuppression.expires,
                        expired: true,
                    },
                    hash,
                };
            }
            return {
                suppressed: true,
                match: {
                    type: 'config-rule',
                    reason: ruleSuppression.reason,
                    expires: ruleSuppression.expires,
                    expired,
                },
                hash,
            };
        }
        // Not suppressed
        return {
            suppressed: false,
            hash,
        };
    }
    /**
     * Apply suppressions to a list of findings
     */
    applySuppressions(findings, files) {
        // Build file content lookup
        const fileContentMap = new Map();
        for (const file of files) {
            fileContentMap.set((0, hash_1.normalizePathForHash)(file.path), file.content);
        }
        const passed = [];
        const suppressed = [];
        let expiredCount = 0;
        const stats = {
            total: findings.length,
            inlineSuppressed: 0,
            configFindingSuppressed: 0,
            configRuleSuppressed: 0,
            expired: 0,
        };
        for (const finding of findings) {
            // Get file content for inline suppression checking
            const normalizedPath = (0, hash_1.normalizePathForHash)(finding.filePath);
            const fileContent = fileContentMap.get(normalizedPath);
            const result = this.isFindingSuppressed(finding, fileContent);
            if (result.suppressed) {
                suppressed.push({
                    vulnerability: {
                        id: finding.id,
                        filePath: finding.filePath,
                        lineNumber: finding.lineNumber,
                        category: finding.category,
                        severity: finding.severity,
                        title: finding.title,
                    },
                    suppression: {
                        type: result.match.type,
                        reason: result.match.reason,
                        expires: result.match.expires,
                        hash: result.hash,
                    },
                });
                // Update stats
                switch (result.match.type) {
                    case 'inline':
                        stats.inlineSuppressed++;
                        break;
                    case 'config-finding':
                        stats.configFindingSuppressed++;
                        break;
                    case 'config-rule':
                        stats.configRuleSuppressed++;
                        break;
                }
            }
            else {
                passed.push(finding);
                // Track expired suppressions
                if (result.match?.expired) {
                    expiredCount++;
                    stats.expired++;
                }
            }
        }
        return {
            findings: passed,
            suppressed,
            expiredSuppressions: expiredCount,
            stats,
        };
    }
    /**
     * Get all suppressions from config
     */
    getAllSuppressions() {
        return {
            rules: this.config.suppressions?.rules || [],
            findings: this.config.suppressions?.findings || [],
        };
    }
    /**
     * Get ignore patterns from config
     */
    getIgnorePatterns() {
        return this.config.ignore || [];
    }
    /**
     * Check if suppression system is active (has any suppressions)
     */
    hasSuppressions() {
        const rules = this.config.suppressions?.rules || [];
        const findings = this.config.suppressions?.findings || [];
        const ignore = this.config.ignore || [];
        return rules.length > 0 || findings.length > 0 || ignore.length > 0;
    }
    /**
     * Get summary of suppression configuration
     */
    getSummary() {
        return {
            configPath: this.configPath,
            ruleCount: this.config.suppressions?.rules?.length || 0,
            findingCount: this.config.suppressions?.findings?.length || 0,
            ignorePatternCount: this.config.ignore?.length || 0,
            hasErrors: this.configErrors.length > 0,
        };
    }
    /**
     * Clear the inline suppression cache
     * (useful if files have been modified)
     */
    clearCache() {
        this.inlineSuppressionCache.clear();
    }
}
exports.SuppressionManager = SuppressionManager;
//# sourceMappingURL=manager.js.map