"use strict";
/**
 * Auth Helper Detector
 *
 * Detects authentication helper functions that throw on missing auth,
 * return non-null types, and provide security guarantees.
 *
 * When these helpers are called, subsequent code is GUARANTEED to have
 * an authenticated user - no additional `if (!userId)` checks are needed.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectAuthHelpers = detectAuthHelpers;
exports.usesThrowingAuthHelper = usesThrowingAuthHelper;
exports.hasAuthHelperCallBefore = hasAuthHelperCallBefore;
exports.isUserIdAlreadyValidated = isUserIdAlreadyValidated;
// ============================================================================
// Well-Known Auth Helper Patterns
// ============================================================================
/**
 * Common patterns for auth helpers that THROW on missing auth
 * These are functions that guarantee authenticated context after call
 */
const THROWING_AUTH_HELPER_PATTERNS = [
    // Generic patterns
    {
        namePattern: /^(get|fetch|require|ensure)(Current)?(User|UserId|Auth|Session|Principal)(Id)?$/i,
        callPattern: /\b(get|fetch|require|ensure)(Current)?(User|UserId|Auth|Session|Principal)(Id)?\s*\(/gi,
        description: 'Auth helper that retrieves authenticated user',
    },
    // Clerk patterns
    {
        namePattern: /^auth$/,
        callPattern: /\bauth\s*\(\s*\)/gi,
        description: 'Clerk auth() helper',
    },
    {
        namePattern: /^currentUser$/,
        callPattern: /\bcurrentUser\s*\(\s*\)/gi,
        description: 'Clerk currentUser() helper',
    },
    // NextAuth patterns
    {
        namePattern: /^getServerSession$/,
        callPattern: /\bgetServerSession\s*\(/gi,
        description: 'NextAuth getServerSession()',
    },
    {
        namePattern: /^getSession$/,
        callPattern: /\bgetSession\s*\(/gi,
        description: 'Session helper',
    },
    // Supabase patterns
    {
        namePattern: /^getUser$/,
        callPattern: /\bsupabase\.auth\.getUser\s*\(/gi,
        description: 'Supabase getUser()',
    },
];
/**
 * Patterns that indicate a function THROWS on missing auth
 */
const THROWING_INDICATORS = [
    /throw\s+new\s+(Error|UnauthorizedError|AuthError|HttpException)/i,
    /throw\s+.*401/i,
    /throw\s+.*unauthorized/i,
    /throw\s+.*unauthenticated/i,
    /return\s+.*401/i,
    /NextResponse\.json\s*\([^)]*401/i,
    /res\.status\s*\(\s*401\s*\)/i,
    /redirect\s*\(\s*['"`].*login/i,
];
/**
 * Patterns that indicate NON-NULL return type
 */
const NON_NULL_RETURN_PATTERNS = [
    /:\s*Promise<string>/i, // : Promise<string>
    /:\s*string(?!\s*\|)/i, // : string (not string | null)
    /:\s*Promise<User>/i, // : Promise<User>
    /:\s*User(?!\s*\|)/i, // : User (not User | null)
    /:\s*Promise<\w+>(?!\s*\|)/i, // : Promise<SomeType>
    /\w+!$/i, // Non-null assertion in return
];
// ============================================================================
// Detection Functions
// ============================================================================
/**
 * Detect auth helper functions in the codebase
 */
function detectAuthHelpers(files) {
    const helpers = [];
    const detectedNames = new Set();
    // First pass: find auth helper definitions
    for (const file of files) {
        // Skip non-code files
        if (!/\.(ts|tsx|js|jsx)$/i.test(file.path))
            continue;
        // Look for function definitions that look like auth helpers
        const functionMatches = findAuthHelperDefinitions(file.content, file.path);
        for (const helper of functionMatches) {
            if (!detectedNames.has(helper.name)) {
                detectedNames.add(helper.name);
                helpers.push(helper);
            }
        }
    }
    // Add well-known helpers if not already detected
    for (const pattern of THROWING_AUTH_HELPER_PATTERNS) {
        const nameMatch = pattern.namePattern.source.replace(/[\^\$\\b]/g, '');
        if (!detectedNames.has(nameMatch)) {
            // Check if this pattern is used in any file
            const isUsed = files.some(f => pattern.callPattern.test(f.content));
            if (isUsed) {
                helpers.push({
                    name: nameMatch,
                    throwsOnMissing: true, // Assume throwing for well-known patterns
                    returnsNonNull: true,
                    callPattern: pattern.callPattern,
                });
            }
        }
    }
    // Generate summary
    const throwingHelpers = helpers.filter(h => h.throwsOnMissing);
    const summary = generateAuthHelperSummary(helpers);
    return {
        helpers,
        hasThrowingHelpers: throwingHelpers.length > 0,
        summary,
    };
}
/**
 * Find auth helper function definitions in a file
 */
function findAuthHelperDefinitions(content, filePath) {
    const helpers = [];
    const lines = content.split('\n');
    // Patterns for function definitions
    const funcDefPatterns = [
        // async function getCurrentUserId(): Promise<string> { ... throw
        /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*(?::\s*([^{]+))?\s*\{/gi,
        // const getCurrentUserId = async (): Promise<string> => { ... throw
        /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*(?::\s*([^=]+))?\s*=>/gi,
    ];
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        for (const pattern of funcDefPatterns) {
            pattern.lastIndex = 0;
            const match = pattern.exec(line);
            if (!match)
                continue;
            const funcName = match[1];
            const returnType = match[2]?.trim();
            // Check if this looks like an auth helper by name
            const isAuthHelperName = THROWING_AUTH_HELPER_PATTERNS.some(p => p.namePattern.test(funcName));
            if (!isAuthHelperName)
                continue;
            // Look ahead for throwing patterns and return type
            const functionBody = extractFunctionBody(lines, i);
            const throwsOnMissing = THROWING_INDICATORS.some(p => p.test(functionBody));
            const returnsNonNull = returnType
                ? NON_NULL_RETURN_PATTERNS.some(p => p.test(`: ${returnType}`))
                : false;
            // Create call pattern for this helper
            const callPattern = new RegExp(`\\b${escapeRegex(funcName)}\\s*\\(`, 'gi');
            helpers.push({
                name: funcName,
                definedIn: filePath,
                throwsOnMissing,
                returnType,
                returnsNonNull: returnsNonNull || throwsOnMissing, // If it throws, the return is non-null
                callPattern,
            });
        }
    }
    return helpers;
}
/**
 * Extract function body for analysis (up to closing brace)
 */
function extractFunctionBody(lines, startLine, maxLines = 50) {
    let braceCount = 0;
    let started = false;
    const bodyLines = [];
    for (let i = startLine; i < Math.min(lines.length, startLine + maxLines); i++) {
        const line = lines[i];
        bodyLines.push(line);
        for (const char of line) {
            if (char === '{') {
                braceCount++;
                started = true;
            }
            else if (char === '}') {
                braceCount--;
                if (started && braceCount === 0) {
                    return bodyLines.join('\n');
                }
            }
        }
    }
    return bodyLines.join('\n');
}
/**
 * Check if a code line uses a throwing auth helper
 * Returns the helper if found, undefined otherwise
 */
function usesThrowingAuthHelper(lineContent, surroundingContent, helpers) {
    // Check if any throwing helper is called in the surrounding context
    const throwingHelpers = helpers.filter(h => h.throwsOnMissing);
    for (const helper of throwingHelpers) {
        helper.callPattern.lastIndex = 0;
        if (helper.callPattern.test(surroundingContent)) {
            return helper;
        }
    }
    return undefined;
}
/**
 * Well-known auth helper call patterns - used as fallback when no helpers are detected
 * These patterns are common across frameworks and should be recognized even without project analysis
 */
const WELL_KNOWN_AUTH_CALL_PATTERNS = [
    // Generic throwing auth patterns
    { pattern: /\bgetCurrentUserId\s*\(/i, name: 'getCurrentUserId' },
    { pattern: /\bgetCurrentUser\s*\(/i, name: 'getCurrentUser' },
    { pattern: /\brequireAuth\s*\(/i, name: 'requireAuth' },
    { pattern: /\brequireUser\s*\(/i, name: 'requireUser' },
    { pattern: /\bensureAuth\s*\(/i, name: 'ensureAuth' },
    { pattern: /\bensureAuthenticated\s*\(/i, name: 'ensureAuthenticated' },
    { pattern: /\bverifyAuth\s*\(/i, name: 'verifyAuth' },
    { pattern: /\bcheckAuth\s*\(/i, name: 'checkAuth' },
    { pattern: /\bvalidateAuth\s*\(/i, name: 'validateAuth' },
    { pattern: /\bassertAuth\s*\(/i, name: 'assertAuth' },
    { pattern: /\bgetAuth\s*\(/i, name: 'getAuth' },
    { pattern: /\bfetchCurrentUser\s*\(/i, name: 'fetchCurrentUser' },
    // Clerk
    { pattern: /\bauth\s*\(\s*\)\.protect\s*\(/i, name: 'auth().protect' },
    { pattern: /\bcurrentUser\s*\(\s*\)/i, name: 'currentUser' },
    // NextAuth
    { pattern: /\bgetServerSession\s*\(/i, name: 'getServerSession' },
    // Supabase
    { pattern: /\bsupabase\.auth\.getUser\s*\(/i, name: 'supabase.auth.getUser' },
    // Destructuring pattern
    { pattern: /const\s+\{\s*user\s*\}\s*=\s*await\s+auth/i, name: 'destructured auth' },
];
/**
 * Check if a file has auth helper calls before a given line
 * This indicates the code after the call is in authenticated context
 */
function hasAuthHelperCallBefore(content, lineNumber, helpers) {
    const lines = content.split('\n');
    const throwingHelpers = helpers.filter(h => h.throwsOnMissing);
    // Increase search window from 30 to 100 lines for better coverage
    const searchStart = Math.max(0, lineNumber - 100);
    // Look backwards from the current line
    for (let i = lineNumber - 1; i >= searchStart; i--) {
        const line = lines[i];
        // Check detected helpers first
        for (const helper of throwingHelpers) {
            helper.callPattern.lastIndex = 0;
            if (helper.callPattern.test(line)) {
                return { hasCall: true, helper, callLine: i + 1 };
            }
        }
        // Stop at function boundaries (for module-level helpers, we still check inside function)
        if (/\b(function|async function|=>|export\s+default)\b/.test(line) && /\{/.test(line)) {
            // If this is the route handler definition line, continue checking inside it
            if (i !== lineNumber - 1) {
                break;
            }
        }
    }
    // FORWARD SEARCH: Always search forward into the function body
    // This catches auth helpers called INSIDE the route handler, not just before it
    // Example: export async function GET() { const userId = await getCurrentUserId(); ... }
    const endLine = Math.min(lines.length, lineNumber + 30);
    for (let i = lineNumber; i < endLine; i++) {
        const line = lines[i];
        // Check detected helpers
        for (const helper of throwingHelpers) {
            helper.callPattern.lastIndex = 0;
            if (helper.callPattern.test(line)) {
                return { hasCall: true, helper, callLine: i + 1 };
            }
        }
        // Also check well-known patterns (fallback for single-file scans)
        for (const known of WELL_KNOWN_AUTH_CALL_PATTERNS) {
            if (known.pattern.test(line)) {
                return {
                    hasCall: true,
                    helper: {
                        name: known.name,
                        throwsOnMissing: true,
                        returnsNonNull: true,
                        callPattern: known.pattern,
                    },
                    callLine: i + 1
                };
            }
        }
        // Stop at another function boundary (but not the current line)
        if (i > lineNumber && /\bexport\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/i.test(line)) {
            break;
        }
    }
    return { hasCall: false };
}
/**
 * Generate summary for AI validation
 */
function generateAuthHelperSummary(helpers) {
    if (helpers.length === 0) {
        return 'No auth helper functions detected.';
    }
    const throwing = helpers.filter(h => h.throwsOnMissing);
    const lines = [];
    lines.push('### Auth Helper Functions');
    lines.push('');
    if (throwing.length > 0) {
        lines.push('**Throwing auth helpers** (guarantee authenticated context when called):');
        for (const h of throwing) {
            const location = h.definedIn ? ` (defined in ${h.definedIn})` : '';
            lines.push(`- \`${h.name}()\`${location}`);
        }
        lines.push('');
        lines.push('When these helpers are called at the start of a function, subsequent code is GUARANTEED to have an authenticated user. Do NOT flag "missing auth" or suggest `if (!userId)` checks after these calls.');
    }
    return lines.join('\n');
}
/**
 * Escape special regex characters
 */
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
 * Check if code suggests user ID is already validated
 * Detects patterns like: const userId = await getCurrentUserId()
 */
function isUserIdAlreadyValidated(content, lineNumber, helpers) {
    const lines = content.split('\n');
    const contextStart = Math.max(0, lineNumber - 20);
    const context = lines.slice(contextStart, lineNumber).join('\n');
    // Check for throwing helper calls
    const throwingHelpers = helpers.filter(h => h.throwsOnMissing);
    for (const helper of throwingHelpers) {
        helper.callPattern.lastIndex = 0;
        if (helper.callPattern.test(context)) {
            return true;
        }
    }
    // Check for common validation patterns
    const validationPatterns = [
        /const\s+(?:userId|user_id|currentUserId)\s*=\s*await/i,
        /if\s*\(\s*!(?:userId|user_id|user|session)\s*\)/i, // Already has the check
        /(?:userId|user_id)\s*\|\|\s*throw/i,
        /auth\(\)\.protect\(\)/i, // Clerk protect
    ];
    return validationPatterns.some(p => p.test(context));
}
//# sourceMappingURL=auth-helper-detector.js.map