/**
 * src/tools/email-browser.ts
 *
 * Browser-driven email fallback. When SMTP rejects the user's password
 * (common case: Gmail / Outlook / Yahoo blocked the regular password
 * because the account has 2FA on), we fall back to logging in through
 * the actual web UI via Playwright.
 *
 * One persistent browser profile per email address — kept under
 * <dataDir>/browser-state/<provider>/<safe-email>/ — so the second
 * send doesn't have to re-login. Cookies, localStorage, and IndexedDB
 * all persist between runs.
 *
 * Currently implements Gmail. Outlook, Yahoo, etc. would slot in next
 * to it as separate orchestrators.
 *
 * First send: headed window so the user can see the login + solve any
 * 2FA / device-verification challenge if Google flags us. After a
 * successful send, subsequent sends use the same profile so re-login
 * is rare. Set MUASSIS_EMAIL_HEADLESS=1 to force headless even on
 * first run.
 */
import { existsSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { createChildLogger } from '../utils/logger.js';
import { getDataDir } from '../utils/data-dir.js';
const log = createChildLogger('email-browser');
const NAVIGATION_TIMEOUT = 45_000;
const ACTION_TIMEOUT = 20_000;
function profileDirFor(email) {
    const safe = createHash('sha1').update(email.toLowerCase()).digest('hex').slice(0, 16);
    const dir = join(getDataDir(), 'browser-state', 'gmail', safe);
    mkdirSync(dir, { recursive: true });
    return dir;
}
/**
 * Drive Gmail's web UI to send an email. Reuses the saved persistent
 * context if one exists. Returns awaitingChallenge=true when Google
 * presents a 2FA / device-verification page — the caller relays that
 * back to the user as "tap the phone, then try again."
 *
 * Selectors are kept to the most stable ones I know. Gmail evolves;
 * these need maintenance every few quarters. Failures throw with a
 * screenshot path saved to <dataDir>/browser-state/gmail/<hash>/last-error.png
 * so the user can see exactly where the flow stopped.
 */
export async function sendEmailViaGmailWeb(args) {
    const { email, password, to, subject, body } = args;
    if (!email || !password) {
        return { ok: false, output: 'sendEmailViaGmailWeb: email + password required' };
    }
    // Discover bundled Playwright browsers if the env var wasn't set by the
    // wrapper. The .app's Tauri main.rs sets PLAYWRIGHT_BROWSERS_PATH on
    // spawn, but the dev sidecar / npx-run paths don't — so we self-locate.
    // Bundled location (in the .app): <gateway>/playwright-browsers/
    // npm fallback: ~/Library/Caches/ms-playwright/
    if (!process.env.PLAYWRIGHT_BROWSERS_PATH) {
        const { existsSync: ex } = await import('node:fs');
        const { fileURLToPath } = await import('node:url');
        const { dirname: dn, resolve: rs, join: jn } = await import('node:path');
        const here = dn(fileURLToPath(import.meta.url));
        const candidates = [
            rs(here, '../../playwright-browsers'), // dist sibling
            rs(here, '../../../playwright-browsers'), // sometimes one level higher
            jn(process.env.HOME || '', 'Library/Caches/ms-playwright'), // macOS default
            jn(process.env.HOME || '', '.cache/ms-playwright'), // linux default
        ];
        for (const c of candidates) {
            try {
                if (ex(c)) {
                    process.env.PLAYWRIGHT_BROWSERS_PATH = c;
                    log.info({ path: c }, 'auto-discovered bundled Playwright browsers');
                    break;
                }
            }
            catch { /* keep looking */ }
        }
    }
    const playwright = await import('playwright');
    const profileDir = profileDirFor(email);
    const headless = process.env.MUASSIS_EMAIL_HEADLESS === '1' || alreadyLoggedIn(profileDir);
    log.info({ email, profileDir, headless }, 'Launching Gmail browser');
    let context = null;
    let page = null;
    try {
        context = await playwright.chromium.launchPersistentContext(profileDir, {
            headless,
            viewport: { width: 1280, height: 900 },
            args: ['--disable-blink-features=AutomationControlled'],
        });
        context.setDefaultTimeout(ACTION_TIMEOUT);
        context.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT);
        page = context.pages()[0] ?? await context.newPage();
        // Try the compose URL directly — if we have a valid session, it
        // takes us straight to the compose modal. Otherwise we'll be
        // redirected to accounts.google.com.
        await page.goto('https://mail.google.com/mail/u/0/#inbox', { waitUntil: 'load' });
        if (page.url().includes('accounts.google.com') || page.url().includes('signin')) {
            const loginResult = await loginToGoogle(page, email, password);
            if (!loginResult.ok) {
                await captureFailureScreenshot(page, profileDir);
                return loginResult;
            }
            // After login wait for the inbox to load
            await page.waitForURL(/mail\.google\.com\/mail/, { timeout: NAVIGATION_TIMEOUT });
        }
        // We're in Gmail. Open compose.
        log.info({ email }, 'Logged in, opening compose');
        await openCompose(page);
        // Fill the fields
        await page.fill('input[name="to"], textarea[name="to"]', to);
        await page.fill('input[name="subjectbox"]', subject);
        // Body: Gmail's body is a contenteditable div. Click into it first.
        const bodyBox = page.locator('div[role="textbox"][aria-label*="Body" i], div[g_editable="true"]').first();
        await bodyBox.click();
        await bodyBox.fill(body);
        // Click Send. Multiple selectors because Gmail varies.
        const sendBtn = page.locator([
            '[data-tooltip="Send (⌘Enter)"]',
            '[data-tooltip^="Send"]',
            'div[role="button"][aria-label^="Send"]',
            'button:has-text("Send")',
        ].join(', ')).first();
        await sendBtn.click({ timeout: ACTION_TIMEOUT });
        // (Audit H4) Confirmation MUST be observed before declaring success.
        // The previous `.catch(() => null)` swallowed timeout errors, so the
        // function returned "sent" even when Gmail showed an error dialog.
        let confirmation = null;
        try {
            const winner = await Promise.race([
                page.waitForSelector('text=/Message sent|تم الإرسال/i', { timeout: 10_000 }).then(() => 'toast'),
                page.waitForSelector('input[name="subjectbox"]', { state: 'detached', timeout: 10_000 }).then(() => 'dialog-closed'),
            ]);
            confirmation = winner;
        }
        catch (waitErr) {
            const errSnap = page.url();
            log.warn({ to, err: waitErr.message, url: errSnap }, 'Gmail send confirmation never appeared');
            return {
                ok: false,
                output: `Gmail send: confirmation never appeared (waited 10s). Likely failed silently. ` +
                    `Open ${profileDir}/last-error.png to see Gmail's state.`,
            };
        }
        log.info({ to, subject, confirmation }, 'Email sent via Gmail web');
        return { ok: true, output: `تم إرسال البريد إلى ${to} عبر Gmail (مسار المتصفح، تأكيد: ${confirmation}).` };
    }
    catch (err) {
        if (page) {
            try {
                await captureFailureScreenshot(page, profileDir);
            }
            catch { /* ignore */ }
        }
        return {
            ok: false,
            output: `Browser-driven send failed: ${err instanceof Error ? err.message : String(err)}.\n` +
                `Screenshot saved to ${profileDir}/last-error.png — open it to see what Gmail showed.`,
        };
    }
    finally {
        if (context) {
            try {
                await context.close();
            }
            catch { /* ignore */ }
        }
    }
}
function alreadyLoggedIn(profileDir) {
    // Heuristic: if the profile dir has cookies, we've logged in once.
    // The actual check happens at runtime — if cookies are stale we get
    // redirected back to accounts.google.com and run the login flow.
    return existsSync(join(profileDir, 'Default', 'Cookies'));
}
async function loginToGoogle(page, email, password) {
    log.info({ email }, 'Running Google login flow');
    // Step 1: email
    try {
        await page.waitForSelector('input[type="email"]', { timeout: 15_000 });
        await page.fill('input[type="email"]', email);
        await page.locator('button:has-text("Next"), button:has-text("التالي"), #identifierNext').first().click();
    }
    catch {
        return { ok: false, output: 'Could not find Gmail email input — Google may have changed the page or blocked the request.' };
    }
    // Step 2: password
    try {
        await page.waitForSelector('input[type="password"]', { timeout: 20_000 });
        await page.fill('input[type="password"]', password);
        await page.locator('button:has-text("Next"), button:has-text("التالي"), #passwordNext').first().click();
    }
    catch {
        return { ok: false, output: 'Password input did not appear — possibly an "account not found" or rate-limit page.' };
    }
    // Step 3: handle outcomes
    // Wait for any of: success URL, 2FA challenge, "wrong password", "device verify"
    const outcome = await Promise.race([
        page.waitForURL(/mail\.google\.com\/mail/, { timeout: 30_000 }).then(() => 'success'),
        page.waitForSelector('text=/Wrong password|Couldn.?t find your account|كلمة المرور غير صحيحة/i', { timeout: 30_000 }).then(() => 'bad-creds'),
        page.waitForSelector('text=/2.Step|Verify it.s you|تأكّد|Confirm you.re not a robot|التحقّق|Use your phone/i', { timeout: 30_000 }).then(() => 'challenge'),
    ]).catch(() => 'unknown');
    switch (outcome) {
        case 'success':
            return { ok: true, output: 'logged in' };
        case 'bad-creds':
            return {
                ok: false,
                output: 'Google rejected the password. If your account has 2FA, the regular password won\'t work — ' +
                    'use an App Password (https://myaccount.google.com/apppasswords) and paste as EMAIL_PASSWORD=…',
            };
        case 'challenge':
            return {
                ok: false,
                awaitingChallenge: true,
                output: 'Google asked for phone verification (tap the prompt on your phone, or enter the code Gmail sent). ' +
                    'After you complete it, ask me to send the email again — the session is saved and I won\'t need to log in.',
            };
        default:
            return {
                ok: false,
                output: 'Gmail login flow ended in an unexpected state. Open the screenshot to see what Google showed.',
            };
    }
}
async function openCompose(page) {
    // Best stable Gmail selector for "Compose" is gh="cm".
    const compose = page.locator([
        '[gh="cm"]',
        'div[role="button"]:has-text("Compose")',
        'div[role="button"]:has-text("إنشاء")',
    ].join(', ')).first();
    await compose.click({ timeout: ACTION_TIMEOUT });
    // Wait for the compose dialog to appear
    await page.waitForSelector('input[name="subjectbox"]', { timeout: ACTION_TIMEOUT });
}
async function captureFailureScreenshot(page, profileDir) {
    const path = join(profileDir, 'last-error.png');
    await page.screenshot({ path, fullPage: false });
    log.info({ path }, 'Saved Gmail failure screenshot');
}
//# sourceMappingURL=email-browser.js.map