"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.scanFileForBlindSpots = scanFileForBlindSpots;
exports.scanDirectoryForBlindSpots = scanDirectoryForBlindSpots;
exports.summarizeBlindSpots = summarizeBlindSpots;
exports.formatBlindSpotReport = formatBlindSpotReport;
exports.getCategoryCounts = getCategoryCounts;
exports.rankBlindSpots = rankBlindSpots;
exports.getTopFixes = getTopFixes;
exports.getCategorySummary = getCategorySummary;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
// ─── Helpers ──────────────────────────────────────────────────────────────
const REACT_EXT = new Set(['.jsx', '.tsx']);
const JS_EXT = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '__pycache__', 'archived']);
function shouldScan(p) {
    const parts = p.split(path.sep);
    return !parts.some(dir => IGNORE_DIRS.has(dir) || dir.startsWith('.'));
}
function readLines(filePath) {
    try {
        return fs.readFileSync(filePath, 'utf-8').split(/\r?\n/);
    }
    catch {
        return [];
    }
}
function lineAt(lines, idx) {
    if (idx < 0 || idx >= lines.length)
        return '';
    return lines[idx].trim();
}
function findLine(lines, predicate, startFrom = 0) {
    for (let i = startFrom; i < lines.length; i++) {
        if (predicate(lines[i]))
            return i + 1;
    }
    return 0;
}
function isAsyncFunc(line) {
    return /^\s*(export\s+)?(async\s+)?(function|const|let|var|class|\([^)]*\)|\w+\s*=\s*async|\w+\s*:\s*async)/.test(line)
        && /async\s/.test(line);
}
function hasTryCatchBetween(lines, start, depth = 1) {
    // Walk forward, track brace depth, look for matching try/catch pair
    let tries = 0, catches = 0;
    for (let i = start; i < lines.length && i < start + 80; i++) {
        const l = lines[i];
        if (/\btry\s*\{/.test(l))
            tries++;
        if (/\}\s*catch\b/.test(l) || /\bcatch\s*\(/.test(l))
            catches++;
    }
    return catches > 0;
}
function makeId() {
    return 'bs_' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
}
// ─── Detectors ────────────────────────────────────────────────────────────
function detectPromisesWithoutCatch(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\.then\s*\(/.test(l) && !/\.catch\s*\(/.test(l)) {
            // Look ahead 5 lines for a .catch
            let hasCatch = false;
            for (let j = i + 1; j <= i + 5 && j < lines.length; j++) {
                if (/\.catch\s*\(/.test(lines[j])) {
                    hasCatch = true;
                    break;
                }
            }
            if (!hasCatch) {
                out.push({
                    id: makeId(), category: 'error_handling', severity: 'high',
                    message: 'Promise.then() without .catch() — unhandled rejection risk',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Add .catch() handler, or use try/await with proper error handling',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
function detectMissingReactErrorBoundaries(filePath, lines) {
    const ext = path.extname(filePath);
    if (!REACT_EXT.has(ext))
        return [];
    const out = [];
    // Check for render functions / class components without ErrorBoundary wrap
    let hasErrorBoundary = false;
    for (const l of lines) {
        if (/ErrorBoundary|componentDidCatch|getDerivedStateFromError/.test(l)) {
            hasErrorBoundary = true;
            break;
        }
    }
    if (hasErrorBoundary)
        return [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/(return\s*\(\s*<|render\s*\(\s*\)\s*\{|class\s+\w+\s+extends\s+(React\.)?Component)/.test(l)) {
            out.push({
                id: makeId(), category: 'error_handling', severity: 'medium',
                message: 'React component without ErrorBoundary — unhandled render errors crash tree',
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: 'Wrap render in <ErrorBoundary> or add componentDidCatch',
                detectedAt: new Date().toISOString(),
            });
            break; // one per file
        }
    }
    return out;
}
function detectApiCallsWithoutErrorState(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\b(fetch|axios|got)\s*\(/.test(l) || /\b(fetch|axios|got)\.\w+\s*\(/.test(l)) {
            // Look back 10 lines and ahead 15 lines for .catch, try/catch, or error state
            let hasError = false;
            for (let j = Math.max(0, i - 10); j < i + 15 && j < lines.length; j++) {
                if (/\.catch\s*\(/.test(lines[j]) || /\btry\s*\{/.test(lines[j])) {
                    hasError = true;
                    break;
                }
            }
            if (!hasError) {
                out.push({
                    id: makeId(), category: 'error_handling', severity: 'high',
                    message: 'API call without error handling — silent failures on network errors',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Add try/catch or .catch() handler; expose error state to UI',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
function detectDbOperationsWithoutRollback(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\b(prisma\.\w+\.\w+|db\.\w+\.\w+|knex\(.+?\)\(|sequelize\.\w+|mongoose\.\w+\.\w+|pool\.query)\s*\(/.test(l)) {
            let hasTransaction = false;
            for (let j = i; j < i + 20 && j < lines.length; j++) {
                if (/\$transaction|\$transaction\(|BEGIN|transaction\(\)|\.transaction\(/.test(lines[j])) {
                    hasTransaction = true;
                    break;
                }
            }
            if (!hasTransaction) {
                out.push({
                    id: makeId(), category: 'error_handling', severity: 'medium',
                    message: 'DB operation without transaction — partial writes on failure',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Wrap multi-step DB ops in $transaction or .transaction()',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
function detectFileOpsWithoutErrorHandling(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\b(readFile|writeFile|appendFile|unlink|rm|mkdir|readdir|createReadStream|createWriteStream)\s*\(/.test(l)
            && !/Sync\s*\(/.test(l)) {
            // Check for await + try/catch nearby
            let hasError = false;
            for (let j = Math.max(0, i - 5); j < i + 10 && j < lines.length; j++) {
                if (/\btry\s*\{/.test(lines[j])) {
                    hasError = true;
                    break;
                }
            }
            if (!hasError) {
                out.push({
                    id: makeId(), category: 'error_handling', severity: 'low',
                    message: 'File operation without error handling — crashes on ENOENT/EACCES',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Wrap in try/catch and handle ENOENT explicitly',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
function detectApiWithoutTimeout(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\baxios\s*\(/.test(l) || /axios\.\w+\s*\(/.test(l)) {
            // Look at next 10 lines for timeout config
            let hasTimeout = false;
            for (let j = i; j < i + 10 && j < lines.length; j++) {
                if (/timeout\s*:/.test(lines[j])) {
                    hasTimeout = true;
                    break;
                }
            }
            if (!hasTimeout) {
                out.push({
                    id: makeId(), category: 'error_handling', severity: 'medium',
                    message: 'Axios call without timeout — hangs forever on unresponsive servers',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Add timeout: 5000 (or appropriate) in axios config',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
function detectAwaitWithoutNullCheck(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        const m = l.match(/(?:const|let|var)\s+(\w+)\s*=\s*await\s+(?:this\.)?(\w+)\s*\(/);
        if (!m)
            continue;
        const varName = m[1];
        // Look ahead 10 lines for `if (!varName)` or `?.` or null check
        let checked = false;
        for (let j = i + 1; j < i + 10 && j < lines.length; j++) {
            if (new RegExp(`if\\s*\\(\\s*!?\\s*${varName}\\b|${varName}\\s*\\?\\.|${varName}\\s*&&|${varName}\\s*\\|\\|`).test(lines[j])) {
                checked = true;
                break;
            }
        }
        if (!checked) {
            out.push({
                id: makeId(), category: 'null_safety', severity: 'high',
                message: `Awaited value '${varName}' used without null check`,
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: `Add null check: if (!${varName}) return; or use optional chaining (${varName}?.x)`,
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
function detectArrayAccessWithoutLengthCheck(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        // Only flag array[0] (not string[0] which is always valid char access)
        const arrMatch = l.match(/\b(\w+)\s*\[\s*0\s*\]/);
        if (!arrMatch)
            continue;
        const varName = arrMatch[1];
        // Skip known non-array identifiers (single-char, common words)
        if (/^(args|params|argv|flags|opts|options|props)$/i.test(varName))
            continue;
        // Check if length was checked within 5 lines above
        let checked = false;
        for (let j = Math.max(0, i - 5); j < i; j++) {
            if (new RegExp(`\\b${varName}\\.length\\s*[><!]|\\b${varName}\\?\\.length`).test(lines[j])) {
                checked = true;
                break;
            }
        }
        if (!checked) {
            out.push({
                id: makeId(), category: 'null_safety', severity: 'low',
                message: `Array index [0] access on '${varName}' without length check — undefined on empty array`,
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: `Check \`${varName}.length > 0\` before access, or use \`${varName}[0] ?? default\``,
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
function detectProcessEnvWithoutFallback(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/process\.env\.\w+/.test(l) && !/\|\|/.test(l) && !/\?\?/.test(l) && !/process\.env\.\w+\s*=/.test(l)) {
            out.push({
                id: makeId(), category: 'validation', severity: 'low',
                message: 'process.env used without fallback — undefined if missing',
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: 'Add fallback: `process.env.X || "default"` or use ?? operator',
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
function detectN1QueryPattern(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/\bfor\s*\(/.test(l) || /\.forEach\s*\(/.test(l) || /\.map\s*\(/.test(l)) {
            // Look inside for await with DB call
            for (let j = i + 1; j < i + 15 && j < lines.length; j++) {
                if (/await\s+(prisma\.|db\.|sequelize|mongoose|knex|pool\.)/.test(lines[j])) {
                    out.push({
                        id: makeId(), category: 'performance', severity: 'high',
                        message: 'N+1 query pattern: DB call inside loop',
                        filePath, lineNumber: j + 1, codeSnippet: lines[j].trim().slice(0, 120),
                        recommendation: 'Batch query: use `findMany({ where: { id: { in: ids } } })` or include relation',
                        detectedAt: new Date().toISOString(),
                    });
                    break;
                }
            }
        }
    }
    return out;
}
function detectSyncOpsInAsyncCode(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    let inAsync = false;
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/async\s+(function|\w+\s*\(|\w+\s*=\s*async|\(|\w+\s*:\s*async)/.test(l))
            inAsync = true;
        if (inAsync && /readFileSync|writeFileSync|appendFileSync|execSync|spawnSync/.test(l)) {
            out.push({
                id: makeId(), category: 'performance', severity: 'medium',
                message: 'Sync file/exec operation in async function — blocks event loop',
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: 'Use async variant: readFile, writeFile, exec (with await)',
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
function detectConsoleLogInProduction(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    let count = 0;
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/(^|\s)console\.log\s*\(/.test(l)) {
            count++;
            if (count <= 2) {
                out.push({
                    id: makeId(), category: 'quality', severity: 'low',
                    message: 'console.log in source — use structured logger in production',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Replace with logger.info() or remove debug logs',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    // Cap at 3 console.log warnings per file to avoid noise
    return out.slice(0, 3);
}
function detectAnyUsage(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        // Match `: any` or `<any>` or `as any` but not `Many` or `company`
        if (/:\s*any[\s,;\)\]\}]/.test(l) || /<any>/.test(l) || /\bas\s+any\b/.test(l)) {
            out.push({
                id: makeId(), category: 'quality', severity: 'medium',
                message: '`any` type used — defeats TypeScript safety',
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: 'Use `unknown` and narrow with type guards, or define proper interface',
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
function detectNonNullAssertionOveruse(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    let count = 0;
    for (const l of lines) {
        if (/\w![\s\.\(\[]/.test(l) && !/!==/.test(l) && !/!=/.test(l))
            count++;
    }
    if (count < 3)
        return [];
    return [{
            id: makeId(), category: 'null_safety', severity: 'medium',
            message: `High non-null assertion usage (${count}x in file) — possible unsafe null bypasses`,
            filePath, lineNumber: 1, codeSnippet: `(${count} non-null assertions in this file)`,
            recommendation: 'Replace `x!` with explicit null checks or `x ?? default`',
            detectedAt: new Date().toISOString(),
        }];
}
function detectMissingPagination(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        if (/prisma\.\w+\.findMany\s*\(/.test(l) || /\.find\s*\(\s*\{\s*\}\s*\)/.test(l)) {
            // Check next 5 lines for skip/take/limit
            let hasPagination = false;
            for (let j = i; j < i + 5 && j < lines.length; j++) {
                if (/\b(skip|take|limit|offset)\s*:/.test(lines[j])) {
                    hasPagination = true;
                    break;
                }
            }
            if (!hasPagination) {
                out.push({
                    id: makeId(), category: 'performance', severity: 'medium',
                    message: 'findMany() without pagination — loads entire table into memory',
                    filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                    recommendation: 'Add `take: 50` and `skip: offset` for pagination',
                    detectedAt: new Date().toISOString(),
                });
            }
        }
    }
    return out;
}
// ─── Additional detectors (Test 4 fixes) ───────────────────────────────────
// Detect async functions whose body contains no try/catch
function detectAsyncWithoutTryCatch(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        // Match declarations: "async function name(" or "async ("
        if (!/^\s*(export\s+)?(async\s+function\b|async\s+[\w$]+\s*[(=]|async\s*\()/i.test(l))
            continue;
        // Find the body by brace matching from the end of the line
        const openIdx = l.indexOf('{');
        if (openIdx < 0) {
            // Body may open on next line; defer detection by 1 line
            if (i + 1 < lines.length && lines[i + 1].includes('{')) {
                // continue below using i+1 as body start
            }
            else {
                continue;
            }
        }
        // Find closing brace by tracking depth from this line
        let depth = 0;
        let sawTry = false;
        let sawReturnAwait = false;
        let lastBraceLine = i;
        for (let j = i; j < lines.length && j < i + 60; j++) {
            const line = lines[j];
            for (let c = 0; c < line.length; c++) {
                if (line[c] === '{')
                    depth++;
                else if (line[c] === '}') {
                    depth--;
                    if (depth === 0) {
                        lastBraceLine = j;
                        break;
                    }
                }
            }
            if (/\btry\s*\{/.test(line))
                sawTry = true;
            if (/\bawait\s+/.test(line))
                sawReturnAwait = true;
            if (depth === 0 && j > i) {
                lastBraceLine = j;
                break;
            }
        }
        if (depth !== 0)
            continue; // malformed, skip
        if (sawReturnAwait && !sawTry) {
            out.push({
                id: makeId(), category: 'error_handling', severity: 'high',
                message: 'Async function awaits a value but has no try/catch — unhandled rejection risk',
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: 'Wrap awaits in try/catch or add .catch() on the promise',
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
// Detect chained property access after await without null check (e.g. result.data.items)
function detectChainedPropertyAccess(filePath, lines) {
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const out = [];
    for (let i = 0; i < lines.length; i++) {
        const l = lineAt(lines, i);
        // Match patterns like: return result.data.items; or const x = foo.bar.baz;
        const m = l.match(/(?:return\s+|=\s*|\(\s*)([a-zA-Z_$][\w$]*)\s*\.\s*([a-zA-Z_$][\w$]*)\s*\.\s*([a-zA-Z_$][\w$]*)/);
        if (!m)
            continue;
        const root = m[1];
        // Look back 20 lines for: const root = await ... or similar source
        let awaitedRoot = false;
        for (let j = Math.max(0, i - 20); j < i; j++) {
            const back = lines[j];
            if (new RegExp(`\\b(const|let|var)\\s+${root}\\s*=\\s*await\\b`).test(back)) {
                awaitedRoot = true;
                break;
            }
        }
        if (!awaitedRoot)
            continue;
        // Look at the SAME line and the few lines after for a null guard on root
        let guarded = false;
        for (let j = Math.max(0, i - 3); j < i + 5 && j < lines.length; j++) {
            if (new RegExp(`\\b${root}\\s*\\?\\.|\\bif\\s*\\(\\s*!?\\s*${root}\\b|\\b${root}\\s*&&|\\b${root}\\s*\\|\\|`).test(lines[j])) {
                guarded = true;
                break;
            }
        }
        if (!guarded) {
            out.push({
                id: makeId(), category: 'null_safety', severity: 'high',
                message: `Chained access on awaited value '${root}.${m[2]}.${m[3]}' without null check — undefined on partial response`,
                filePath, lineNumber: i + 1, codeSnippet: l.slice(0, 120),
                recommendation: `Use optional chaining: ${root}?.${m[2]}?.${m[3]}, or guard with if (!${root}) return`,
                detectedAt: new Date().toISOString(),
            });
        }
    }
    return out;
}
// ─── Scanner ──────────────────────────────────────────────────────────────
const ALL_DETECTORS = [
    detectPromisesWithoutCatch,
    detectMissingReactErrorBoundaries,
    detectApiCallsWithoutErrorState,
    detectDbOperationsWithoutRollback,
    detectFileOpsWithoutErrorHandling,
    detectApiWithoutTimeout,
    detectAwaitWithoutNullCheck,
    detectArrayAccessWithoutLengthCheck,
    detectProcessEnvWithoutFallback,
    detectN1QueryPattern,
    detectSyncOpsInAsyncCode,
    detectConsoleLogInProduction,
    detectAnyUsage,
    detectNonNullAssertionOveruse,
    detectMissingPagination,
    detectAsyncWithoutTryCatch,
    detectChainedPropertyAccess,
];
function scanFileForBlindSpots(filePath) {
    if (!shouldScan(filePath))
        return [];
    const ext = path.extname(filePath);
    if (!JS_EXT.has(ext))
        return [];
    const lines = readLines(filePath);
    if (lines.length === 0)
        return [];
    const out = [];
    for (const detector of ALL_DETECTORS) {
        try {
            out.push(...detector(filePath, lines));
        }
        catch { /* skip on parse error */ }
    }
    // Cap to top 25 issues per file (highest severity first) to avoid noise
    const sevRank = { critical: 0, high: 1, medium: 2, low: 3 };
    return out.sort((a, b) => sevRank[a.severity] - sevRank[b.severity]).slice(0, 25);
}
function scanDirectoryForBlindSpots(dir, maxFiles = 500) {
    const results = [];
    // If path is a file, scan it directly
    try {
        const stat = fs.statSync(dir);
        if (stat.isFile()) {
            return scanFileForBlindSpots(dir);
        }
    }
    catch { /* fall through to directory walk */ }
    const queue = [dir];
    let scanned = 0;
    while (queue.length > 0 && scanned < maxFiles) {
        const current = queue.shift();
        let entries;
        try {
            entries = fs.readdirSync(current, { withFileTypes: true });
        }
        catch {
            continue;
        }
        for (const entry of entries) {
            if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.'))
                continue;
            const full = path.join(current, entry.name);
            if (entry.isDirectory()) {
                queue.push(full);
            }
            else if (entry.isFile()) {
                const ext = path.extname(entry.name);
                if (JS_EXT.has(ext)) {
                    results.push(...scanFileForBlindSpots(full));
                    scanned++;
                }
            }
        }
    }
    return results;
}
function summarizeBlindSpots(spots) {
    const summary = {
        total: spots.length, critical: 0, high: 0, medium: 0, low: 0,
        byCategory: {}, byFile: {},
    };
    for (const s of spots) {
        summary[s.severity]++;
        summary.byCategory[s.category] = (summary.byCategory[s.category] || 0) + 1;
        summary.byFile[s.filePath] = (summary.byFile[s.filePath] || 0) + 1;
    }
    return summary;
}
function formatBlindSpotReport(spots) {
    if (spots.length === 0)
        return '✅ No blind spots detected.\n';
    const summary = summarizeBlindSpots(spots);
    const lines = [];
    lines.push('⚠️  BLIND SPOTS DETECTED\n');
    const bySev = ['critical', 'high', 'medium', 'low'];
    const icons = { critical: '🔴', high: '🟡', medium: '🟠', low: '⚪' };
    const labels = { critical: 'CRITICAL', high: 'HIGH', medium: 'MEDIUM', low: 'LOW' };
    for (const sev of bySev) {
        const items = spots.filter(s => s.severity === sev);
        if (items.length === 0)
            continue;
        lines.push(`${icons[sev]} ${labels[sev]} (${items.length}):`);
        for (const s of items.slice(0, 5)) {
            lines.push(`  → ${s.message}`);
            lines.push(`     ${s.filePath}:${s.lineNumber}`);
        }
        if (items.length > 5)
            lines.push(`     ... ${items.length - 5} more`);
        lines.push('');
    }
    lines.push(`Total: ${summary.total} (🔴 ${summary.critical} | 🟡 ${summary.high} | 🟠 ${summary.medium} | ⚪ ${summary.low})`);
    return lines.join('\n');
}
function getCategoryCounts(dir) {
    const spots = scanDirectoryForBlindSpots(dir, 200);
    return summarizeBlindSpots(spots).byCategory;
}
const SEV_WEIGHT = { critical: 4, high: 3, medium: 2, low: 1 };
const FIX_DIFFICULTY = {
    error_handling: 1.5,
    null_safety: 2,
    validation: 2,
    performance: 3,
    quality: 1,
    security: 4,
};
function computePriorityScore(spot, frequencyMap, fileImportance) {
    const severity = SEV_WEIGHT[spot.severity] || 1;
    const confidence = spot.message.length > 0 ? 0.9 : 0.5;
    const frequency = (frequencyMap.get(spot.message) || 1);
    const importance = fileImportance.get(spot.filePath) || 1;
    const difficulty = FIX_DIFFICULTY[spot.category] || 2;
    return severity * confidence * frequency * importance / difficulty;
}
function rankBlindSpots(spots) {
    const freqMap = new Map();
    for (const s of spots) {
        freqMap.set(s.message, (freqMap.get(s.message) || 0) + 1);
    }
    const fileImportance = new Map();
    for (const [msg, count] of freqMap.entries()) {
        const matching = spots.filter(s => s.message === msg);
        for (const s of matching) {
            fileImportance.set(s.filePath, Math.max(fileImportance.get(s.filePath) || 1, count));
        }
    }
    const scored = spots.map(s => ({
        ...s,
        priorityScore: computePriorityScore(s, freqMap, fileImportance),
    }));
    return scored.sort((a, b) => (b.priorityScore || 0) - (a.priorityScore || 0));
}
function getTopFixes(spots, count = 5) {
    return rankBlindSpots(spots).slice(0, count);
}
function getCategorySummary(spots) {
    const byCategory = new Map();
    const sevOrd = { critical: 0, high: 1, medium: 2, low: 3 };
    const sevFromOrd = { 0: 'critical', 1: 'high', 2: 'medium', 3: 'low' };
    for (const s of spots) {
        const entry = byCategory.get(s.category);
        if (!entry) {
            byCategory.set(s.category, { count: 1, maxSev: sevOrd[s.severity] });
        }
        else {
            entry.count++;
            entry.maxSev = Math.min(entry.maxSev, sevOrd[s.severity]);
        }
    }
    return Array.from(byCategory.entries())
        .map(([category, v]) => ({ category, count: v.count, topSeverity: sevFromOrd[v.maxSev] }))
        .sort((a, b) => b.count - a.count);
}
//# sourceMappingURL=enhanced-blind-spots.js.map