"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.architectureHealthCategoryWeights = exports.defaultArchitectureHealthCategories = void 0;
exports.summarizeArchitectureScoreProblems = summarizeArchitectureScoreProblems;
exports.calculateArchitectureHealthScore = calculateArchitectureHealthScore;
exports.defaultArchitectureHealthCategories = [
    'architecture',
    'dependency',
    'api',
    'database',
    'documentation',
    'security',
];
exports.architectureHealthCategoryWeights = {
    architecture: 0.25,
    dependency: 0.20,
    security: 0.20,
    database: 0.15,
    api: 0.10,
    documentation: 0.10,
};
const qualityErrorPenalty = 10;
const qualityWarningPenalty = 4;
const qualityInfoPenalty = 1;
const readinessSetupGapErrorPenalty = 6;
const readinessSetupGapWarningPenalty = 3;
const readinessSetupGapInfoPenalty = 1;
const overallSetupGapErrorPenalty = 2;
const overallSetupGapWarningPenalty = 1;
const overallSetupGapInfoPenalty = 1;
const minScore = 0;
const maxScore = 100;
const cycleRuleIds = new Set(['AP-DEP-001', 'AP-DEP-008']);
const severeDependencyRuleIds = new Set([
    'AP-DEP-001',
    'AP-DEP-002',
    'AP-DEP-003',
    'AP-DEP-005',
    'AP-DEP-008',
    'AP-DEP-010',
]);
const lowWeightHeuristicRuleIds = new Set([
    'AP-APP-001',
    'AP-APP-002',
    'AP-APP-004',
    'AP-APP-005',
    'AP-AUTH-001',
    'AP-AUTH-002',
    'AP-AUTH-003',
    'AP-TEN-001',
    'AP-TEN-004',
    'AP-TXN-001',
    'AP-TXN-002',
    'AP-TXN-003',
    'AP-TXN-004',
    'AP-DOM-001',
    'AP-DOM-004',
    'AP-API-010',
    'AP-API-011',
    'AP-API-012',
    'AP-API-013',
    'AP-API-014',
    'AP-DQR-005',
    'AP-DQR-006',
    'AP-DQR-007',
    'AP-DQR-008',
    'AP-EVT-001',
    'AP-EVT-002',
    'AP-EVT-003',
    'AP-EVT-004',
]);
const mediumWeightHeuristicRuleIds = new Set([
    'AP-APP-003',
    'AP-TEN-002',
    'AP-TEN-003',
    'AP-DOM-002',
    'AP-DOM-003',
    'AP-DOM-005',
    'AP-API-015',
]);
const warningFamilyPenaltyCap = 18;
const heuristicWarningFamilyPenaltyCap = 12;
const architectureSmellFamilyPenaltyCap = 7;
function clampScore(value) {
    if (value < minScore) {
        return minScore;
    }
    if (value > maxScore) {
        return maxScore;
    }
    return value;
}
function getSeverityRank(severity) {
    if (severity === 'error') {
        return 0;
    }
    if (severity === 'warning') {
        return 1;
    }
    return 2;
}
function getRuleFamily(ruleId) {
    if (!ruleId) {
        return 'unknown';
    }
    const match = /^AP-[A-Z]+/u.exec(ruleId);
    return match?.[0] ?? ruleId;
}
function extractProblemIdentity(deductionKey) {
    if (!deductionKey) {
        return '';
    }
    if (deductionKey.includes('|')) {
        const parts = deductionKey.split('|');
        if (parts[0] === 'quality' || parts[0] === 'setup' || parts[0] === 'guidance' || parts[0] === 'passed') {
            parts.shift();
        }
        if (parts[0]?.startsWith('AP-')) {
            parts.shift();
        }
        return parts.join('|');
    }
    const parts = deductionKey.split(':');
    if (parts[0] === 'quality' || parts[0] === 'setup' || parts[0] === 'guidance' || parts[0] === 'passed') {
        parts.shift();
    }
    if (parts[0]?.startsWith('AP-')) {
        parts.shift();
    }
    return parts.join(':');
}
function normalizeProblemIdentity(result, identity) {
    const normalized = identity.replace(/\\/g, '/').trim().toLowerCase();
    if (!normalized) {
        return '';
    }
    if (normalized.includes('|')) {
        const parts = normalized.split('|');
        if (result.category === 'dependency' &&
            parts.length >= 4 &&
            /^\d+$/u.test(parts[3] ?? '')) {
            return [parts[0], parts[1], parts[2], parts[4] ?? ''].join('|');
        }
    }
    return normalized;
}
function buildQualityProblemClusterKey(result) {
    const family = getRuleFamily(result.ruleId);
    const identity = normalizeProblemIdentity(result, extractProblemIdentity(result.deductionKey));
    if (typeof result.ruleId === 'string' && cycleRuleIds.has(result.ruleId)) {
        return ['quality', result.category, family, 'cycle', identity || 'shared-root-cause'].join('|');
    }
    if (result.severity === 'warning') {
        return ['quality', result.category, family, identity || result.ruleId || 'generic'].join('|');
    }
    return ['quality', result.category, family, result.ruleId ?? 'unknown', identity || 'generic'].join('|');
}
function getClusterWeight(cluster, index) {
    if (index === 0) {
        return 1;
    }
    if (cluster.severity === 'error') {
        return Math.max(0.35, 0.85 - index * 0.15);
    }
    if (cluster.isArchitectureSmell) {
        return Math.max(0.15, 0.65 - index * 0.12);
    }
    if (cluster.isHeuristic) {
        return Math.max(0.2, 0.75 - index * 0.12);
    }
    return Math.max(0.2, 0.8 - index * 0.12);
}
function getFamilyPenaltyCap(cluster) {
    if (cluster.severity !== 'warning') {
        return undefined;
    }
    if (cluster.isArchitectureSmell) {
        return architectureSmellFamilyPenaltyCap;
    }
    if (cluster.isHeuristic) {
        return heuristicWarningFamilyPenaltyCap;
    }
    return warningFamilyPenaltyCap;
}
function summarizeArchitectureScoreProblems(input) {
    const qualityResults = input.results.filter((result) => !result.passed && (result.impact ?? 'quality') === 'quality');
    const clusterMap = new Map();
    for (const result of qualityResults) {
        const key = buildQualityProblemClusterKey(result);
        const family = getRuleFamily(result.ruleId);
        const existing = clusterMap.get(key);
        const resultPenalty = getQualityPenaltyForResult(result);
        if (existing) {
            existing.resultCount += 1;
            if (getSeverityRank(result.severity) < getSeverityRank(existing.severity)) {
                existing.severity = result.severity;
            }
            existing.basePenalty = Math.max(existing.basePenalty, resultPenalty);
            if (typeof result.ruleId === 'string') {
                existing.ruleIds.add(result.ruleId);
            }
            existing.isHeuristic = existing.isHeuristic || isHeuristicRuleId(result.ruleId);
            existing.isArchitectureSmell =
                existing.isArchitectureSmell || isArchitectureSmellWarningRuleId(result.ruleId);
            continue;
        }
        clusterMap.set(key, {
            key,
            category: result.category,
            family,
            severity: result.severity,
            impact: result.impact ?? 'quality',
            ruleIds: new Set(typeof result.ruleId === 'string' ? [result.ruleId] : []),
            resultCount: 1,
            basePenalty: resultPenalty,
            isHeuristic: isHeuristicRuleId(result.ruleId),
            isArchitectureSmell: isArchitectureSmellWarningRuleId(result.ruleId),
        });
    }
    const familyGroups = new Map();
    const preliminaryClusters = [...clusterMap.values()]
        .sort((left, right) => {
        const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
        if (severityCompare !== 0) {
            return severityCompare;
        }
        const categoryCompare = left.category.localeCompare(right.category);
        if (categoryCompare !== 0) {
            return categoryCompare;
        }
        const familyCompare = left.family.localeCompare(right.family);
        if (familyCompare !== 0) {
            return familyCompare;
        }
        return left.key.localeCompare(right.key);
    })
        .map((entry) => {
        const confidenceBonus = Math.min(3, Math.max(0, entry.ruleIds.size - 1));
        const duplicateBonus = Math.min(2, Math.max(0, Math.ceil(Math.log2(entry.resultCount)) - 1));
        return {
            key: entry.key,
            category: entry.category,
            family: entry.family,
            severity: entry.severity,
            impact: entry.impact,
            ruleIds: [...entry.ruleIds].sort((left, right) => left.localeCompare(right)),
            resultCount: entry.resultCount,
            penalty: entry.basePenalty + confidenceBonus + duplicateBonus,
            isHeuristic: entry.isHeuristic,
            isArchitectureSmell: entry.isArchitectureSmell,
        };
    });
    for (const cluster of preliminaryClusters) {
        const familyKey = `${cluster.category}|${cluster.family}`;
        const existing = familyGroups.get(familyKey) ?? [];
        existing.push(cluster);
        familyGroups.set(familyKey, existing);
    }
    const clusters = [];
    for (const group of [...familyGroups.entries()]
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([, value]) => value)) {
        const ordered = [...group].sort((left, right) => {
            const severityCompare = getSeverityRank(left.severity) - getSeverityRank(right.severity);
            if (severityCompare !== 0) {
                return severityCompare;
            }
            if (left.penalty !== right.penalty) {
                return right.penalty - left.penalty;
            }
            return left.key.localeCompare(right.key);
        });
        let appliedPenalty = 0;
        for (const [index, cluster] of ordered.entries()) {
            const weightedPenalty = Math.round(cluster.penalty * getClusterWeight(cluster, index));
            const familyCap = getFamilyPenaltyCap(cluster);
            const remainingCap = familyCap === undefined ? undefined : Math.max(0, familyCap - appliedPenalty);
            const finalPenalty = remainingCap === undefined
                ? weightedPenalty
                : Math.min(weightedPenalty, remainingCap);
            appliedPenalty += finalPenalty;
            clusters.push({
                ...cluster,
                penalty: finalPenalty,
            });
        }
    }
    const qualityClusterCount = clusters.length;
    const blockingClusterCount = clusters.filter((cluster) => cluster.severity === 'error').length;
    const heuristicClusterCount = clusters.filter((cluster) => cluster.isHeuristic).length;
    return {
        clusters,
        qualityClusterCount,
        blockingClusterCount,
        heuristicClusterCount,
    };
}
function getOverallSetupGapPenaltyForSeverity(severity) {
    if (severity === 'error') {
        return overallSetupGapErrorPenalty;
    }
    if (severity === 'warning') {
        return overallSetupGapWarningPenalty;
    }
    return overallSetupGapInfoPenalty;
}
function getAdditionalQualityPenalty(result) {
    if (result.impact !== 'quality') {
        return 0;
    }
    let penalty = result.severity === 'error' ? 2 : 0;
    switch (result.ruleId) {
        case 'AP-DEP-004':
            penalty -= result.severity === 'error' ? 8 : 1;
            break;
        case 'AP-DEP-005':
            penalty += 6;
            break;
        case 'AP-DEP-002':
        case 'AP-DEP-003':
        case 'AP-DEP-010':
            penalty += 4;
            break;
        case 'AP-DEP-001':
        case 'AP-DEP-008':
            penalty += 5;
            break;
        default:
            break;
    }
    return penalty;
}
function isHeuristicRuleId(ruleId) {
    return (typeof ruleId === 'string' &&
        (lowWeightHeuristicRuleIds.has(ruleId) || mediumWeightHeuristicRuleIds.has(ruleId)));
}
function isArchitectureSmellWarningRuleId(ruleId) {
    return (typeof ruleId === 'string' &&
        (/^AP-APP-/u.test(ruleId) || /^AP-DOM-/u.test(ruleId) || /^AP-TXN-/u.test(ruleId)));
}
function getQualityPenaltyForResult(result) {
    if (result.severity === 'warning' && isArchitectureSmellWarningRuleId(result.ruleId)) {
        return 1;
    }
    if (result.severity === 'warning' &&
        typeof result.ruleId === 'string' &&
        lowWeightHeuristicRuleIds.has(result.ruleId)) {
        return 2;
    }
    if (result.severity === 'warning' &&
        typeof result.ruleId === 'string' &&
        mediumWeightHeuristicRuleIds.has(result.ruleId)) {
        return 3;
    }
    let base = qualityInfoPenalty;
    if (result.severity === 'error') {
        base = qualityErrorPenalty;
    }
    else if (result.severity === 'warning') {
        base = qualityWarningPenalty;
    }
    return base + getAdditionalQualityPenalty(result);
}
function dedupeResultsByDeductionKey(results) {
    const seen = new Set();
    const deduped = [];
    for (const result of results) {
        if (!result.deductionKey) {
            deduped.push(result);
            continue;
        }
        if (seen.has(result.deductionKey)) {
            continue;
        }
        seen.add(result.deductionKey);
        deduped.push(result);
    }
    return deduped;
}
function getReadinessPenaltyForSetupGapSeverity(severity) {
    if (severity === 'error') {
        return readinessSetupGapErrorPenalty;
    }
    if (severity === 'warning') {
        return readinessSetupGapWarningPenalty;
    }
    return readinessSetupGapInfoPenalty;
}
function deriveGovernanceRiskLevelFromResults(results) {
    const qualityFailures = dedupeResultsByDeductionKey(results).filter((result) => !result.passed && (result.impact ?? 'quality') === 'quality');
    const hasCriticalRule = qualityFailures.some((result) => result.ruleId === 'AP-DEP-008' ||
        result.ruleId === 'AP-DEP-005');
    if (hasCriticalRule) {
        return 'critical';
    }
    if (qualityFailures.length > 0 && qualityFailures.every((result) => isHeuristicRuleId(result.ruleId))) {
        return qualityFailures.some((result) => typeof result.ruleId === 'string' && mediumWeightHeuristicRuleIds.has(result.ruleId))
            ? 'moderate'
            : 'low';
    }
    if (qualityFailures.length > 1) {
        return 'high';
    }
    if (qualityFailures.length === 1) {
        return 'moderate';
    }
    return 'low';
}
function applyFinalScoreAdjustments(input) {
    let adjusted = input.baseScore;
    const uniqueResults = dedupeResultsByDeductionKey(input.results);
    const problemSummary = summarizeArchitectureScoreProblems({ results: uniqueResults });
    const publicSurfaceBypassViolations = problemSummary.clusters.filter((cluster) => cluster.ruleIds.includes('AP-DEP-005')).length;
    const cycleViolations = problemSummary.clusters.filter((cluster) => cluster.ruleIds.some((ruleId) => cycleRuleIds.has(ruleId))).length;
    adjusted -= Math.min(6, publicSurfaceBypassViolations * 2);
    adjusted -= Math.min(4, cycleViolations * 2);
    const governanceRiskLevel = input.context?.governanceRiskLevel ??
        deriveGovernanceRiskLevelFromResults(input.results);
    if (governanceRiskLevel === 'critical') {
        adjusted -= 4;
    }
    else if (governanceRiskLevel === 'high') {
        adjusted -= 2;
    }
    if (input.context?.diffContextAvailable === false) {
        adjusted -= 1;
    }
    if (input.context?.enforcementAction === 'fail') {
        adjusted = Math.min(adjusted, 85);
    }
    if (publicSurfaceBypassViolations > 0) {
        adjusted = Math.min(adjusted, 72);
    }
    if (cycleViolations > 0) {
        adjusted = Math.min(adjusted, 78);
    }
    if (governanceRiskLevel === 'critical') {
        adjusted = Math.min(adjusted, 82);
    }
    const apDep004OnlyFloor = getApDep004OnlyContractDriftFloor({
        categoryScores: input.categoryScores,
        results: uniqueResults,
    });
    if (apDep004OnlyFloor !== undefined) {
        adjusted = Math.max(adjusted, apDep004OnlyFloor);
        return clampScore(Math.round(adjusted));
    }
    return clampScore(Math.round(adjusted));
}
function getApDep004OnlyContractDriftFloor(input) {
    if (!input.categoryScores) {
        return undefined;
    }
    const failedQualityResults = input.results.filter((result) => !result.passed && (result.impact ?? 'quality') === 'quality');
    const failedErrors = failedQualityResults.filter((result) => result.severity === 'error');
    if (failedErrors.length === 0 ||
        failedErrors.some((result) => result.ruleId !== 'AP-DEP-004')) {
        return undefined;
    }
    const hasSevereDependencyFailure = failedQualityResults.some((result) => typeof result.ruleId === 'string' &&
        severeDependencyRuleIds.has(result.ruleId));
    if (hasSevereDependencyFailure) {
        return undefined;
    }
    const scores = Object.values(input.categoryScores);
    if (scores.length === 0) {
        return undefined;
    }
    const categoryAverage = scores.reduce((total, score) => total + score, 0) / scores.length;
    return clampScore(Math.min(75, Math.max(65, Math.round(categoryAverage - 20))));
}
function getCategoryWeight(category) {
    if (Object.prototype.hasOwnProperty.call(exports.architectureHealthCategoryWeights, category)) {
        return exports.architectureHealthCategoryWeights[category];
    }
    return 0;
}
function calculateWeightedApplicableCategoryScore(input) {
    let weightedSum = 0;
    let applicableWeightTotal = 0;
    for (const category of input.applicableCategories) {
        const weight = getCategoryWeight(category);
        if (weight <= 0) {
            continue;
        }
        weightedSum += clampScore(input.categoryScores[category] ?? 100) * weight;
        applicableWeightTotal += weight;
    }
    if (applicableWeightTotal <= 0) {
        return 100;
    }
    return clampScore(weightedSum / applicableWeightTotal);
}
function calculateScopedScores(results, scopeKey, categories, context) {
    const grouped = new Map();
    for (const result of results) {
        const scopeId = result[scopeKey];
        if (!scopeId) {
            continue;
        }
        const existing = grouped.get(scopeId) ?? [];
        existing.push(result);
        grouped.set(scopeId, existing);
    }
    if (grouped.size === 0) {
        return undefined;
    }
    return [...grouped.entries()]
        .sort(([left], [right]) => left.localeCompare(right))
        .reduce((accumulator, [scopeId, scopedResults]) => {
        accumulator[scopeId] = calculateArchitectureHealthScore({
            results: scopedResults,
            categories,
            context,
            includeScopedScores: false,
        }).overallScore;
        return accumulator;
    }, {});
}
function calculateArchitectureHealthScore(input) {
    const applicableResults = input.results.filter((result) => result.applicability !== 'not-applicable');
    const notApplicable = input.results.length - applicableResults.length;
    const categoryOrder = input.categories && input.categories.length > 0
        ? [...input.categories]
        : [...exports.defaultArchitectureHealthCategories];
    const categoryScoreMap = new Map();
    for (const category of categoryOrder) {
        categoryScoreMap.set(category, 100);
    }
    const readinessCategoryScoreMap = new Map();
    for (const category of categoryOrder) {
        readinessCategoryScoreMap.set(category, 100);
    }
    let errors = 0;
    let warnings = 0;
    let info = 0;
    let passed = 0;
    let setupGaps = 0;
    let guidanceFindings = 0;
    let qualityFindings = 0;
    let readinessScore = 100;
    const appliedHealthDeductions = new Set();
    const appliedReadinessDeductions = new Set();
    const qualityResults = [];
    const applicableCategorySet = new Set();
    for (const result of applicableResults) {
        applicableCategorySet.add(result.category);
        if (result.passed) {
            passed += 1;
            continue;
        }
        if (result.severity === 'error') {
            errors += 1;
        }
        else if (result.severity === 'warning') {
            warnings += 1;
        }
        else {
            info += 1;
        }
        const impact = result.impact ?? 'quality';
        const deductionKey = result.deductionKey;
        const healthDeductionKey = deductionKey ? `health:${deductionKey}` : undefined;
        const readinessDeductionKey = deductionKey ? `readiness:${deductionKey}` : undefined;
        if (impact === 'setup-gap') {
            setupGaps += 1;
        }
        else if (impact === 'guidance') {
            guidanceFindings += 1;
        }
        else {
            qualityFindings += 1;
        }
        if (!categoryScoreMap.has(result.category)) {
            categoryScoreMap.set(result.category, 100);
        }
        if (!readinessCategoryScoreMap.has(result.category)) {
            readinessCategoryScoreMap.set(result.category, 100);
        }
        if (impact === 'quality') {
            qualityResults.push(result);
        }
        if (impact === 'setup-gap') {
            const shouldApplyOverall = !healthDeductionKey || !appliedHealthDeductions.has(healthDeductionKey);
            if (shouldApplyOverall) {
                const categoryScore = categoryScoreMap.get(result.category) ?? 100;
                categoryScoreMap.set(result.category, clampScore(categoryScore - getOverallSetupGapPenaltyForSeverity(result.severity)));
                if (healthDeductionKey) {
                    appliedHealthDeductions.add(healthDeductionKey);
                }
            }
            const shouldApply = !readinessDeductionKey ||
                !appliedReadinessDeductions.has(readinessDeductionKey);
            if (shouldApply) {
                const penalty = getReadinessPenaltyForSetupGapSeverity(result.severity);
                readinessScore = clampScore(readinessScore - penalty);
                const readinessCategoryScore = readinessCategoryScoreMap.get(result.category) ?? 100;
                readinessCategoryScoreMap.set(result.category, clampScore(readinessCategoryScore - penalty));
                if (readinessDeductionKey) {
                    appliedReadinessDeductions.add(readinessDeductionKey);
                }
            }
        }
    }
    for (const cluster of summarizeArchitectureScoreProblems({ results: qualityResults }).clusters) {
        const categoryScore = categoryScoreMap.get(cluster.category) ?? 100;
        categoryScoreMap.set(cluster.category, clampScore(categoryScore - cluster.penalty));
    }
    const categoryScores = [...categoryScoreMap.entries()]
        .sort((left, right) => left[0].localeCompare(right[0]))
        .reduce((accumulator, [category, score]) => {
        accumulator[category] = score;
        return accumulator;
    }, {});
    const readinessCategoryScores = [...readinessCategoryScoreMap.entries()]
        .sort((left, right) => left[0].localeCompare(right[0]))
        .reduce((accumulator, [category, score]) => {
        accumulator[category] = score;
        return accumulator;
    }, {});
    const applicableCategories = [...applicableCategorySet]
        .filter((category) => getCategoryWeight(category) > 0)
        .sort((left, right) => left.localeCompare(right));
    const weightedBaseScore = calculateWeightedApplicableCategoryScore({
        categoryScores,
        applicableCategories,
    });
    return {
        overallScore: applyFinalScoreAdjustments({
            baseScore: weightedBaseScore,
            results: applicableResults,
            categoryScores,
            context: input.context,
        }),
        categoryScores,
        applicableCategories,
        weightedBaseScore: Math.round(weightedBaseScore),
        ...(input.includeScopedScores === false
            ? {}
            : {
                componentScores: calculateScopedScores(applicableResults, 'componentId', input.categories, input.context),
                moduleScores: calculateScopedScores(applicableResults, 'moduleId', input.categories, input.context),
            }),
        readinessScore,
        readinessCategoryScores,
        totalChecks: applicableResults.length,
        notApplicable,
        errors,
        warnings,
        info,
        passed,
        setupGaps,
        guidanceFindings,
        qualityFindings,
    };
}
//# sourceMappingURL=architectureHealthScore.js.map