// Next.js: getServerSideProps without try/catch
const NEXT_GSP = /export\s+(?:const|async\s+function|function)\s+getServerSideProps/;
const HAS_TRY = /\btry\s*\{/;
// Express: async route handler without error handling
const EXPRESS_ASYNC_ROUTE = /(?:app|router)\.\w+\s*\([^,)]+,\s*async\s*(?:\([^)]*\)|[a-z]+)\s*=>/;
const HAS_CATCH_OR_NEXT = /(?:\.catch\s*\(|next\s*\(\s*(?:err|error)|\btry\s*\{)/;
// Prisma: findUnique/findFirst without null check
const PRISMA_FIND = /await\s+\w+\.\w+\.(?:findUnique|findFirst)\s*\(/;
// Matches: if (!result), if (result === null), if (result !== null), result ?? fallback
const PRISMA_NULL_CHECK = /(?:if\s*\(\s*!\s*\w+|if\s*\(\s*\w+\s*(?:===|!==)\s*null|\?\?)/;
// Django: Model.objects.get() without DoesNotExist handler
const DJANGO_OBJECTS_GET = /\w+\.objects\.get\s*\(/;
// Gin: ShouldBindJSON with ignored error
const GIN_BIND = /\.ShouldBindJSON\s*\(&?\w+\)/;
const GIN_BIND_ERROR_HANDLED = /(?:if\s+err\s*!=\s*nil|if\s+\w+\s*:?=\s*.*ShouldBind)/;
// Actix: unwrap in request handler function
const ACTIX_HANDLER = /(?:async\s+fn\s+\w+\s*\([^)]*(?:HttpRequest|web::Data|web::Json|web::Path)[^)]*\))/;
function splitLines(content) {
    return content.split('\n');
}
function isNextJsFile(filePath, content) {
    return (filePath.endsWith('.tsx') ||
        filePath.endsWith('.jsx') ||
        content.includes('next/') ||
        content.includes("from 'next"));
}
function isExpressFile(content) {
    return content.includes("from 'express'") || content.includes("require('express')");
}
function isPrismaFile(content) {
    return content.includes('@prisma/client') || content.includes('PrismaClient');
}
function isDjangoFile(filePath, content) {
    return (filePath.endsWith('.py') &&
        (content.includes('from django') || content.includes('import django')));
}
function isGinFile(content) {
    return content.includes('"github.com/gin-gonic/gin"');
}
function isActixFile(content) {
    return content.includes('actix_web') || content.includes('use actix_web');
}
function detectNextJs(filePath, content) {
    const risks = [];
    if (!isNextJsFile(filePath, content) || !NEXT_GSP.test(content)) {
        return risks;
    }
    // Find the function and check if it has try/catch
    if (!HAS_TRY.test(content)) {
        const lines = splitLines(content);
        for (let i = 0; i < lines.length; i++) {
            if (NEXT_GSP.test(lines[i] ?? '')) {
                risks.push({
                    file: filePath,
                    line: i + 1,
                    pattern: 'NEXTJS_GSP_NO_TRY',
                    severity: 'HIGH',
                    description: 'getServerSideProps without try/catch. Any uncaught error will crash the entire page render.',
                    suggestedFix: 'Wrap the body of getServerSideProps in try/catch and return { notFound: true } or redirect on error.',
                    autoFixable: false,
                });
            }
        }
    }
    return risks;
}
function detectExpress(filePath, content) {
    const risks = [];
    if (!isExpressFile(content)) {
        return risks;
    }
    const lines = splitLines(content);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (EXPRESS_ASYNC_ROUTE.test(line) && !HAS_CATCH_OR_NEXT.test(content)) {
            risks.push({
                file: filePath,
                line: i + 1,
                pattern: 'EXPRESS_ASYNC_ROUTE_NO_ERROR_HANDLER',
                severity: 'HIGH',
                description: 'Async Express route handler without .catch() or next(err). Unhandled promise rejections crash the server in Node.js.',
                suggestedFix: 'Add .catch(next) at the end, wrap in try/catch and call next(err), or use an async wrapper like express-async-errors.',
                autoFixable: false,
            });
        }
    }
    return risks;
}
function detectPrisma(filePath, content) {
    const risks = [];
    if (!isPrismaFile(content)) {
        return risks;
    }
    const lines = splitLines(content);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (PRISMA_FIND.test(line)) {
            // Check surrounding lines for null check
            const windowStart = Math.max(0, i - 1);
            const windowEnd = Math.min(lines.length - 1, i + 5);
            let hasNullCheck = false;
            for (let j = windowStart; j <= windowEnd; j++) {
                if (PRISMA_NULL_CHECK.test(lines[j] ?? '')) {
                    hasNullCheck = true;
                    break;
                }
            }
            if (!hasNullCheck) {
                risks.push({
                    file: filePath,
                    line: i + 1,
                    pattern: 'PRISMA_FIND_NO_NULL_CHECK',
                    severity: 'HIGH',
                    description: 'Prisma findUnique/findFirst can return null when record is not found. Accessing fields without null check crashes.',
                    suggestedFix: 'Check for null: if (!result) { throw new NotFoundError() } or use findUniqueOrThrow().',
                    autoFixable: false,
                });
            }
        }
    }
    return risks;
}
function detectDjango(filePath, content) {
    const risks = [];
    if (!isDjangoFile(filePath, content)) {
        return risks;
    }
    const lines = splitLines(content);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (DJANGO_OBJECTS_GET.test(line) && !content.includes('DoesNotExist')) {
            risks.push({
                file: filePath,
                line: i + 1,
                pattern: 'DJANGO_GET_NO_DOES_NOT_EXIST',
                severity: 'HIGH',
                description: 'Model.objects.get() raises DoesNotExist exception if no record matches. Not catching it crashes the view.',
                suggestedFix: 'Use try/except Model.DoesNotExist, or use get_object_or_404(), or filter().first().',
                autoFixable: false,
            });
        }
    }
    return risks;
}
function detectGin(filePath, content) {
    const risks = [];
    if (!isGinFile(content)) {
        return risks;
    }
    const lines = splitLines(content);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (GIN_BIND.test(line) && !GIN_BIND_ERROR_HANDLED.test(line)) {
            // Check nearby lines for error handling
            const windowStart = Math.max(0, i - 1);
            const windowEnd = Math.min(lines.length - 1, i + 3);
            let handled = false;
            for (let j = windowStart; j <= windowEnd; j++) {
                if (/if\s+err\s*!=\s*nil/.test(lines[j] ?? '')) {
                    handled = true;
                    break;
                }
            }
            if (!handled) {
                risks.push({
                    file: filePath,
                    line: i + 1,
                    pattern: 'GIN_BIND_ERROR_IGNORED',
                    severity: 'HIGH',
                    description: 'Gin ShouldBindJSON error not checked. Binding failures are silently ignored, leading to zero-value structs.',
                    suggestedFix: 'Check the error: if err := c.ShouldBindJSON(&input); err != nil { c.JSON(400, gin.H{"error": err.Error()}); return }',
                    autoFixable: false,
                });
            }
        }
    }
    return risks;
}
function detectActix(filePath, content) {
    const risks = [];
    if (!isActixFile(content) || !filePath.endsWith('.rs')) {
        return risks;
    }
    if (!ACTIX_HANDLER.test(content) || !content.includes('.unwrap()')) {
        return risks;
    }
    const lines = splitLines(content);
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i] ?? '';
        if (line.includes('.unwrap()')) {
            risks.push({
                file: filePath,
                line: i + 1,
                pattern: 'ACTIX_UNWRAP_IN_HANDLER',
                severity: 'CRITICAL',
                description: 'unwrap() in Actix-web request handler panics and crashes the worker thread on error.',
                suggestedFix: 'Return a proper error response: use ? operator with impl ResponseError, or map_err() to return HttpResponse::InternalServerError().',
                autoFixable: false,
            });
        }
    }
    return risks;
}
export const frameworkDetector = {
    language: 'multi',
    frameworks: ['Next.js', 'Express', 'Prisma', 'Django', 'Gin', 'Actix'],
    fileExtensions: ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs'],
    detect(filePath, content) {
        return [
            ...detectNextJs(filePath, content),
            ...detectExpress(filePath, content),
            ...detectPrisma(filePath, content),
            ...detectDjango(filePath, content),
            ...detectGin(filePath, content),
            ...detectActix(filePath, content),
        ];
    },
};
//# sourceMappingURL=framework-detector.js.map