"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorPredictor = void 0;
const typescript_1 = __importDefault(require("typescript"));
class ErrorPredictor {
    predict(code) {
        const predictions = [];
        const astPredictions = this.analyzeAst(code);
        predictions.push(...astPredictions);
        const lines = code.split('\n');
        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 nullPrediction = this.checkNullAccess(lineText, lineNum, lines, i);
                if (nullPrediction)
                    predictions.push(nullPrediction);
                const typePrediction = this.checkTypeMismatch(lineText, lineNum);
                if (typePrediction)
                    predictions.push(typePrediction);
            }
            const asyncPrediction = this.checkAsyncError(lineText, lineNum, lines, i);
            if (asyncPrediction && !astPredictions.some((p) => p.line === lineNum && p.type === asyncPrediction.type)) {
                predictions.push(asyncPrediction);
            }
        }
        return predictions;
    }
    getQuickFix(prediction) {
        switch (prediction.type) {
            case 'null_access':
                return `if (value !== null && value !== undefined) { /* use value */ }`;
            case 'type_mismatch':
                return `// Consider adding type assertion or runtime check`;
            case 'missing_await':
                return `await ${prediction.message.split("'")[1] || 'promise'}`;
            case 'unhandled_promise':
                return `.catch((error) => { console.error(error); })`;
        }
    }
    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;
        }
        const isAsyncContext = [false];
        this.visitNode(sourceFile, code, results, isAsyncContext);
        return results;
    }
    visitNode(node, code, results, isAsyncContext) {
        if (typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isFunctionExpression(node) || typescript_1.default.isArrowFunction(node)) {
            const hasAsync = node.modifiers?.some((m) => m.kind === typescript_1.default.SyntaxKind.AsyncKeyword) ?? false;
            isAsyncContext.push(hasAsync);
            typescript_1.default.forEachChild(node, (child) => this.visitNode(child, code, results, isAsyncContext));
            isAsyncContext.pop();
            return;
        }
        if (typescript_1.default.isCallExpression(node)) {
            const callText = node.expression.getText();
            const awaitKeyword = node.parent && typescript_1.default.isAwaitExpression(node.parent);
            const knownAsyncAPIs = ['fetch', 'axios', 'request', '$http', 'api.get', 'api.post', 'api.put', 'api.delete'];
            if (knownAsyncAPIs.some((api) => callText.startsWith(api)) && !awaitKeyword) {
                const inAsync = isAsyncContext[isAsyncContext.length - 1];
                if (inAsync) {
                    const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                    if (!results.some((r) => r.type === 'missing_await' && r.line === line)) {
                        results.push({
                            line,
                            type: 'missing_await',
                            severity: 'high',
                            message: `Missing 'await' for async call: ${callText}`,
                            suggestion: `Add 'await' before the async call`,
                        });
                    }
                }
            }
            if (!awaitKeyword && typescript_1.default.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'then') {
                const hasCatchParent = node.parent && typescript_1.default.isPropertyAccessExpression(node.parent) && node.parent.name.text === 'catch';
                if (!hasCatchParent) {
                    const siblingWasCatch = this.siblingIsCatch(node);
                    if (!siblingWasCatch) {
                        const line = typescript_1.default.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()).line + 1;
                        if (!results.some((r) => r.type === 'unhandled_promise' && r.line === line)) {
                            results.push({
                                line,
                                type: 'unhandled_promise',
                                severity: 'medium',
                                message: 'Unhandled promise rejection',
                                suggestion: 'Add .catch() handler',
                            });
                        }
                    }
                }
            }
        }
        typescript_1.default.forEachChild(node, (child) => this.visitNode(child, code, results, isAsyncContext));
    }
    siblingIsCatch(thenCall) {
        let current = thenCall.parent;
        if (current && typescript_1.default.isPropertyAccessExpression(current)) {
            const parentCall = current.parent;
            if (parentCall && typescript_1.default.isCallExpression(parentCall)) {
                const expr = parentCall.expression;
                if (typescript_1.default.isPropertyAccessExpression(expr) && expr.name.text === 'catch') {
                    return true;
                }
            }
        }
        return false;
    }
    checkTypeMismatch(line, lineNum) {
        const typePatterns = [
            { pattern: /const (\w+):\s*string\s*=\s*(\d+)/, msg: "String type assigned number value" },
            { pattern: /const (\w+):\s*number\s*=\s*['"]/, msg: "Number type assigned string value" },
            { pattern: /(\w+)\s*==\s*(\w+)/, msg: "Loose equality may cause type coercion" },
        ];
        for (const { pattern, msg } of typePatterns) {
            if (pattern.test(line)) {
                return {
                    line: lineNum,
                    type: 'type_mismatch',
                    severity: 'medium',
                    message: msg,
                    suggestion: 'Use strict equality (===) or correct the type',
                };
            }
        }
        return null;
    }
    checkNullAccess(line, lineNum, allLines, idx) {
        const nullPatterns = [
            /(\w+)\.(\w+)\s*\(/,
            /(\w+)\[(\w+)\]/,
        ];
        for (const pattern of nullPatterns) {
            const match = line.match(pattern);
            if (match) {
                const varName = match[1];
                if (['console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Date', 'Promise', 'RegExp', 'Map', 'Set', 'this', 'process', 'module', 'exports', 'require', 'window', 'document', 'globalThis'].includes(varName))
                    continue;
                const hasNullCheck = this.hasNullCheckBefore(varName, allLines, idx);
                if (!hasNullCheck) {
                    return {
                        line: lineNum,
                        type: 'null_access',
                        severity: 'high',
                        message: `Potential null access on '${varName}'`,
                        suggestion: `Add null check before accessing '${varName}'`,
                    };
                }
            }
        }
        return null;
    }
    hasNullCheckBefore(varName, lines, currentIdx) {
        const checkPatterns = [
            new RegExp(`if\\s*\\(\\s*${varName}\\s*[!=]`),
            new RegExp(`if\\s*\\(\\s*${varName}\\s*!==\\s*(null|undefined)`),
            new RegExp(`${varName}\\s*\\?\\s*\\.`),
            new RegExp(`${varName}\\s*&&`),
        ];
        for (let i = Math.max(0, currentIdx - 10); i < currentIdx; i++) {
            for (const pattern of checkPatterns) {
                if (pattern.test(lines[i]))
                    return true;
            }
        }
        return false;
    }
    checkAsyncError(line, lineNum, allLines, idx) {
        if (/\.then\s*\(/.test(line) && !/\.catch\s*\(/.test(line)) {
            const hasCatchAfter = allLines.slice(idx).some((l) => l.includes('.catch'));
            if (!hasCatchAfter) {
                return {
                    line: lineNum,
                    type: 'unhandled_promise',
                    severity: 'medium',
                    message: 'Unhandled promise rejection',
                    suggestion: 'Add .catch() handler',
                };
            }
        }
        if (/\b(fetch|axios|request)\s*\(/.test(line) && !/\bawait\b/.test(line)) {
            const isInAsync = allLines.slice(0, idx).some((l) => l.includes('async'));
            if (isInAsync) {
                return {
                    line: lineNum,
                    type: 'missing_await',
                    severity: 'high',
                    message: "Missing 'await' for async operation",
                    suggestion: "Add 'await' before the async call",
                };
            }
        }
        return null;
    }
}
exports.ErrorPredictor = ErrorPredictor;
//# sourceMappingURL=error-predictor.js.map