import { loadCredentials } from "../lib/credentials.js";
import { fetchRecent, stripHtml } from "../lib/imap.js";
const POLL_INTERVAL_MS = 5_000;
/**
 * OTP context patterns — phrases that typically surround a verification code.
 * The regex captures the numeric code adjacent to these contextual markers.
 * None use the /g flag — they are stateless and safe for repeated exec() calls.
 */
const OTP_CONTEXT_PATTERNS = [
    /(?:verification|verify|security|confirmation|one[- ]time|otp|login|sign[- ]?in|access)\s*(?:code|pin|number|token)\s*(?:is|:)?\s*(\d{4,8})/i,
    /(\d{4,8})\s*(?:is your|is the)\s*(?:verification|security|confirmation|one[- ]time|otp|login|sign[- ]?in)/i,
    /(?:code|pin|token)\s*(?:is|:)\s*(\d{4,8})/i,
    /(?:enter|use|input|type)\s*(?:the\s*)?(?:code\s*)?(\d{4,8})/i,
];
function extractOtpFromText(text) {
    // Try context-aware patterns first
    for (const pattern of OTP_CONTEXT_PATTERNS) {
        const match = pattern.exec(text);
        if (match?.[1])
            return match[1];
    }
    // Fallback: extract standalone 4-8 digit numbers, filtering false positives.
    // Pattern created locally to avoid /g lastIndex state leaking between calls.
    const standalonePattern = /\b(\d{4,8})\b/g;
    const matches = [];
    let match;
    while ((match = standalonePattern.exec(text)) !== null) {
        const num = match[1];
        // Filter out likely years (1900-2099)
        if (/^(19|20)\d{2}$/.test(num))
            continue;
        // Filter out numbers that are part of a phone-like sequence
        const surrounding = text.slice(Math.max(0, match.index - 5), match.index + num.length + 5);
        if (/\+?\d[\d\s\-()]{9,}/.test(surrounding))
            continue;
        matches.push(num);
    }
    // If exactly one candidate remains, it's likely the OTP
    if (matches.length === 1)
        return matches[0];
    // Multiple candidates — prefer 6-digit codes (most common OTP length)
    const sixDigit = matches.find((m) => m.length === 6);
    if (sixDigit)
        return sixDigit;
    // Return the first candidate if any exist
    return matches[0] ?? null;
}
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function emailOtpExtract(params) {
    const result = await loadCredentials(params.accountId);
    if ("error" in result) {
        return { success: false, message: result.error };
    }
    const { config, password } = result.credentials;
    const timeoutMs = (params.timeout ?? 60) * 1000;
    const senderDomain = params.sender.toLowerCase();
    const startTime = Date.now();
    const to = config.agentAddress !== config.email ? config.agentAddress : undefined;
    // Compile subject regex once (not per-message) — also avoids ReDoS on each iteration
    let subjectRegex = null;
    if (params.subjectPattern) {
        try {
            subjectRegex = new RegExp(params.subjectPattern, "i");
        }
        catch {
            // Invalid regex — ignore filter
        }
    }
    // Phase 1: Check recent messages (last 5 minutes) for an OTP that already arrived
    const recentSince = new Date(Date.now() - 5 * 60 * 1000);
    const { messages: recentMessages } = await fetchRecent(config, password, {
        limit: 10,
        since: recentSince,
        to,
    });
    for (const msg of recentMessages) {
        if (!msg.from.toLowerCase().includes(senderDomain))
            continue;
        if (subjectRegex && !subjectRegex.test(msg.subject))
            continue;
        const text = stripHtml(msg.bodyPreview);
        const code = extractOtpFromText(text);
        if (code) {
            return {
                success: true,
                code,
                message: `OTP extracted: ${code}`,
                from: msg.from,
                subject: msg.subject,
            };
        }
    }
    // Phase 2: Poll for new messages until timeout
    // Track the best "found but no code" match to report if timeout expires
    let bestNoCodeMatch = null;
    while (Date.now() - startTime < timeoutMs) {
        await sleep(POLL_INTERVAL_MS);
        const { messages } = await fetchRecent(config, password, {
            limit: 5,
            since: recentSince,
            to,
        });
        for (const msg of messages) {
            if (!msg.from.toLowerCase().includes(senderDomain))
                continue;
            if (subjectRegex && !subjectRegex.test(msg.subject))
                continue;
            const text = stripHtml(msg.bodyPreview);
            const code = extractOtpFromText(text);
            if (code) {
                return {
                    success: true,
                    code,
                    message: `OTP extracted: ${code}`,
                    from: msg.from,
                    subject: msg.subject,
                };
            }
            // Found a matching email but no code — record it but keep polling
            if (!bestNoCodeMatch) {
                bestNoCodeMatch = { from: msg.from, subject: msg.subject, preview: text.slice(0, 200) };
            }
        }
    }
    // Timeout reached
    const elapsed = Math.round((Date.now() - startTime) / 1000);
    if (bestNoCodeMatch) {
        return {
            success: false,
            message: `Found email from ${bestNoCodeMatch.from} ("${bestNoCodeMatch.subject}") but could not extract a verification code within ${elapsed}s. Body preview: ${bestNoCodeMatch.preview}`,
            from: bestNoCodeMatch.from,
            subject: bestNoCodeMatch.subject,
        };
    }
    return {
        success: false,
        message: `No verification email from *${senderDomain}* received within ${elapsed}s. Check the email address is correct, or ask the service to resend the code.`,
    };
}
//# sourceMappingURL=email-otp-extract.js.map