"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PerformancePredictor = void 0;
const typescript_1 = __importDefault(require("typescript"));
const DB_API_NAMES = ['find', 'findOne', 'findAll', 'findBy', 'query', 'execute'];
class PerformancePredictor {
    predict(code) {
        const predictions = [];
        const lines = code.split('\n');
        const astPredictions = this.analyzeAst(code);
        predictions.push(...astPredictions);
        for (let i = 0; i < lines.length; i++) {
            const lineText = lines[i];
            const lineNum = i + 1;
            const alreadyReported = astPredictions.some((p) => p.line === lineNum);
            if (!alreadyReported) {
                const n1Prediction = this.checkNPlusOne(lineText, lineNum, lines, i);
                if (n1Prediction)
                    predictions.push(n1Prediction);
                const eagerPrediction = this.checkEagerLoading(lineText, lineNum, lines, i);
                if (eagerPrediction)
                    predictions.push(eagerPrediction);
                const memoryPrediction = this.checkMemoryLeak(lineText, lineNum, lines, i);
                if (memoryPrediction)
                    predictions.push(memoryPrediction);
                const bundlePrediction = this.checkLargeBundle(lineText, lineNum);
                if (bundlePrediction)
                    predictions.push(bundlePrediction);
            }
        }
        return predictions;
    }
    analyzeAst(code) {
        const results = [];
        let sourceFile;
        try {
            sourceFile = typescript_1.default.createSourceFile('analyze.tsx', code, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TSX);
        }
        catch {
            return results;
        }
        this.visitNode(sourceFile, code, results);
        return results;
    }
    visitNode(node, code, results) {
        if (typescript_1.default.isImportDeclaration(node)) {
            const clause = node.importClause;
            if (clause && clause.namedBindings && typescript_1.default.isNamespaceImport(clause.namedBindings)) {
                const moduleSpecifier = node.moduleSpecifier.getText().replace(/['"]/g, '');
                const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                if (!results.some((r) => r.type === 'large_bundle' && r.line === line)) {
                    results.push({
                        type: 'large_bundle',
                        line,
                        severity: 'low',
                        message: `Wildcard import of '${moduleSpecifier}' may increase bundle size`,
                        suggestion: `Import only needed exports: import { specificExport } from '${moduleSpecifier}'`,
                    });
                }
            }
        }
        if (typescript_1.default.isCallExpression(node) && typescript_1.default.isPropertyAccessExpression(node.expression)) {
            const methodName = node.expression.name.text;
            if (['forEach', 'map', 'filter', 'reduce', 'some', 'every'].includes(methodName)) {
                const callback = node.arguments[0];
                if (callback && (typescript_1.default.isArrowFunction(callback) || typescript_1.default.isFunctionExpression(callback))) {
                    const hasDbCall = this.callbackHasDbCall(callback);
                    if (hasDbCall) {
                        const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                        if (!results.some((r) => r.type === 'n_plus_one' && r.line === line)) {
                            results.push({
                                type: 'n_plus_one',
                                line,
                                severity: 'high',
                                message: 'Potential N+1 query: database call inside loop',
                                suggestion: 'Use eager loading or batch query',
                            });
                        }
                    }
                }
            }
        }
        if (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)) {
            if (node.expression.text === 'setInterval') {
                const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                const body = node.arguments[0];
                if (body && (typescript_1.default.isArrowFunction(body) || typescript_1.default.isFunctionExpression(body) || typescript_1.default.isIdentifier(body))) {
                    const bodyText = body.getText();
                    const hasClear = bodyText.includes('clearInterval');
                    const siblingText = code.substring(0, node.getStart()) + code.substring(node.getEnd());
                    if (!hasClear && !siblingText.includes('clearInterval')) {
                        if (!results.some((r) => r.type === 'memory_leak' && r.line === line)) {
                            results.push({
                                type: 'memory_leak',
                                line,
                                severity: 'high',
                                message: 'Potential memory leak: setInterval without clearInterval',
                                suggestion: 'Store interval ID and call clearInterval in cleanup',
                            });
                        }
                    }
                }
            }
            if (node.expression.text === 'addEventListener') {
                const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                const siblingText = code.substring(0, node.getStart()) + code.substring(node.getEnd());
                if (!siblingText.includes('removeEventListener')) {
                    if (!results.some((r) => r.type === 'memory_leak' && r.line === line)) {
                        results.push({
                            type: 'memory_leak',
                            line,
                            severity: 'medium',
                            message: 'Potential memory leak: addEventListener without removeEventListener',
                            suggestion: 'Call removeEventListener in cleanup function',
                        });
                    }
                }
            }
        }
        typescript_1.default.forEachChild(node, (child) => this.visitNode(child, code, results));
    }
    callbackHasDbCall(callback) {
        const body = callback.body;
        if (typescript_1.default.isBlock(body)) {
            return this.nodeTreeHasDbCall(body);
        }
        return this.nodeTreeHasDbCall(body);
    }
    nodeTreeHasDbCall(node) {
        if (typescript_1.default.isCallExpression(node) && typescript_1.default.isPropertyAccessExpression(node.expression)) {
            const callName = node.expression.name.text;
            if (DB_API_NAMES.includes(callName))
                return true;
        }
        let found = false;
        typescript_1.default.forEachChild(node, (child) => {
            if (this.nodeTreeHasDbCall(child))
                found = true;
        });
        return found;
    }
    checkNPlusOne(line, lineNum, allLines, idx) {
        if (/\.forEach\s*\(/.test(line) || /\.map\s*\(/.test(line)) {
            const bodyEnd = Math.min(allLines.length, idx + 10);
            const bodyLines = allLines.slice(idx, bodyEnd);
            const hasDbCall = bodyLines.some((l) => DB_API_NAMES.some((api) => new RegExp(`\\b${api}\\s*\\(`).test(l)));
            if (hasDbCall) {
                return {
                    type: 'n_plus_one', line: lineNum, severity: 'high',
                    message: 'Potential N+1 query: database call inside loop',
                    suggestion: 'Use eager loading or batch query: include: [relation] or findAll({ where: { id: ids } })',
                };
            }
        }
        return null;
    }
    checkEagerLoading(line, lineNum, allLines, idx) {
        if (/\bfindAll?\s*\(\s*\{/.test(line) && !/\binclude\s*:/.test(line)) {
            const contextLines = allLines && idx !== undefined ? allLines.slice(idx, Math.min(allLines.length, idx + 15)) : [line];
            const hasRelationAccess = contextLines.some((l) => this.checkForRelationAccess(l));
            if (hasRelationAccess) {
                return {
                    type: 'eager_loading', line: lineNum, severity: 'medium',
                    message: 'Missing eager loading: related data may cause extra queries',
                    suggestion: 'Add include: ["relation"] to prevent N+1 queries',
                };
            }
        }
        return null;
    }
    checkMemoryLeak(line, lineNum, allLines, idx) {
        if (/setInterval\s*\(/.test(line)) {
            const contextLines = allLines && idx !== undefined ? allLines.slice(idx, Math.min(allLines.length, idx + 20)) : [line];
            if (!contextLines.some((l) => /clearInterval/.test(l))) {
                return {
                    type: 'memory_leak', line: lineNum, severity: 'high',
                    message: 'Potential memory leak: setInterval without clearInterval',
                    suggestion: 'Store interval ID and call clearInterval in cleanup/useEffect return',
                };
            }
        }
        if (/addEventListener\s*\(/.test(line)) {
            const contextLines = allLines && idx !== undefined ? allLines.slice(idx, Math.min(allLines.length, idx + 20)) : [line];
            if (!contextLines.some((l) => /removeEventListener/.test(l))) {
                return {
                    type: 'memory_leak', line: lineNum, severity: 'medium',
                    message: 'Potential memory leak: addEventListener without removeEventListener',
                    suggestion: 'Call removeEventListener in cleanup function',
                };
            }
        }
        return null;
    }
    checkLargeBundle(line, lineNum) {
        if (/import\s+\*\s+as\s+(\w+)\s+from/.test(line)) {
            const match = line.match(/import\s+\*\s+as\s+(\w+)\s+from\s+['"](\w+)['"]/);
            if (match) {
                return {
                    type: 'large_bundle', line: lineNum, severity: 'low',
                    message: `Wildcard import of '${match[1]}' may increase bundle size`,
                    suggestion: `Import only needed exports: import { specificExport } from '${match[1]}'`,
                };
            }
        }
        return null;
    }
    checkForRelationAccess(line) {
        return [/\b(?:include|through|belongsTo|hasMany|hasOne)\b/, /\b(?:user|author|owner|creator|parent|children)\b/]
            .some((p) => p.test(line));
    }
}
exports.PerformancePredictor = PerformancePredictor;
//# sourceMappingURL=performance-predictor.js.map