// engine/performance-analyzer.ts — SPEC-300: Detect performance risks in spec text
//
// Analyzes spec content for common performance anti-patterns using keyword matching.
// Detection is intentionally conservative: negate keywords prevent false positives.
// ---------------------------------------------------------------------------
// Risk pattern definitions
// ---------------------------------------------------------------------------
export const RISK_PATTERNS = [
    {
        category: 'n-plus-one',
        severity: 'high',
        triggerKeywords: [
            'for each',
            'per user',
            'per item',
            'loop through',
            'every record',
            'for every',
            'iterate over all',
        ],
        negateIfPresent: ['batch', 'bulk', 'single query'],
        description: 'Spec implies iterating over records with per-record DB queries, creating N+1 query risk.',
        recommendation: 'Use batch/bulk queries or eager loading to fetch related data in a single query.',
    },
    {
        category: 'large-data',
        severity: 'high',
        triggerKeywords: [
            'import all',
            'export all',
            'bulk import',
            'migrate all',
            'all records',
            'full table',
            'entire dataset',
        ],
        negateIfPresent: ['paginated', 'chunked', 'streaming'],
        description: 'Spec involves loading or transferring large volumes of data without chunking or streaming.',
        recommendation: 'Process data in paginated chunks or use streaming to avoid memory exhaustion and timeouts.',
    },
    {
        category: 'missing-pagination',
        severity: 'medium',
        triggerKeywords: ['list all', 'get all', 'fetch all', 'return all', 'show all'],
        negateIfPresent: ['page', 'limit', 'cursor', 'paginate', 'offset'],
        description: 'Spec returns unbounded lists of records without pagination, risking slow responses at scale.',
        recommendation: 'Add page/limit or cursor-based pagination to all list endpoints from the start.',
    },
    {
        category: 'missing-cache',
        severity: 'medium',
        triggerKeywords: ['real-time', 'always fresh', 'live data', 'no cache', 'every request'],
        negateIfPresent: ['cache', 'ttl', 'redis', 'memoize', 'cdn'],
        description: 'Spec requires fresh data on every request with no caching strategy, risking high DB load.',
        recommendation: 'Define an appropriate TTL or cache invalidation strategy (Redis, CDN, in-memory).',
    },
    {
        category: 'blocking-ops',
        severity: 'medium',
        triggerKeywords: ['synchronous', 'blocking call', 'wait for response', 'sequential processing'],
        negateIfPresent: ['async', 'queue', 'background job', 'worker', 'non-blocking'],
        description: 'Spec describes synchronous blocking operations that hold threads and degrade throughput.',
        recommendation: 'Move heavy operations to async queues or background workers to keep the request thread free.',
    },
    {
        category: 'missing-index',
        severity: 'low',
        triggerKeywords: ['filter by', 'search by', 'sort by', 'query by', 'find by'],
        negateIfPresent: ['indexed', 'index on', 'composite index', 'b-tree'],
        description: 'Spec queries data by fields that may lack database indexes, causing full-table scans.',
        recommendation: 'Add database indexes on all filter/sort/search columns mentioned in acceptance criteria.',
    },
];
// ---------------------------------------------------------------------------
// Core analysis functions
// ---------------------------------------------------------------------------
/**
 * Analyze text against the risk patterns and return all detected risks.
 * Each pattern is checked independently; negate keywords suppress a match.
 */
export function analyzeText(text, patterns) {
    const lower = text.toLowerCase();
    const risks = [];
    for (const pattern of patterns) {
        const triggered = pattern.triggerKeywords.some((kw) => lower.includes(kw.toLowerCase()));
        if (!triggered) {
            continue;
        }
        const negated = (pattern.negateIfPresent ?? []).some((kw) => lower.includes(kw.toLowerCase()));
        if (negated) {
            continue;
        }
        risks.push({
            category: pattern.category,
            severity: pattern.severity,
            description: pattern.description,
            recommendation: pattern.recommendation,
        });
    }
    return risks;
}
/**
 * Compute overall risk level from a list of detected risks.
 * 'high' if any high-severity, 'medium' if any medium, 'low' if any low, 'none' if empty.
 */
export function computeOverallRisk(risks) {
    if (risks.length === 0) {
        return 'none';
    }
    if (risks.some((r) => r.severity === 'high')) {
        return 'high';
    }
    if (risks.some((r) => r.severity === 'medium')) {
        return 'medium';
    }
    return 'low';
}
/**
 * Analyze a spec for performance risks.
 * Combines spec title with the raw markdown content for analysis.
 */
export function analyzeSpecPerformance(spec, specContent) {
    const combinedText = `${spec.title} ${specContent}`;
    const risks = analyzeText(combinedText, RISK_PATTERNS);
    // Group by category
    const risksByCategory = {};
    const allCategories = [
        'n-plus-one',
        'large-data',
        'missing-pagination',
        'missing-cache',
        'blocking-ops',
        'missing-index',
    ];
    for (const cat of allCategories) {
        risksByCategory[cat] = [];
    }
    for (const risk of risks) {
        risksByCategory[risk.category].push(risk);
    }
    const overallRisk = computeOverallRisk(risks);
    // Deduplicate recommendations
    const recommendations = [...new Set(risks.map((r) => r.recommendation))];
    return {
        specId: spec.id,
        riskCount: risks.length,
        risksByCategory,
        overallRisk,
        recommendations,
    };
}
//# sourceMappingURL=performance-analyzer.js.map