"use strict";
/**
 * Request Builders for AI Validation
 *
 * Functions for building validation requests with full or scoped file context.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildHighContextValidationRequest = buildHighContextValidationRequest;
exports.buildMultiFileValidationRequest = buildMultiFileValidationRequest;
exports.buildFileTaintSummary = buildFileTaintSummary;
exports.buildFindingTaintAnnotation = buildFindingTaintAnnotation;
exports.buildFindingRouteAnnotation = buildFindingRouteAnnotation;
exports.buildFileRouteSummary = buildFileRouteSummary;
exports.buildCrossFileTaintAnnotation = buildCrossFileTaintAnnotation;
exports.buildFindingFrameworkAnnotation = buildFindingFrameworkAnnotation;
const project_context_1 = require("../model/project-context");
const utils_1 = require("../model/route-discovery/utils");
const path_helpers_1 = require("./utils/path-helpers");
const context_extractor_1 = require("./utils/context-extractor");
// ============================================================================
// Single-File Request Builder
// ============================================================================
/**
 * Build a high-context validation request with full or scoped file content
 */
function buildHighContextValidationRequest(file, findings, projectContext, options, fileTaintAnalyses, routeMap, crossFileTaintPaths, frameworkAnalyses) {
    const mode = options?.contextMode ?? 'scoped';
    // Get file content (full or scoped)
    const { numberedContent, contextNote } = getFileContent(file, findings, mode, options?.extractionConfig);
    // Build file-level taint summary
    const taintSummary = fileTaintAnalyses ? buildFileTaintSummary(file.path, fileTaintAnalyses) : '';
    // Build file-level route summary
    const routeSummary = routeMap ? buildFileRouteSummary(file.path, routeMap) : '';
    // Build candidate findings list with taint, route, and framework annotations
    const candidatesText = findings.map((f, idx) => {
        const taintAnnotation = fileTaintAnalyses
            ? buildFindingTaintAnnotation(f, file.path, fileTaintAnalyses)
            : '';
        const crossFileAnnotation = crossFileTaintPaths
            ? buildCrossFileTaintAnnotation(f, file.path, crossFileTaintPaths)
            : '';
        const routeAnnotation = routeMap
            ? buildFindingRouteAnnotation(f, file.path, routeMap)
            : '';
        const frameworkAnnotation = frameworkAnalyses
            ? buildFindingFrameworkAnnotation(f, file.path, frameworkAnalyses)
            : '';
        return `### Candidate ${idx}
- **Rule**: ${f.title}
- **Category**: ${f.category}
- **Original Severity**: ${f.severity}
- **Line**: ${f.lineNumber}
- **Detection Layer**: ${f.layer}
- **Description**: ${f.description}
- **Flagged Code**: \`${f.lineContent.trim()}\`${taintAnnotation}${crossFileAnnotation}${routeAnnotation}${frameworkAnnotation}`;
    }).join('\n\n');
    // Get file-specific context
    const fileContext = (0, project_context_1.getFileValidationContext)(file, projectContext);
    return `## Project Context
${projectContext.summary}

${fileContext}
${taintSummary}${routeSummary}
## Full File Content${contextNote}
\`\`\`${file.language || (0, path_helpers_1.getLanguageFromPath)(file.path)}
${numberedContent}
\`\`\`

## Candidate Findings to Validate (${findings.length} total)

${candidatesText}

---

Please validate each candidate finding. Return a JSON array with your decision for each.
Remember: Be AGGRESSIVE in rejecting false positives. Use the full file context and project architecture to make informed decisions.`;
}
// ============================================================================
// Multi-File Request Builder (Phase 2 Optimization)
// ============================================================================
/**
 * Build a multi-file validation request (Phase 2 optimization)
 * Batches multiple files into a single API call to reduce overhead
 */
function buildMultiFileValidationRequest(fileDataList, projectContext, options, fileTaintAnalyses, routeMap, crossFileTaintPaths, frameworkAnalyses) {
    const mode = options?.contextMode ?? 'scoped';
    const filesContent = fileDataList.map(({ file, findings }, fileIndex) => {
        // Get file content (full or scoped)
        const { numberedContent, contextNote } = getFileContent(file, findings, mode, options?.extractionConfig);
        // Build file-level taint summary
        const taintSummary = fileTaintAnalyses ? buildFileTaintSummary(file.path, fileTaintAnalyses) : '';
        // Build file-level route summary
        const routeSummary = routeMap ? buildFileRouteSummary(file.path, routeMap) : '';
        // Build candidate findings list with file-specific indices, taint, route, and framework annotations
        const candidatesText = findings.map((f, idx) => {
            const taintAnnotation = fileTaintAnalyses
                ? buildFindingTaintAnnotation(f, file.path, fileTaintAnalyses)
                : '';
            const crossFileAnnotation = crossFileTaintPaths
                ? buildCrossFileTaintAnnotation(f, file.path, crossFileTaintPaths)
                : '';
            const routeAnnotation = routeMap
                ? buildFindingRouteAnnotation(f, file.path, routeMap)
                : '';
            const frameworkAnnotation = frameworkAnalyses
                ? buildFindingFrameworkAnnotation(f, file.path, frameworkAnalyses)
                : '';
            return `### Candidate ${idx}
- **Rule**: ${f.title}
- **Category**: ${f.category}
- **Original Severity**: ${f.severity}
- **Line**: ${f.lineNumber}
- **Detection Layer**: ${f.layer}
- **Description**: ${f.description}
- **Flagged Code**: \`${f.lineContent.trim()}\`${taintAnnotation}${crossFileAnnotation}${routeAnnotation}${frameworkAnnotation}`;
        }).join('\n\n');
        // Get file-specific context
        const fileContext = (0, project_context_1.getFileValidationContext)(file, projectContext);
        return `
================================================================================
FILE ${fileIndex + 1}: ${file.path}
================================================================================

${fileContext}
${taintSummary}${routeSummary}
### Full File Content${contextNote}
\`\`\`${file.language || (0, path_helpers_1.getLanguageFromPath)(file.path)}
${numberedContent}
\`\`\`

### Candidate Findings to Validate (${findings.length} total)

${candidatesText}`;
    }).join('\n\n');
    return `## Project Context
${projectContext.summary}

${filesContent}

---

## Response Format

For EACH file, provide a JSON object with the file path and validation results.
Return a JSON array where each element has:
- "file": the file path (e.g., "${fileDataList[0]?.file.path || 'path/to/file.ts'}")
- "validations": array of validation results for that file's candidates

Example response format:
\`\`\`json
[
  {
    "file": "src/auth.ts",
    "validations": [
      { "index": 0, "keep": true, "adjustedSeverity": "medium", "notes": "Protected by middleware" },
      { "index": 1, "keep": false }
    ]
  },
  {
    "file": "src/api.ts",
    "validations": [
      { "index": 0, "keep": true, "notes": "User input flows to SQL query" }
    ]
  }
]
\`\`\`

Remember: Be AGGRESSIVE in rejecting false positives. Use the full file context and project architecture to make informed decisions.`;
}
// ============================================================================
// Annotation Helpers
// ============================================================================
/**
 * Build a file-level taint summary for the AI prompt.
 * Returns empty string if no taint analysis exists for the file.
 */
function buildFileTaintSummary(filePath, fileTaintAnalyses) {
    const analysis = fileTaintAnalyses.get(filePath);
    if (!analysis || analysis.sources.length === 0)
        return '';
    const sourceTypes = [...new Set(analysis.sources.map(s => s.sourceType))].join(', ');
    const totalPaths = analysis.taintPaths.length;
    const unsanitised = analysis.taintPaths.filter(p => !p.sanitised).length;
    const sanitiserNames = [...new Set(analysis.sanitisers.map(s => s.name))];
    const lines = [
        `### Data Flow Analysis`,
        `- User input sources: ${sourceTypes}`,
        `- Taint paths: ${totalPaths} (${unsanitised} unsanitised)`,
    ];
    if (sanitiserNames.length > 0) {
        lines.push(`- Sanitisers detected: ${sanitiserNames.join(', ')}`);
    }
    return lines.join('\n') + '\n';
}
/**
 * Build a per-finding taint annotation for the AI prompt.
 * Returns a string to append after the "Flagged Code" line.
 */
function buildFindingTaintAnnotation(finding, filePath, fileTaintAnalyses) {
    const analysis = fileTaintAnalyses.get(filePath);
    if (!analysis)
        return '';
    // Check if any taint path has a sink at the finding's line number
    const matchingPaths = analysis.taintPaths.filter(tp => tp.sink.line === finding.lineNumber);
    if (matchingPaths.length > 0) {
        // Pick the best path (unsanitised first, then highest confidence)
        const bestPath = matchingPaths.find(p => !p.sanitised) ?? matchingPaths[0];
        const chainStr = bestPath.chain
            .map(v => `${v.name}(L${v.taintedAt})`)
            .join(' → ');
        const fullChain = chainStr
            ? `${bestPath.source.variable}(L${bestPath.source.line}) → ${chainStr} → sink(L${bestPath.sink.line})`
            : `${bestPath.source.variable}(L${bestPath.source.line}) → sink(L${bestPath.sink.line})`;
        const lines = [
            `\n- **Taint Analysis**: User input reaches this sink`,
            `  - Source: \`${bestPath.source.expression}\` (${bestPath.source.sourceType}, L${bestPath.source.line})`,
            `  - Chain: ${fullChain}`,
            `  - Sink type: ${bestPath.sink.sinkType}`,
            `  - Sanitised: ${bestPath.sanitised ? 'Yes' : 'No'}`,
            `  - Confidence: ${bestPath.confidence}`,
        ];
        return lines.join('\n');
    }
    // File has taint analysis with sources but no path reaches this finding's line
    if (analysis.sources.length > 0) {
        return '\n- **Taint Analysis**: No user-input data flow reaches this line';
    }
    return '';
}
// ============================================================================
// Route Annotation Helpers
// ============================================================================
/**
 * Build a per-finding route annotation for the AI prompt.
 * Returns a string to append after taint annotations.
 */
function buildFindingRouteAnnotation(finding, filePath, routeMap) {
    const fileRoutes = routeMap.fileToRoutes.get(filePath);
    if (!fileRoutes || fileRoutes.length === 0)
        return '';
    // Find the closest route to the finding's line
    const route = (0, utils_1.findClosestRoute)(fileRoutes, finding.lineNumber);
    if (!route)
        return '';
    const framework = route.framework.replace(/_/g, ' ');
    const methods = route.methods.join(', ');
    const authStr = route.authMiddleware.length > 0
        ? route.authMiddleware.join(', ')
        : 'NONE';
    const rateLimitStr = route.rateLimiting ? 'Yes' : 'NONE';
    const lines = [
        `\n- **Route Context:**`,
        `  - Handler: ${framework} ${methods} \`${route.path}\``,
        `  - Auth middleware: ${authStr}`,
        `  - Rate limiting: ${rateLimitStr}`,
    ];
    if (route.isPublic) {
        lines.push(`  - Public endpoint: Yes`);
    }
    return lines.join('\n');
}
/**
 * Build a file-level route summary for the AI prompt.
 * Returns empty string if no routes exist for the file.
 */
function buildFileRouteSummary(filePath, routeMap) {
    const fileRoutes = routeMap.fileToRoutes.get(filePath);
    if (!fileRoutes || fileRoutes.length === 0)
        return '';
    const routeDescriptions = fileRoutes.map(r => {
        const methods = r.methods.join('|');
        const auth = r.authMiddleware.length > 0 ? ` [auth: ${r.authMiddleware.join(', ')}]` : ' [no auth]';
        const rateLimit = r.rateLimiting ? ' [rate-limited]' : '';
        const pub = r.isPublic ? ' [public]' : '';
        return `  - ${methods} \`${r.path}\` (L${r.handlerLine})${auth}${rateLimit}${pub}`;
    });
    return `### Route Map\n${routeDescriptions.join('\n')}\n`;
}
// ============================================================================
// Cross-File Taint Annotation Helpers
// ============================================================================
/**
 * Build a per-finding cross-file taint annotation for the AI prompt.
 * Appended after intra-file taint annotations when cross-file paths exist.
 */
function buildCrossFileTaintAnnotation(finding, filePath, crossFileTaintPaths) {
    // Check if any cross-file taint path has a sink at the finding's line
    const matchingPaths = crossFileTaintPaths.filter(cfp => (cfp.sinkFile === filePath && cfp.sink.line === finding.lineNumber) ||
        (cfp.sourceFile === filePath && cfp.importLink.callLine === finding.lineNumber));
    if (matchingPaths.length === 0)
        return '';
    const bestPath = matchingPaths.find(p => !p.sanitised) ?? matchingPaths[0];
    const lines = [
        `\n- **Cross-File Taint**: User input crosses file boundary`,
        `  - Source: \`${bestPath.source.expression}\` in \`${bestPath.sourceFile}\` (L${bestPath.source.line})`,
        `  - Via: \`${bestPath.importLink.symbolName}()\` imported from \`${bestPath.importLink.toFile}\``,
        `  - Sink: ${bestPath.sink.sinkType} in \`${bestPath.sinkFile}\` (L${bestPath.sink.line})`,
        `  - Sanitised: ${bestPath.sanitised ? 'Yes' : 'No'}`,
    ];
    return lines.join('\n');
}
// ============================================================================
// Framework Annotation Helpers
// ============================================================================
/**
 * Build a per-finding framework annotation for the AI prompt.
 * Returns a string to append after route annotations.
 */
function buildFindingFrameworkAnnotation(finding, filePath, frameworkAnalyses) {
    const analysis = frameworkAnalyses.get(filePath);
    if (!analysis)
        return '';
    const safePatterns = analysis.safeLines.get(finding.lineNumber);
    const unsafePatterns = analysis.unsafeLines.get(finding.lineNumber);
    if (!safePatterns?.length && !unsafePatterns?.length)
        return '';
    const lines = [];
    if (safePatterns) {
        for (const pattern of safePatterns) {
            lines.push(`\n- **Framework Context**: ${pattern.reason}`);
        }
    }
    if (unsafePatterns) {
        for (const pattern of unsafePatterns) {
            lines.push(`\n- **Framework Context**: ${pattern.reason}`);
        }
    }
    return lines.join('');
}
function getFileContent(file, findings, mode, extractionConfig) {
    if (mode === 'full') {
        const numberedContent = file.content
            .split('\n')
            .map((line, i) => `${String(i + 1).padStart(4, ' ')} | ${line}`)
            .join('\n');
        return { numberedContent, contextNote: '' };
    }
    // Scoped mode: extract relevant regions
    const findingLineNumbers = findings.map(f => f.lineNumber);
    const extracted = (0, context_extractor_1.extractRelevantContext)(file.content, findingLineNumbers, extractionConfig);
    const contextNote = extracted.isFullFile
        ? ''
        : ` (Showing ${extracted.linesSent} of ${extracted.totalLines} lines — relevant regions around findings)`;
    return { numberedContent: extracted.content, contextNote };
}
//# sourceMappingURL=request-builder.js.map