"use strict";
/**
 * Project Model Builder — Assembles the full project model from source files.
 *
 * Orchestrates middleware detection, auth imports, project context, and
 * detector context into a single ProjectModel used by the scan pipeline.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildProjectModel = buildProjectModel;
exports.buildDetectorContext = buildDetectorContext;
exports.buildFileDetectorContext = buildFileDetectorContext;
exports.isConfigFile = isConfigFile;
const types_1 = require("../shared/types");
const project_context_1 = require("./project-context");
const middleware_detector_1 = require("./middleware-detector");
const imported_auth_detector_1 = require("./imported-auth-detector");
const file_classifier_1 = require("../parse/file-classifier");
const source_discovery_1 = require("./source-discovery");
const sanitiser_detection_1 = require("./sanitiser-detection");
const taint_tracker_1 = require("./taint-tracker");
const sink_matcher_1 = require("./sink-matcher");
const route_discovery_1 = require("./route-discovery");
const route_auth_resolver_1 = require("./route-auth-resolver");
const module_graph_1 = require("./module-graph");
const function_classifier_1 = require("./function-classifier");
const cross_file_taint_1 = require("./cross-file-taint");
const framework_models_1 = require("./framework-models");
// ============================================================================
// Project Model Builder
// ============================================================================
/**
 * Build the full project model from source files.
 * Detects auth middleware, file auth imports, project context, and detector context.
 */
function buildProjectModel(files) {
    const middlewareConfig = (0, middleware_detector_1.detectGlobalAuthMiddleware)(files);
    const fileAuthImports = (0, imported_auth_detector_1.buildFileAuthImports)(files);
    const projectContext = (0, project_context_1.buildProjectContext)(files);
    const detectorContext = buildDetectorContext(projectContext, middlewareConfig);
    const { analyses: fileTaintAnalyses, stats: taintStats } = buildFileTaintAnalyses(files);
    // CE Phase 4: Module Graph & Cross-File Analysis
    const moduleGraph = (0, module_graph_1.buildModuleGraph)(files);
    const functionProfiles = (0, function_classifier_1.buildFunctionProfiles)(files, moduleGraph);
    const crossFileTaintPaths = (0, cross_file_taint_1.analyzeCrossFileTaint)(files, moduleGraph, functionProfiles, fileTaintAnalyses);
    // CE Phase 2: Route Discovery (uses module graph for cross-file auth)
    const routeStart = Date.now();
    const { routeMap: rawRouteMap, mountPoints } = (0, route_discovery_1.discoverRoutes)(files, projectContext.frameworks.primary);
    const routeMap = (0, route_auth_resolver_1.resolveRouteAuth)(rawRouteMap, mountPoints, files, middlewareConfig, moduleGraph);
    const routeDuration = Date.now() - routeStart;
    const routeStats = buildRouteStats(routeMap, routeDuration);
    // CE Phase 5: Framework-Aware Analysis
    const frameworkAnalyses = (0, framework_models_1.analyzeFrameworkPatterns)(files, projectContext);
    const contextEngine = {
        projectContext,
        fileTaintAnalyses,
        routeMap,
        moduleGraph,
        crossFileTaintPaths,
        functionProfiles,
        frameworkAnalyses,
    };
    return {
        contextEngine,
        middlewareConfig,
        fileAuthImports,
        detectorContext,
        taintStats,
        routeStats,
        moduleGraphStats: moduleGraph.stats,
    };
}
// ============================================================================
// Route Discovery Stats (Context Engine Phase 2)
// ============================================================================
function buildRouteStats(routeMap, duration) {
    const routes = routeMap.routes;
    const authedRoutes = routes.filter(r => r.authMiddleware.length > 0).length;
    const publicRoutes = routes.filter(r => r.isPublic).length;
    const unprotectedRoutes = routes.filter(r => r.authMiddleware.length === 0 && !r.isPublic).length;
    const frameworksDetected = [...new Set(routes.map(r => r.framework))];
    return {
        totalRoutes: routes.length,
        authedRoutes,
        publicRoutes,
        unprotectedRoutes,
        frameworksDetected,
        duration,
    };
}
// ============================================================================
// Taint Analysis (Context Engine Phase 1)
// ============================================================================
const CODE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.php', '.go', '.java', '.cs']);
function isCodeFile(filePath) {
    const ext = filePath.substring(filePath.lastIndexOf('.'));
    return CODE_EXTENSIONS.has(ext.toLowerCase());
}
function analyzeFileTaint(content, filePath) {
    const sources = (0, source_discovery_1.discoverSources)(content, filePath);
    if (sources.length === 0) {
        return { filePath, sources: [], taintedVariables: [], taintPaths: [], sanitisers: [] };
    }
    const sanitisers = (0, sanitiser_detection_1.detectSanitisers)(content, filePath);
    const taintedVariables = (0, taint_tracker_1.propagateTaint)(content, filePath, sources, sanitisers);
    const taintPaths = (0, sink_matcher_1.matchSinks)(content, filePath, taintedVariables, sanitisers);
    return { filePath, sources, taintedVariables, taintPaths, sanitisers };
}
function buildFileTaintAnalyses(files) {
    const startTime = Date.now();
    const analyses = new Map();
    const sourceTypeBreakdown = {};
    const sinkTypeBreakdown = {};
    let filesAnalyzed = 0;
    let totalSources = 0;
    let totalTaintedVars = 0;
    let totalTaintPaths = 0;
    let unsanitisedPaths = 0;
    let sanitisedPaths = 0;
    let totalSanitisers = 0;
    for (const file of files) {
        if (!isCodeFile(file.path) || (0, file_classifier_1.isTestOrMockFile)(file.path))
            continue;
        filesAnalyzed++;
        const analysis = analyzeFileTaint(file.content, file.path);
        if (analysis.sources.length > 0 || analysis.taintPaths.length > 0) {
            analyses.set(file.path, analysis);
        }
        // Accumulate stats
        totalSources += analysis.sources.length;
        totalTaintedVars += analysis.taintedVariables.length;
        totalTaintPaths += analysis.taintPaths.length;
        totalSanitisers += analysis.sanitisers.length;
        for (const src of analysis.sources) {
            sourceTypeBreakdown[src.sourceType] = (sourceTypeBreakdown[src.sourceType] || 0) + 1;
        }
        for (const tp of analysis.taintPaths) {
            if (tp.sanitised)
                sanitisedPaths++;
            else
                unsanitisedPaths++;
            sinkTypeBreakdown[tp.sink.sinkType] = (sinkTypeBreakdown[tp.sink.sinkType] || 0) + 1;
        }
    }
    return {
        analyses,
        stats: {
            filesAnalyzed,
            filesWithSources: Array.from(analyses.values()).filter(a => a.sources.length > 0).length,
            filesWithTaintPaths: Array.from(analyses.values()).filter(a => a.taintPaths.length > 0).length,
            totalSources,
            totalTaintedVars,
            totalTaintPaths,
            unsanitisedPaths,
            sanitisedPaths,
            totalSanitisers,
            sourceTypeBreakdown,
            sinkTypeBreakdown,
            duration: Date.now() - startTime,
        },
    };
}
// ============================================================================
// Detector Context Builder (Phase 2b)
// ============================================================================
/**
 * Build a DetectorContext from ProjectContext
 * This provides rich context to Layer 2 detectors for smarter decisions
 */
function buildDetectorContext(projectContext, middlewareConfig) {
    // Map ProjectContext to DetectorContext format
    const context = (0, types_1.createEmptyDetectorContext)();
    // Auth context
    if (middlewareConfig?.hasAuthMiddleware) {
        context.auth.middleware = {
            hasAuthMiddleware: true,
            authType: middlewareConfig.authType ?? null,
            protectedPaths: middlewareConfig.protectedPaths,
            publicPaths: middlewareConfig.publicPaths,
        };
    }
    // Auth helpers from project context
    if (projectContext.auth.throwingAuthHelpers.length > 0) {
        context.auth.authHelpers = {
            hasThrowingHelpers: true,
            throwingHelperNames: projectContext.auth.throwingAuthHelpers.map(h => h.name),
        };
    }
    // Public endpoints
    context.auth.publicEndpoints = projectContext.auth.publicPaths;
    // Framework context
    context.framework = {
        primary: projectContext.frameworks.primary ?? null,
        frontend: projectContext.frameworks.frontend ?? null,
        hasTypeScript: projectContext.frameworks.usesTypeScript,
    };
    // Data access context
    context.dataAccess = {
        orm: mapOrmToDataAccessOrm(projectContext.dataAccess.orm),
        hasRLS: projectContext.dataAccess.hasRLS,
        validationLib: mapValidationLib(projectContext.dataAccess.validationLibrary),
    };
    return context;
}
/**
 * Build file-specific context for a single file
 * This is merged with the project-level DetectorContext
 */
function buildFileDetectorContext(baseContext, filePath) {
    return {
        ...baseContext,
        file: {
            isServerOnly: (0, file_classifier_1.isServerOnlyFile)(filePath),
            isClientBundled: (0, file_classifier_1.isClientBundledFile)(filePath),
            isTestFile: (0, file_classifier_1.isTestOrMockFile)(filePath),
            isConfigFile: isConfigFile(filePath),
            isToolingDir: (0, file_classifier_1.isToolingDirectory)(filePath),
        },
    };
}
/**
 * Check if file is a configuration file
 */
function isConfigFile(filePath) {
    const configPatterns = [
        /config\.(ts|js|json|yaml|yml)$/i,
        /settings\.(ts|js|json)$/i,
        /constants\.(ts|js)$/i,
        /\.config\.(ts|js|mjs|cjs)$/i,
        /\.env/i,
        /tsconfig\.json$/i,
        /package\.json$/i,
        /jest\.config/i,
        /vitest\.config/i,
        /eslint/i,
        /prettier/i,
    ];
    return configPatterns.some(p => p.test(filePath));
}
/**
 * Map ProjectContext ORM to DetectorContext ORM
 */
function mapOrmToDataAccessOrm(orm) {
    if (!orm)
        return null;
    switch (orm) {
        case 'prisma':
        case 'drizzle':
        case 'typeorm':
        case 'sequelize':
        case 'knex':
            return orm;
        default:
            return null;
    }
}
/**
 * Map validation library to DetectorContext format
 */
function mapValidationLib(lib) {
    if (!lib)
        return null;
    switch (lib) {
        case 'zod':
        case 'yup':
        case 'joi':
        case 'valibot':
            return lib;
        default:
            return null;
    }
}
//# sourceMappingURL=index.js.map