"use strict";
/**
 * Anthropic Provider Implementation
 *
 * Validation using Anthropic Claude 3.5 Haiku model.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateWithAnthropic = validateWithAnthropic;
const project_context_1 = require("../../model/project-context");
const clients_1 = require("../clients");
const retry_1 = require("../utils/retry");
const response_parser_1 = require("../utils/response-parser");
const request_builder_1 = require("../request-builder");
const validation_1 = require("../prompts/validation");
/**
 * Validate findings using Anthropic Claude 3.5 Haiku
 */
async function validateWithAnthropic(findings, files, ceResult, stats, onProgress) {
    console.log('[AI Validation] Initializing Anthropic client...');
    const client = (0, clients_1.getAnthropicClient)();
    // Extract project context from CE result or build from scratch
    const context = ceResult?.projectContext ?? (0, project_context_1.buildProjectContext)(files);
    const fileTaintAnalyses = ceResult?.fileTaintAnalyses;
    const routeMap = ceResult?.routeMap;
    const frameworkAnalyses = ceResult?.frameworkAnalyses;
    if (!ceResult) {
        console.log('[AI Validation] No ContextEngineResult provided, built project context from scratch:', {
            hasAuthMiddleware: context.auth.hasGlobalMiddleware,
            authProvider: context.auth.authProvider,
            orm: context.dataAccess.orm,
            framework: context.frameworks.primary,
        });
    }
    // Group findings by file for efficient validation
    const findingsByFile = new Map();
    for (const finding of findings) {
        const existing = findingsByFile.get(finding.filePath) || [];
        existing.push(finding);
        findingsByFile.set(finding.filePath, existing);
    }
    const validatedFindings = [];
    // Phase 2: Multi-file batching
    const fileEntries = Array.from(findingsByFile.entries());
    // Track metrics
    let totalBatchWaitTime = 0;
    let totalApiBatches = 0;
    const totalFileBatches = Math.ceil(fileEntries.length / clients_1.FILES_PER_API_BATCH);
    console.log(`[AI Validation] Phase 2: Processing ${fileEntries.length} files in ${totalFileBatches} API batch(es) (${clients_1.FILES_PER_API_BATCH} files/batch)`);
    // Track files processed for progress reporting
    let filesValidated = 0;
    // Process files in batches - each batch is ONE API call with multiple files
    for (let batchStart = 0; batchStart < fileEntries.length; batchStart += clients_1.FILES_PER_API_BATCH) {
        const fileBatch = fileEntries.slice(batchStart, batchStart + clients_1.FILES_PER_API_BATCH);
        const batchNum = Math.floor(batchStart / clients_1.FILES_PER_API_BATCH) + 1;
        // Report progress before processing batch
        if (onProgress) {
            onProgress({
                filesProcessed: filesValidated,
                totalFiles: fileEntries.length,
                status: `AI validating batch ${batchNum}/${totalFileBatches}`,
            });
        }
        console.log(`[AI Validation] API Batch ${batchNum}/${totalFileBatches}: ${fileBatch.length} files`);
        // Prepare file data for batch request
        const fileDataList = [];
        const filesWithoutContent = [];
        for (const [filePath, fileFindings] of fileBatch) {
            const file = files.find(f => f.path === filePath);
            if (!file) {
                filesWithoutContent.push({ filePath, findings: fileFindings });
            }
            else {
                fileDataList.push({ file, findings: fileFindings, filePath });
            }
        }
        // Handle files without content - mark as not validated
        for (const { findings } of filesWithoutContent) {
            for (const f of findings) {
                validatedFindings.push({
                    ...f,
                    validatedByAI: false,
                    validationStatus: 'not_validated',
                    validationNotes: 'File content not available for validation',
                });
            }
        }
        // Skip API call if no files with content
        if (fileDataList.length === 0) {
            continue;
        }
        const batchStartTime = Date.now();
        try {
            // Build multi-file validation request with scoped context, taint data, and route map
            const validationRequest = (0, request_builder_1.buildMultiFileValidationRequest)(fileDataList.map(({ file, findings }) => ({ file, findings })), context, { contextMode: 'scoped' }, fileTaintAnalyses, routeMap, ceResult?.crossFileTaintPaths, frameworkAnalyses);
            // Assemble category-aware prompt for this batch
            const batchCategories = [...new Set(fileBatch.flatMap(([, fileFindings]) => fileFindings.map(f => f.category)))];
            const systemPrompt = (0, validation_1.assembleValidationPrompt)(batchCategories);
            // Use Anthropic prompt caching with multi-file request
            const response = await (0, retry_1.makeAnthropicRequestWithRetry)(() => client.messages.create({
                model: 'claude-3-5-haiku-20241022',
                max_tokens: 16384, // Must accommodate 8 files × multiple findings with full validation fields
                system: [
                    {
                        type: 'text',
                        text: systemPrompt,
                        cache_control: { type: 'ephemeral' }, // Cache for 5 minutes
                    },
                ],
                messages: [{ role: 'user', content: validationRequest }],
            }));
            // Track API call stats
            stats.apiCalls++;
            totalApiBatches++;
            // Extract cache metrics from usage
            const usage = response.usage;
            if (usage) {
                // DEBUG: Log full usage object to understand token breakdown
                console.log(`[DEBUG] Batch ${batchNum} - Full API Response Usage:`);
                console.log(JSON.stringify(usage, null, 2));
                console.log(`[DEBUG] Breakdown:`);
                console.log(`  - input_tokens: ${usage.input_tokens || 0}`);
                console.log(`  - output_tokens: ${usage.output_tokens || 0}`);
                // @ts-ignore
                console.log(`  - cache_creation_input_tokens: ${usage.cache_creation_input_tokens || 0}`);
                // @ts-ignore
                console.log(`  - cache_read_input_tokens: ${usage.cache_read_input_tokens || 0}`);
                stats.estimatedInputTokens += usage.input_tokens || 0;
                stats.estimatedOutputTokens += usage.output_tokens || 0;
                // @ts-ignore - cache fields not in types yet
                const cacheCreation = usage.cache_creation_input_tokens || 0;
                // @ts-ignore
                const cacheRead = usage.cache_read_input_tokens || 0;
                stats.cacheCreationTokens += cacheCreation;
                stats.cacheReadTokens += cacheRead;
            }
            const textContent = response.content.find((block) => block.type === 'text');
            if (!textContent || textContent.type !== 'text') {
                // No valid response - mark all findings as not validated
                for (const { findings } of fileDataList) {
                    for (const f of findings) {
                        validatedFindings.push({
                            ...f,
                            validatedByAI: false,
                            validationStatus: 'not_validated',
                            validationNotes: 'No valid response from AI',
                        });
                    }
                }
                continue;
            }
            // Parse multi-file response
            const expectedFiles = fileDataList.map(({ filePath }) => filePath);
            const validationResultsMap = (0, response_parser_1.parseMultiFileValidationResponse)(textContent.text, expectedFiles);
            // Apply results per file
            for (const { filePath, findings } of fileDataList) {
                const fileResults = validationResultsMap.get(filePath);
                if (!fileResults || fileResults.length === 0) {
                    // No results for this file - try single-file parsing as fallback
                    const singleFileResults = (0, response_parser_1.parseValidationResponse)(textContent.text);
                    if (singleFileResults.length > 0 && fileDataList.length === 1) {
                        // Single file in batch, use single-file parsing
                        const { processed: processedFindings, dismissedCount } = (0, response_parser_1.applyValidationResults)(findings, singleFileResults);
                        stats.validatedFindings += processedFindings.length + dismissedCount;
                        stats.dismissedFindings += dismissedCount;
                        for (const processed of processedFindings) {
                            if (processed.validationStatus === 'confirmed') {
                                stats.confirmedFindings++;
                            }
                            else if (processed.validationStatus === 'downgraded') {
                                stats.downgradedFindings++;
                            }
                            validatedFindings.push(processed);
                        }
                    }
                    else {
                        // No validation results - REJECT all findings for this file (conservative approach)
                        console.warn(`[AI Validation] No results for ${filePath} - REJECTING ${findings.length} findings`);
                        stats.validatedFindings += findings.length;
                        stats.dismissedFindings += findings.length;
                        // Don't add to validatedFindings - findings are rejected
                    }
                }
                else {
                    // Apply validation results for this file
                    const { processed: processedFindings, dismissedCount } = (0, response_parser_1.applyValidationResults)(findings, fileResults);
                    stats.validatedFindings += processedFindings.length + dismissedCount;
                    stats.dismissedFindings += dismissedCount;
                    for (const processed of processedFindings) {
                        if (processed.validationStatus === 'confirmed') {
                            stats.confirmedFindings++;
                        }
                        else if (processed.validationStatus === 'downgraded') {
                            stats.downgradedFindings++;
                        }
                        validatedFindings.push(processed);
                    }
                }
            }
        }
        catch (error) {
            console.error(`[AI Validation] Error in batch ${batchNum}:`, error);
            // Fallback: keep all findings but mark as not validated
            for (const { findings } of fileDataList) {
                for (const f of findings) {
                    validatedFindings.push({
                        ...f,
                        validatedByAI: false,
                        validationStatus: 'not_validated',
                        validationNotes: 'Validation failed due to API error',
                    });
                }
            }
        }
        const batchDuration = Date.now() - batchStartTime;
        totalBatchWaitTime += batchDuration;
        // Update files validated counter
        filesValidated += fileBatch.length;
        // Report progress after batch completion
        if (onProgress) {
            onProgress({
                filesProcessed: filesValidated,
                totalFiles: fileEntries.length,
                status: `AI validation complete for batch ${batchNum}/${totalFileBatches}`,
            });
        }
    }
    // Calculate cache hit rate
    const totalCacheableTokens = stats.cacheCreationTokens + stats.cacheReadTokens;
    stats.cacheHitRate = totalCacheableTokens > 0
        ? stats.cacheReadTokens / totalCacheableTokens
        : 0;
    // Calculate estimated cost with cache pricing
    const freshInputCost = (stats.estimatedInputTokens * clients_1.HAIKU_PRICING.input) / 1000000;
    const cacheWriteCost = (stats.cacheCreationTokens * clients_1.HAIKU_PRICING.cacheWrite) / 1000000;
    const cacheReadCost = (stats.cacheReadTokens * clients_1.HAIKU_PRICING.cacheRead) / 1000000;
    const outputCost = (stats.estimatedOutputTokens * clients_1.HAIKU_PRICING.output) / 1000000;
    stats.estimatedCost = freshInputCost + cacheWriteCost + cacheReadCost + outputCost;
    // Log validation stats with cache metrics and performance
    console.log(`[AI Validation] Stats:`);
    console.log(`  - Total findings: ${stats.totalFindings}`);
    console.log(`  - AI validated: ${stats.validatedFindings}`);
    console.log(`  - Confirmed: ${stats.confirmedFindings}`);
    console.log(`  - Dismissed: ${stats.dismissedFindings}`);
    console.log(`  - Downgraded: ${stats.downgradedFindings}`);
    console.log(`  - API calls: ${stats.apiCalls}`);
    console.log(`  - Performance:`);
    console.log(`    - Total duration: ${(totalBatchWaitTime / 1000).toFixed(1)}s`);
    console.log(`    - Total API batches: ${totalApiBatches}`);
    console.log(`    - Avg time per file: ${fileEntries.length > 0 ? (totalBatchWaitTime / fileEntries.length).toFixed(0) : 0}ms`);
    console.log(`  - Cache metrics:`);
    console.log(`    - Cache writes: ${stats.cacheCreationTokens.toLocaleString()} tokens`);
    console.log(`    - Cache reads: ${stats.cacheReadTokens.toLocaleString()} tokens`);
    console.log(`    - Cache hit rate: ${(stats.cacheHitRate * 100).toFixed(1)}%`);
    console.log(`  - Token usage:`);
    console.log(`    - Input (total): ${stats.estimatedInputTokens.toLocaleString()} tokens`);
    console.log(`    - Output: ${stats.estimatedOutputTokens.toLocaleString()} tokens`);
    console.log(`  - Estimated cost: $${stats.estimatedCost.toFixed(4)}`);
    return { vulnerabilities: validatedFindings, stats };
}
//# sourceMappingURL=anthropic.js.map