/**
 * auto-token.js — Kling Motion Auto Token Bot (v6 — Figma Sign-In)
 *
 * NEW FLOW:
 *   1. Bot buka figma.com/signup → isi email + password → Submit
 *   2. Verifikasi email Figma via link dari ICLOUD inbox
 *   3. Handle Figma onboarding (skip, free plan)
 *   4. Buka app.weavy.ai/signin → klik "Log in with Figma"
 *   5. Figma OAuth (auto-authorize) → redirect ke Weavy
 *   6. Tangkap Bearer token → simpan ke pool
 *
 * Admin menyediakan daftar email via textarea di dashboard Tools Akun.
 * Bot memproses email satu per satu secara berurutan.
 */

import { chromium } from "playwright";
import * as Accounts from "./account-store.js";
import { loadIcloudSettings } from "./icloud-settings.js";
import { readFileSync } from "fs";
import { fileURLToPath } from "url";
import { dirname, join } from "path";

const __dirname = dirname(fileURLToPath(import.meta.url));

const WEAVY_API        = process.env.WEAVY_API_URL || "https://api.weavy.ai";
const WEAVY_SIGNIN     = "https://app.weavy.ai/signin";
const DEFAULT_RECIPE_ID = process.env.WEAVY_RECIPE_ID || "G6jw67AIcRhGA8eKDF0WEu";
const isHeadless        = () => process.env.PLAYWRIGHT_HEADLESS !== "false";

// ICLOUD API config (digunakan untuk fetch inbox via server API, bukan Puppeteer)
// Dibaca dari settings JSON (admin dashboard), fallback ke .env
const SERVER_PORT     = process.env.PORT || "3002";

function getIcloudConfig() {
  const s = loadIcloudSettings();
  return {
    apiUrl: s.apiUrl || process.env.ICLOUD_API_URL || `http://localhost:${SERVER_PORT}`,
    session: s.sessionToken || process.env.ICLOUD_SESSION_TOKEN || "",
    device: s.deviceId || process.env.ICLOUD_DEVICE_ID || "",
    account: s.accountId || process.env.ICLOUD_ACCOUNT_ID || "",
  };
}
const OWNER_PASSWORD  = process.env.OWNER_PASSWORD || "4rg0v14N"; // Owner password untuk auth admin endpoint

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

let activeBrowser = null;

export async function stopActiveBot() {
  if (activeBrowser) {
    try {
      await activeBrowser.close();
      activeBrowser = null;
      return { ok: true, msg: "Bot dihentikan paksa." };
    } catch (e) {
      return { ok: false, error: e.message };
    }
  }
  return { ok: false, error: "Tidak ada bot yang sedang berjalan." };
}

function makeLogger(emit) {
  return (msg, type = "info") => {
    const time = new Date().toLocaleTimeString("id-ID");
    const line = `[${time}] ${msg}`;
    console.log(`[AutoToken] ${line}`);
    if (emit) emit({ type, msg: line, raw: msg });
  };
}

// ─── CAPTCHA Detection (comprehensive) ─────────────────────────────

async function detectCaptchaChallenge(page) {
  try {
    const url = page.url().toLowerCase();
    if (
      url.includes("captcha") ||
      url.includes("challenge") ||
      url.includes("turnstile") ||
      url.includes("recaptcha") ||
      url.includes("hcaptcha") ||
      url.includes("arkose") ||
      url.includes("funcaptcha") ||
      url.includes("verify")
    ) {
      return true;
    }
    const bodyText = await page.evaluate(() => document.body?.innerText || "");
    const lower = bodyText.toLowerCase();
    const keywords = [
      "captcha", "verify you are human", "i am not a robot", "bukan robot",
      "sedang memverifikasi", "verification", "turnstile", "challenge",
      "press and hold", "tekan dan tahan", "cloudflare", "hcaptcha",
      "arkose", "funcaptcha", "are you human"
    ];
    for (const kw of keywords) {
      if (lower.includes(kw)) return true;
    }
    return false;
  } catch {
    return false;
  }
}

// ─── Stealth Anti-Detection Script ──────────────────────────────────
// Comprehensive hiding of WebDriver/Playwright automation traces
const STEALTH_SCRIPT = `
// 1. Overwrite navigator.webdriver
Object.defineProperty(navigator, 'webdriver', { get: () => false });

// 2. Overwrite chrome.runtime
window.chrome = {
  runtime: { onConnect: { addListener: () => {} }, onMessage: { addListener: () => {} } },
  loadTimes: () => {},
  csi: () => {},
  app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } },
};

// 3. Overwrite navigator.plugins (realistic length)
const fakePlugins = [
  { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
  { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
  { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
];
Object.defineProperty(navigator, 'plugins', {
  get: () => {
    const arr = fakePlugins.map(p => {
      const plugin = {
        name: p.name, filename: p.filename, description: p.description, length: 1,
        0: { type: '', suffixes: '' },
        namedItem: () => null, item: () => null,
      };
      return plugin;
    });
    arr.item = (i) => arr[i] || null;
    arr.namedItem = () => null;
    arr.refresh = () => {};
    Object.setPrototypeOf(arr, PluginArray.prototype);
    return arr;
  },
});

// 4. Overwrite navigator.languages
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en', 'id'] });

// 5. Overwrite navigator.platform
Object.defineProperty(navigator, 'platform', { get: () => 'Win32' });

// 6. Overwrite navigator.vendor
Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.' });

// 7. Overwrite navigator.hardwareConcurrency (realistic)
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });

// 8. Overwrite navigator.deviceMemory
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });

// 9. Overwrite permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
  parameters.name === 'notifications' ?
    Promise.resolve({ state: Notification.permission }) :
    originalQuery(parameters)
);

// 10. Remove Playwright-specific properties
delete window.__playwright;
delete window.__pw_manual;
delete window.__PW_inspect;
`;

// ─── Stealth context factory ────────────────────────────────────────
async function createStealthContext(browser) {
  const context = await browser.newContext({
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    viewport: { width: 1280, height: 720 },
    locale: "en-US",
    timezoneId: "Asia/Jakarta",
    permissions: ["geolocation", "notifications"],
    geolocation: { latitude: -6.2088, longitude: 106.8456 },
    colorScheme: "light",
    deviceScaleFactor: 1,
    isMobile: false,
    hasTouch: false,
  });
  context.setDefaultTimeout(30000);

  // Inject stealth BEFORE any page script runs (lebih kuat dari page event)
  await context.addInitScript(STEALTH_SCRIPT);

  return context;
}

// ─── Helper: get launch options ─────────────────────────────────────

function getLaunchOptions(hl) {
  const options = {
    headless: hl,
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-blink-features=AutomationControlled",
      "--disable-features=IsolateOrigins,site-per-process",
      "--disable-site-isolation-trials",
      "--disable-web-security",
      "--disable-features=BlockInsecurePrivateNetworkRequests",
      "--disable-features=OutOfBlinkCors",
      "--disable-component-update",
      "--disable-domain-reliability",
      "--disable-sync",
      "--no-default-browser-check",
      "--no-first-run",
      "--disable-default-apps",
      "--disable-popup-blocking",
      "--disable-prompt-on-repost",
      "--disable-hang-monitor",
      "--disable-client-side-phishing-detection",
      "--disable-infobars",
      "--disable-breakpad",
      "--disable-dev-shm-usage",
      "--mute-audio",
      "--window-size=1280,720",
      "--start-maximized",
    ],
    slowMo: 50,
  };

  // Proxy support
  let hasProxies = false;
  try {
    const proxyFilePath = join(__dirname, "../proxies.txt");
    const content = readFileSync(proxyFilePath, "utf8");
    const lines = content.split(/\r?\n/).map(l => l.trim()).filter(l => l && !l.startsWith("#"));
    if (lines.length > 0) hasProxies = true;
  } catch (_e) {}

  if (process.env.PROXY_ENABLED === "true" || hasProxies) {
    let proxySelected = null;
    try {
      const proxyFilePath = join(__dirname, "../proxies.txt");
      const content = readFileSync(proxyFilePath, "utf8");
      const lines = content.split(/\r?\n/).map(l => l.trim()).filter(l => l && !l.startsWith("#"));
      if (lines.length > 0) {
        const randomLine = lines[Math.floor(Math.random() * lines.length)];
        const parts = randomLine.split(":");
        if (parts.length >= 2) {
          proxySelected = {
            server: `http://${parts[0]}:${parts[1]}`,
            username: parts[2] || "",
            password: parts[3] || ""
          };
        }
      }
    } catch (_e) {}
    if (!proxySelected && process.env.PROXY_SERVER) {
      proxySelected = {
        server: process.env.PROXY_SERVER,
        username: process.env.PROXY_USERNAME || "",
        password: process.env.PROXY_PASSWORD || ""
      };
    }
    if (proxySelected) {
      options.proxy = { server: proxySelected.server };
      if (proxySelected.username) options.proxy.username = proxySelected.username;
      if (proxySelected.password) options.proxy.password = proxySelected.password;
    }
  }

  return options;
}

// ─── Step 1: Fetch Google OOB Verification Code via ICLOUD API ──────
//
//   MENGGUNAKAN ICLOUD API LANGSUNG (via server endpoint), BUKAN Puppeteer.
//   API List:   GET  /api/inbox/universal
//   API Thread: POST /api/inbox/universal/thread
//
//   JAUH LEBIH CEPAT & RELIABLE daripada klik-klik UI iframe.

async function fetchGoogleVerificationCode(targetEmail, log, skipThreadIds = []) {
  log(`📨 Polling ICLOUD API untuk kode verifikasi ${targetEmail}...`);
  const skipIds = new Set(Array.isArray(skipThreadIds) ? skipThreadIds : []);

  // Helper ekstrak kode (sama dengan di server.js)
  function extractCode(text) {
    if (!text) return null;
    const patterns = [
      /G[-\s]*(\d{6})\b/i,
      /(\d{6})\s+is\s+(?:your|the)\s+(?:Google|verification)\s+code/i,
      /(?:Google|verification)\s+code\s+(?:is\s+)?(\d{6})\b/i,
      /one.?time\s+(?:verification\s+)?code\s*(?:is)?\s*[:：]\s*([A-Z0-9]{6})\b/i,
      /verification\s+code\s*(?:is)?\s*[:：]\s*([A-Z0-9]{6})\b/i,
      /code\s*(?:is)?\s*[:：]\s*([A-Z0-9]{6})\b/i,
    ];
    for (const p of patterns) {
      const m = text.match(p);
      if (m && m[1] && /^[A-Z0-9]{6}$/.test(m[1])) {
        const upper = m[1].toUpperCase();
        if (["GOOGLE","VERIFY","CLOUD","PLEASE","SECURE","ACCESS","SIGNIN"].includes(upper)) continue;
        return upper;
      }
    }
    const idx = text.toLowerCase().search(/verification|one.?time\s+code|google\s+code/i);
    if (idx >= 0) {
      const snippet = text.substring(idx, idx + 350);
      const mDigit = snippet.match(/\b(\d{6})\b/);
      if (mDigit && mDigit[1]) return mDigit[1];
      const m6 = snippet.match(/\b([A-Z0-9]{6})\b/);
      if (m6 && m6[1] && !["GOOGLE","VERIFY","CLOUD","PLEASE","SECURE","ACCESS","SIGNIN"].includes(m6[1])) return m6[1];
    }
    const mAnyDigit = text.match(/\b(\d{6})\b/);
    if (mAnyDigit && mAnyDigit[1]) return mAnyDigit[1];
    return null;
  }

  // Opsi 1: Panggil server endpoint local (lebih simpel, 1 HTTP call)
  try {
    const resp = await fetch(`http://localhost:${SERVER_PORT}/api/admin/icloud/find-code`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-admin-password": OWNER_PASSWORD,
      },
      body: JSON.stringify({ email: targetEmail, skipThreadIds: [...skipIds] }),
    });
    const data = await resp.json();
    if (data.ok && data.code) {
      log(`🎯 KODE DITEMUKAN via server API: ${data.code} (attempt ${data.attempt}, thread ${data.threadId || '?'})`);
      return data.code;
    }
  } catch (e) {
    log(`⚠️ Server API gagal: ${e.message}, fallback ke direct ICLOUD API...`, "warn");
  }

  // Opsi 2: Direct ICLOUD API call (fallback)
  const cfg = getIcloudConfig();
  if (!cfg.session) {
    throw new Error("ICLOUD_SESSION_TOKEN tidak di-set (via Admin Dashboard atau .env)");
  }

  const headers = {
    Authorization: `Bearer ${cfg.session}`,
    "x-device-id": cfg.device,
    "x-account-id": cfg.account,
    "Content-Type": "application/json",
  };

  const maxAttempts = 30;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await sleep(3000);
    log(`🔍 Direct API poll #${attempt + 1}...`);

    try {
      // 1. Thread list
      const listResp = await fetch(`${cfg.apiUrl}/api/inbox/universal`, { headers });
      const listData = await listResp.json();

      // Deteksi session invalid — hentikan polling segera
      if (listData.success === false || listData.error) {
        log(`❌ ICLOUD API Error: ${listData.error || listData.message || "session invalid"}`, "error");
        break;
      }

      let threads = listData.data || listData || [];
      if (!Array.isArray(threads) || !threads.length) continue;

      // ── SORT DESC + SKIP attempted threads ──
      threads = threads
        .filter(t => !skipIds.has(t.id || t.threadId))
        .sort((a, b) => {
          const ad = new Date(a.date || a.timestamp || 0).getTime();
          const bd = new Date(b.date || b.timestamp || 0).getTime();
          return bd - ad;
        });

      if (!threads.length) continue;

      // 2. Match thread (newest first)
      const searchStr = (t) => [t.subject, t.to, t.from, t.text].join(" ").toLowerCase();
      const emailLower = targetEmail.toLowerCase();
      const matched = threads.find(t => searchStr(t).includes(emailLower))
        || threads.find(t => searchStr(t).includes("google") || searchStr(t).includes("verification"));

      if (!matched) continue;

      // 3. Thread content
      const threadResp = await fetch(`${cfg.apiUrl}/api/inbox/universal/thread`, {
        method: "POST",
        headers,
        body: JSON.stringify({ threadId: matched.id || matched.threadId }),
      });
      const threadData = await threadResp.json();
      const msgs = threadData.data || threadData || [];
      const msgArray = Array.isArray(msgs) ? msgs : (msgs.messages || []);
      const rawContent = msgArray.map((m) => m.text || m.body || m.html || "").join("\n");
      const content = rawContent.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();

      if (!content || !content.toLowerCase().includes(emailLower)) continue;

      // 4. Extract code using shared helper
      const code = extractCode(content);
      if (code) {
        log(`🎯 KODE DITEMUKAN via direct API: ${code}`);
        return code;
      }
    } catch (e) {
      log(`⚠️ Direct API poll #${attempt + 1} error: ${e.message}`, "warn");
    }
  }

  throw new Error(`Kode verifikasi tidak ditemukan untuk ${targetEmail} setelah ${maxAttempts * 3} detik.`);
}

// ─── Step 2.5: Sign-in ke Figma dengan email + password ─────────────
//
//   Login ke figma.com untuk mendapatkan session Figma,
//   lalu session ini dipakai untuk OAuth ke Weavy.
//   Menangani OTP verifikasi Figma via iCloud inbox.

async function fetchFigmaOtpCode(email, log) {
  log(`📨 Polling ICLOUD API untuk kode OTP Figma...`);

  // Helper: deteksi false positive (sama dengan di server.js)
  function isFalsePositive(code) {
    if (!code) return true;
    const blacklist = [
      "000000", "111111", "222222", "333333", "444444", "555555",
      "666666", "777777", "888888", "999999", "123456", "654321",
    ];
    if (blacklist.includes(code)) return true;
    if (/^(\d{2})\1\1$/.test(code)) return true; // 232323, 121212, etc
    if (code.length === 6 && code.substring(0, 3) === code.substring(3, 6)) return true; // 123123
    return false;
  }
  
  const maxAttempts = 30;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await sleep(3000);
    if (attempt % 3 === 0) log(`🔍 Poll OTP Figma #${attempt + 1}...`);

    try {
      const resp = await fetch(`http://localhost:${SERVER_PORT}/api/admin/icloud/find-code`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-admin-password": OWNER_PASSWORD,
        },
        body: JSON.stringify({ email, skipThreadIds: [], searchKeyword: "figma" }),
      });
      const data = await resp.json();

      if (data.ok) {
        // API udah ekstrak kode — langsung pakai
        if (data.code) {
          log(`🎯 Kode OTP Figma ditemukan: ${data.code}`);
          return data.code;
        }

        // Fallback: cari di rawContent (old format)
        if (data.rawContent) {
          const raw = data.rawContent;
          // Cari semua kandidat 6-7 digit, ambil yang TERAKHIR
          const allMatches = [...raw.matchAll(/\b(\d{6,7})\b/g)];
          for (let i = allMatches.length - 1; i >= 0; i--) {
            const candidate = allMatches[i][1];
            if (!isFalsePositive(candidate)) {
              log(`🎯 Kode OTP Figma ditemukan: ${candidate}`);
              return candidate;
            }
          }
        }
      }
    } catch (e) {
      log(`⚠️ Poll OTP #${attempt + 1} error: ${e.message}`, "warn");
    }
  }

  // Fallback: direct ICLOUD API
  const cfg = getIcloudConfig();
  if (cfg.session) {
    const headers = {
      Authorization: `Bearer ${cfg.session}`,
      "x-device-id": cfg.device,
      "x-account-id": cfg.account,
      "Content-Type": "application/json",
    };

    for (let attempt = 0; attempt < 10; attempt++) {
      await sleep(4000);
      try {
        const listResp = await fetch(`${cfg.apiUrl}/api/inbox/universal`, { headers });
        const listData = await listResp.json();
        let threads = listData.data || listData || [];
        if (!Array.isArray(threads) || !threads.length) continue;

        threads = threads.sort((a, b) => {
          const ad = new Date(a.date || a.timestamp || 0).getTime();
          const bd = new Date(b.date || b.timestamp || 0).getTime();
          return bd - ad;
        });

        const figmaThread = threads.find(t => {
          const s = [t.subject, t.from, t.to].join(" ").toLowerCase();
          return s.includes("figma") || s.includes("authentication");
        });

        if (figmaThread) {
          const threadResp = await fetch(`${cfg.apiUrl}/api/inbox/universal/thread`, {
            method: "POST", headers,
            body: JSON.stringify({ threadId: figmaThread.id || figmaThread.threadId }),
          });
          const threadData = await threadResp.json();
          const msgs = threadData.data || threadData || [];
          const msgArray = Array.isArray(msgs) ? msgs : (msgs.messages || []);
          const raw = msgArray.map((m) => m.text || m.body || m.html || "").join("\n");
          const content = raw.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();

          const codeMatch = content.match(/\b(\d{6,7})\b/);
          if (codeMatch && codeMatch[1] && !isFalsePositive(codeMatch[1])) {
            log(`🎯 Kode OTP Figma via direct API: ${codeMatch[1]}`);
            return codeMatch[1];
          }
          // Fallback: cari semua kandidat, ambil terakhir
          const allMatches = [...content.matchAll(/\b(\d{6,7})\b/g)];
          for (let i = allMatches.length - 1; i >= 0; i--) {
            if (!isFalsePositive(allMatches[i][1])) {
              log(`🎯 Kode OTP Figma via direct API (last): ${allMatches[i][1]}`);
              return allMatches[i][1];
            }
          }
        }
      } catch (e) {
        log(`⚠️ Direct OTP poll #${attempt + 1} error: ${e.message}`, "warn");
      }
    }
  }

  throw new Error("Kode OTP Figma tidak ditemukan di inbox.");
}

async function signInFigma(email, password, page, log) {
  log(`🔐 Login ke Figma: ${email}...`);

  // ── 1. Navigasi ke figma.com, klik "Log in" button ──
  log("📍 Navigasi ke figma.com...");
  await page.goto("https://www.figma.com/", { waitUntil: "domcontentloaded", timeout: 30000 });
  await sleep(4000);

  // Klik "Log in" button di header (top-right)
  log("🖱️ Klik 'Log in' di header...");
  let loginClicked = false;
  for (const sel of [
    "a:has-text('Log in')",
    "a[href*='login']",
    "::-p-text(Log in)",
  ]) {
    try {
      const loc = page.locator(sel);
      if (await loc.count() > 0) {
        await loc.first().click();
        log(`✅ 'Log in' diklik via: ${sel}`);
        loginClicked = true;
        break;
      }
    } catch (_e) {}
  }

  if (!loginClicked) {
    // Coba navigasi langsung ke login
    log("⚠️ Tombol 'Log in' tidak ditemukan, coba figma.com/login...");
    await page.goto("https://www.figma.com/login", { waitUntil: "domcontentloaded", timeout: 30000 });
  }

  await sleep(4000);

  // ── 2. Form login Figma ada di IFRAME ──
  log("🔍 Mencari iframe form login Figma...");
  let frame = null;

  // Tunggu iframe muncul
  for (let i = 0; i < 15; i++) {
    const frames = page.frames();
    for (const f of frames) {
      if (f !== page.mainFrame()) {
        try {
          const hasEmail = await f.locator("#email").count();
          if (hasEmail > 0) {
            frame = f;
            log(`✅ Iframe login Figma ditemukan!`);
            break;
          }
        } catch (_e) {}
      }
    }
    if (frame) break;
    await sleep(1000);
  }

  if (!frame) {
    // Fallback: coba isi di main page
    log("⚠️ Iframe tidak ditemukan, coba form di main page...");
    frame = page;
  }

  // ── 3. Isi email di dalam iframe ──
  log(`📝 Mengisi email: ${email}...`);
  let emailFilled = false;
  for (const sel of ["#email", "input[type='email']", "input[name='email']"]) {
    try {
      const loc = frame.locator(sel);
      if (await loc.count() > 0) {
        await loc.first().click();
        await sleep(200);
        await loc.first().fill(email);
        log(`✅ Email diisi via: ${sel}`);
        emailFilled = true;
        break;
      }
    } catch (_e) {}
  }

  if (!emailFilled) throw new Error("Input email tidak ditemukan di form login Figma (iframe).");

  await sleep(1000);

  // ── 4. Klik "Continue with email" ──
  log("🖱️ Klik 'Continue with email'...");
  let continueClicked = false;
  for (const sel of [
    "button:has-text('Continue with email')",
    "::-p-text(Continue with email)",
    "button[type='submit']",
  ]) {
    try {
      const loc = frame.locator(sel);
      if (await loc.count() > 0) {
        await loc.first().click();
        log(`✅ Continue via: ${sel}`);
        continueClicked = true;
        break;
      }
    } catch (_e) {}
  }

  if (!continueClicked) {
    // Fallback: cari button dengan teks Continue
    const buttons = await frame.locator("button");
    const cnt = await buttons.count();
    for (let i = 0; i < cnt; i++) {
      const text = (await buttons.nth(i).textContent()).toLowerCase();
      if (text.includes("continue") || text.includes("email") || text.includes("log in")) {
        await buttons.nth(i).click();
        log(`✅ Continue via button index ${i}: "${text}"`);
        continueClicked = true;
        break;
      }
    }
  }

  await sleep(3000);

  // ── 5. Isi password (#current-password di iframe) ──
  log(`📝 Mengisi password...`);
  let passFilled = false;
  for (const sel of [
    "#current-password",
    "input[type='password']",
    "input[name='password']",
    "#password",
  ]) {
    try {
      const loc = frame.locator(sel);
      if (await loc.count() > 0) {
        await loc.first().click();
        await sleep(200);
        await loc.first().fill(password);
        log(`✅ Password diisi via: ${sel}`);
        passFilled = true;
        break;
      }
    } catch (_e) {}
  }

  if (!passFilled) {
    log("⚠️ Input password tidak ditemukan.", "warn");
  }

  await sleep(500);

  // ── 6. PROACTIVE OTP: Mulai polling iCloud SEBELUM submit login ──
  //     Strategi: start iCloud fetch IMMEDIATELY di background SEBELUM submit,
  //     supaya kode OTP punya waktu maksimum untuk sampai di inbox.
  log("📨 Memulai polling iCloud untuk kode OTP Figma (background)...");
  const otpPromise = fetchFigmaOtpCode(email, log).catch((err) => {
    log(`⚠️ Background OTP fetch gagal: ${err.message}`, "warn");
    return null;
  });

  // ── 7. Submit form login di IFRAME ──
  //     PRIORITAS: klik button submit di iframe, BUKAN page.keyboard
  //     Rekaman Lighthouse: setelah iframe submit, main page → figma.com/login?is_not_gen_0=true
  log("🖱️ Submit form login di iframe...");
  let formSubmitted = false;

  // 7a. Cari dan klik button "Log in" / submit di dalam iframe
  for (const sel of [
    "button[type='submit']",
    "button:has-text('Log in')",
    "::-p-text(Log in)",
    "button:has-text('Continue')",
    "button",
  ]) {
    try {
      const loc = frame.locator(sel);
      if (await loc.count() > 0) {
        await loc.first().click({ timeout: 3000 });
        log(`✅ Form submit via iframe button: ${sel}`);
        formSubmitted = true;
        break;
      }
    } catch (_e) {}
  }

  if (!formSubmitted) {
    // 7b. Tekan Enter DI DALAM iframe (bukan page)
    log("⌨️ Fallback: tekan Enter di iframe...");
    try {
      // Klik ulang input password untuk pastiin fokus di iframe
      const passInput = frame.locator("#current-password");
      if (await passInput.count() > 0) {
        await passInput.first().click();
        await sleep(200);
      }
      await frame.locator("body").press("Enter");
      log("✅ Enter ditekan di iframe.");
      formSubmitted = true;
    } catch (_e) {}
  }

  if (!formSubmitted) {
    // 7c. Last-resort: page.keyboard
    log("⌨️ Last-resort: tekan Enter di page...");
    await page.keyboard.press("Enter");
  }

  // Polling URL/page untuk deteksi halaman OTP Figma
  // ── STRATEGI BARU (rekaman 23-Jun-2026):
  //     OTP modal (statsig-comp-without-redux) muncul di figma.com/ BUKAN di URL terpisah.
  //     Statsig component bisa ada di MAIN page ATAU nested frame (frame[8]).
  //     Deteksi PRIMER: cari statsig di SEMUA frame. URL check hanya sekunder.
  let otpPageDetected = false;
  let statsigFrame = null; // frame tempat statsig ditemukan
  const MAX_POLL_ITERATIONS = 25;
  const POLL_INTERVAL_MS = 2000;

  // Helper: cari statsig component di SEMUA frame
  async function findStatsigInAnyFrame(pg) {
    // Cek main frame dulu
    try {
      const cnt = await pg.locator("[data-testid='statsig-comp-without-redux']").count().catch(() => 0);
      if (cnt > 0) return pg.mainFrame();
    } catch (_) {}
    // Cek semua child frames
    for (const f of pg.frames()) {
      if (f === pg.mainFrame()) continue;
      try {
        const cnt = await f.locator("[data-testid='statsig-comp-without-redux']").count().catch(() => 0);
        if (cnt > 0) return f;
      } catch (_) {}
    }
    return null;
  }

  for (let i = 0; i < MAX_POLL_ITERATIONS; i++) {
    await sleep(POLL_INTERVAL_MS);

    const currentUrl = page.url();

    // ── PRIMER: Cari statsig component di SEMUA frame ──
    try {
      statsigFrame = await findStatsigInAnyFrame(page);
      if (statsigFrame) {
        otpPageDetected = true;
        const frameType = statsigFrame === page.mainFrame() ? "main" : "child";
        log(`🎯 Statsig OTP component terdeteksi #${i + 1} (${frameType} frame)`);
        break;
      }
    } catch (_e) {}

    // ── SEKUNDER: Cek URL OTP pattern ──
    if (currentUrl.includes("is_not_gen_0=true") ||
        (currentUrl.includes("/login?") && currentUrl.includes("not_gen")) ||
        currentUrl.includes("verify") || currentUrl.includes("two-factor") || currentUrl.includes("2fa")) {
      otpPageDetected = true;
      log(`🎯 URL OTP terdeteksi #${i + 1}: ${currentUrl.substring(0, 80)}`);
      break;
    }

    // ── TERSIER: Cek body text untuk "Authentication code" ──
    try {
      const bodyText = await page.evaluate(() => document.body?.innerText || "").catch(() => "");
      if (bodyText.includes("Authentication code") || bodyText.includes("verification code") ||
          bodyText.includes("Enter the 6-digit") || bodyText.includes("Enter the code")) {
        otpPageDetected = true;
        log(`🎯 "Authentication code" text terdeteksi #${i + 1}`);
        break;
      }
      // Cek juga di dalam frame
      for (const f of page.frames()) {
        if (f === page.mainFrame()) continue;
        try {
          const fText = await f.evaluate(() => document.body?.innerText || "").catch(() => "");
          if (fText.includes("Authentication code") || fText.includes("Enter the code")) {
            otpPageDetected = true;
            log(`🎯 "Authentication code" text di child frame #${i + 1}`);
            break;
          }
        } catch (_) {}
      }
      if (otpPageDetected) break;
    } catch (_e) {}

    // ── Dashboard detection — HANYA jika statsig TIDAK ditemukan ──
    //     Butuh 3x berturut-turut di URL dashboard tanpa ada statsig
    //     URL dashboard = figma.com/files, figma.com/@, atau figma.com/ tanpa login
    const isDashboardUrl = (
      currentUrl.includes("figma.com/files") ||
      currentUrl.includes("figma.com/@") ||
      currentUrl.includes("/drafts") ||
      (currentUrl.match(/figma\.com\/?(\?.*)?$/) && !currentUrl.includes("login") && !currentUrl.includes("not_gen"))
    );
    if (isDashboardUrl && !statsigFrame) {
      dashboardIterations = (dashboardIterations || 0) + 1;
      if (dashboardIterations >= 3) {
        log(`📍 URL #${i + 1}: dashboard terkonfirmasi (${dashboardIterations}x) — tidak ada OTP.`);
        break;
      }
    } else {
      dashboardIterations = 0;
    }

    if (i % 3 === 0) {
      log(`⏳ Poll OTP page #${i + 1}: ${currentUrl.substring(0, 60)}...`);
    }
  }

  if (otpPageDetected) {
    log("🔐 Figma meminta kode OTP (Authentication code)! Menunggu kode dari iCloud...");

    // Await kode OTP dari iCloud (sudah di-fetch di background)
    const otpCode = await otpPromise;
    if (!otpCode) {
      log("❌ Gagal mengambil kode OTP dari iCloud.", "error");
      throw new Error("Kode OTP Figma tidak ditemukan di inbox iCloud.");
    }
    log(`🔑 Kode OTP siap: ${otpCode}`);

    // ── Pilih frame target: statsigFrame (jika ditemukan), fallback ke page ──
    const targetFrame = statsigFrame || page;
    const frameLabel = statsigFrame && statsigFrame !== page.mainFrame() ? "child frame" : "main page";

    // Isi OTP code di frame tempat statsig component berada
    let otpEntered = false;

    // Tunggu statsig input muncul di frame target
    for (let i = 0; i < 10; i++) {
      const hasInput = await targetFrame.locator("[data-testid='statsig-comp-without-redux'] input").count().catch(() => 0);
      if (hasInput > 0) {
        log(`✅ Statsig OTP input terdeteksi di ${frameLabel}.`);
        break;
      }
      await sleep(1000);
    }

    // Selector dari Lighthouse recording (23-Jun-2026):
    //   aria/Authentication code, input,
    //   xpath///*[@data-testid="statsig-comp-without-redux"]/div/div[1]/div/div/div/form/div[2]/input
    //   (catatan: ada extra /div dibanding rekaman sebelumnya)
    for (const sel of [
      "::-p-aria(Authentication code)",
      "[data-testid='statsig-comp-without-redux'] input",
      "[data-testid='statsig-comp-without-redux'] div input",
      "[data-testid='statsig-comp-without-redux'] div div input",
      "input[aria-label*='Authentication' i]",
      "input[type='tel']",
      "input[type='text']",
      "input:not([type='hidden']):not([type='submit']):not([type='password']):not([type='email'])",
    ]) {
      try {
        const loc = targetFrame.locator(sel);
        if (await loc.count() > 0) {
          const isVisible = await loc.first().isVisible().catch(() => false);
          if (isVisible) {
            await loc.first().click();
            await sleep(300);
            await loc.first().fill("");
            await sleep(100);
            await loc.first().fill(otpCode);
            log(`✅ OTP "${otpCode}" diisi via: ${sel} (${frameLabel})`);
            otpEntered = true;
            break;
          }
        }
      } catch (_e) {
        // continue next selector
      }
    }

    if (!otpEntered) {
      // Last-resort: cari input visible yang bisa diisi di target frame
      try {
        const inputs = targetFrame.locator("input:visible");
        const cnt = await inputs.count();
        for (let i = 0; i < cnt; i++) {
          const type = await inputs.nth(i).getAttribute("type").catch(() => "");
          if (type !== "hidden" && type !== "submit" && type !== "password" && type !== "email") {
            await inputs.nth(i).click();
            await sleep(200);
            await inputs.nth(i).fill(otpCode);
            log(`✅ OTP via input visible index ${i} (${frameLabel})`);
            otpEntered = true;
            break;
          }
        }
      } catch (_e) {}
    }

    if (!otpEntered) {
      // Last-last resort: coba di main page juga
      if (targetFrame !== page && targetFrame !== page.mainFrame()) {
        log("⚠️ Gagal isi di child frame, coba main page...");
        for (const sel of [
          "::-p-aria(Authentication code)",
          "[data-testid='statsig-comp-without-redux'] input",
          "input[type='tel']",
        ]) {
          try {
            const loc = page.locator(sel);
            if (await loc.count() > 0 && await loc.first().isVisible().catch(() => false)) {
              await loc.first().click();
              await sleep(200);
              await loc.first().fill(otpCode);
              log(`✅ OTP "${otpCode}" diisi via main page: ${sel}`);
              otpEntered = true;
              break;
            }
          } catch (_e) {}
        }
      }
    }

    if (otpEntered) {
      await sleep(1000);

      // Klik "Log in" button — dari rekaman:
      //   aria/Log in, button,
      //   xpath///*[@data-testid="statsig-comp-without-redux"]/div/div[1]/div/div/div/form/button
      log("🖱️ Klik 'Log in' setelah OTP...");
      let submitClicked = false;
      for (const sel of [
        "[data-testid='statsig-comp-without-redux'] button",
        "[data-testid='statsig-comp-without-redux'] button > span",
        "button > span",
        "::-p-aria(Log in)",
        "button:has-text('Log in')",
        "::-p-text(Log in)",
        "button[type='submit']",
      ]) {
        try {
          // Coba di targetFrame dulu, lalu di page
          for (const ctx of [targetFrame, page]) {
            const loc = ctx.locator(sel);
            if (await loc.count() > 0) {
              await loc.first().click();
              log(`✅ Log in diklik via: ${sel}`);
              submitClicked = true;
              break;
            }
          }
          if (submitClicked) break;
        } catch (_e) {}
      }

      if (!submitClicked) {
        log("⌨️ Fallback: tekan Enter...");
        await page.keyboard.press("Enter");
      }

      // Tunggu navigasi setelah klik Log in
      // Rekaman: setelah klik → navigasi ke figma.com/files/team/.../recents-and-sharing
      await sleep(6000);
      log(`📍 URL setelah OTP submit: ${page.url().substring(0, 80)}...`);
    } else {
      log("⚠️ Input OTP tidak ditemukan di semua frame.", "warn");
    }
  } else {
    log("✅ Tidak ada OTP terdeteksi — login langsung.");
  }

  // ── 8. Handle onboarding jika muncul ──
  for (let i = 0; i < 3; i++) {
    try {
      const skipBtn = page.locator("button:has-text('Skip'), a:has-text('Skip'), button:has-text('Do this later')");
      if (await skipBtn.count() > 0) {
        await skipBtn.first().click();
        log("✅ Skip onboarding diklik.");
        await sleep(1500);
      }
    } catch (_e) {}
  }

  log(`✅ Login Figma berhasil: ${email}`);
  return true;
}

// ─── Step 3: Sign-in ke Weavy via Figma ─────────────────────────────
//
//   Alur:
//     1. Buka app.weavy.ai/signin (LANGSUNG, skip weave.figma.com)
//     2. Klik "Log in with Figma" (button ke-2 di signin)
//     3. Figma OAuth redirect (auto-authorize — session Figma sudah aktif)
//     4. Redirect ke Weavy dashboard
//     5. Tangkap Bearer token dari request API Weavy

async function signInWeavyViaFigma(context, log) {
  log(`🔐 Sign-in ke Weavy via Figma...`);
  const page = await context.newPage();
  let capturedToken = null;

  // Intercept Bearer token dari request API Weavy
  page.on("request", (req) => {
    const auth = req.headers()["authorization"] || "";
    if (auth.startsWith("Bearer ")) {
      const token = auth.replace("Bearer ", "").trim();
      if (token.split(".").length === 3 && token.length > 100 && !capturedToken) {
        capturedToken = token;
        log(`🎯 Bearer token ditangkap! (${token.substring(0, 20)}...)`);
      }
    }
  });

  try {
    // ── 1. Buka LANGSUNG app.weavy.ai/signin ──
    log("📍 Navigasi ke app.weavy.ai/signin...");
    await page.goto(WEAVY_SIGNIN, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(4000);

    log(`📍 URL: ${page.url().substring(0, 80)}...`);

    // ── 2. Klik "Log in with Figma" ──
    log("🖱️ Klik 'Log in with Figma'...");
    let figmaClicked = false;

    // Coba klik button ke-2 (index 1) — Figma biasanya button ke-2 setelah Google
    try {
      const buttons = page.locator("[data-testid='signin-page-content'] button");
      const cnt = await buttons.count();
      log(`🔍 Ada ${cnt} button di signin page.`);
      if (cnt >= 2) {
        await buttons.nth(1).click();
        log(`✅ Button Figma diklik (index 1 dari ${cnt})`);
        figmaClicked = true;
      } else if (cnt === 1) {
        await buttons.nth(0).click();
        log(`✅ Hanya 1 button — diklik.`);
        figmaClicked = true;
      }
    } catch (_e) {
      log(`⚠️ Error klik button by index: ${_e.message}`);
    }

    if (!figmaClicked) {
      // Fallback: cari by text
      for (const sel of [
        "button:has-text('Log in with Figma')",
        "button:has-text('Continue with Figma')",
        "button:has-text('Figma')",
      ]) {
        try {
          const loc = page.locator(sel);
          if (await loc.count() > 0) {
            await loc.first().click();
            log(`✅ Figma diklik via: ${sel}`);
            figmaClicked = true;
            break;
          }
        } catch (_e) {}
      }
    }

    if (!figmaClicked) {
      const buttons = await page.locator("button");
      const count = await buttons.count();
      for (let i = 0; i < count; i++) {
        const text = (await buttons.nth(i).textContent().catch(() => "")).toLowerCase();
        if (text.includes("figma")) {
          await buttons.nth(i).click();
          log(`✅ Figma diklik via button index ${i}: "${text}"`);
          figmaClicked = true;
          break;
        }
      }
    }

    if (!figmaClicked) throw new Error("Tombol 'Log in with Figma' tidak ditemukan di signin page.");

    // ── 3. Tunggu Figma OAuth → mungkin auto-redirect ──
    log("⏳ Menunggu Figma OAuth / redirect ke dashboard...");
    
    let tokenTimeout = 0;
    for (let i = 0; (!capturedToken && i < 50); i++) {
      await sleep(2000);
      tokenTimeout += 2;

      const url = page.url();
      
      // Jika Figma minta authorize, klik Allow
      if (url.includes("figma.com/oauth")) {
        log("📍 Figma OAuth page — klik Allow...");
        for (const sel of [
          "button:has-text('Allow')",
          "button:has-text('Allow access')",
          "button[type='submit']",
          "button",
        ]) {
          try {
            const loc = page.locator(sel);
            if (await loc.count() > 0) {
              await loc.first().click();
              log(`✅ Allow via: ${sel}`);
              await sleep(3000);
              break;
            }
          } catch (_e) {}
        }
      }

      // Jika sudah di dashboard Weavy, reload untuk trigger API calls
      if (url.includes("app.weavy.ai") && !url.includes("signin") && i === 3) {
        log("✅ Dashboard Weavy terdeteksi — reload untuk trigger token...");
        try {
          await page.goto("https://app.weavy.ai", { waitUntil: "domcontentloaded", timeout: 15000 });
          await sleep(3000);
        } catch (_e) {}
      }

      if (i % 5 === 0 && i > 0) {
        log(`⏳ ${tokenTimeout}s | URL: ${url.substring(0, 50)}... | token: ${capturedToken ? "YES" : "no"}`);
      }
    }

    await page.close().catch(() => {});

    if (!capturedToken) {
      throw new Error("Bearer token tidak ditemukan setelah login Figma (timeout 100s).");
    }

    log(`✅ Token didapat: ${capturedToken.substring(0, 25)}...`);
    return { token: capturedToken };
  } catch (err) {
    await page.close().catch(() => {});
    throw err;
  }
}

// ─── Recipe management: ensure Kling 2.6 recipe exists ──────────────

export async function ensureKling26Recipe(token, recipeId, onLog = null) {
  const log = makeLogger(onLog);
  const defaultId = DEFAULT_RECIPE_ID;

  // Cek apakah recipe sudah ada di akun ini
  if (recipeId) {
    try {
      const check = await fetch(`${WEAVY_API}/api/v1/recipes/${recipeId}`, {
        headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      });
      if (check.status === 200) {
        log(`✅ Recipe ${recipeId} sudah ada.`);
        return recipeId;
      }
    } catch (_e) {}
  }

  // Coba pakai recipe ID default
  if (defaultId && defaultId !== recipeId) {
    try {
      const check = await fetch(`${WEAVY_API}/api/v1/recipes/${defaultId}`, {
        headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      });
      if (check.status === 200) {
        log(`✅ Recipe default ${defaultId} tersedia.`);
        return defaultId;
      }
    } catch (_e) {}
  }

  // Buat recipe baru jika belum ada
  log("📝 Membuat recipe Kling 2.6 baru via API...");

  try {
    // Load template
    const templatePath = join(__dirname, "../data/flow-template.json");
    let template;
    try {
      const raw = readFileSync(templatePath, "utf8");
      template = JSON.parse(raw);
    } catch (_e) {
      log(`⚠️ Gagal membaca flow-template.json, buat recipe default.`, "warn");
    }

    const payload = {
      name: "Kling Motion TEKNO v2.6",
      description: "Auto-generated Kling 2.6 Standard recipe",
      nodes: template || [],
      edges: [],
      modelService: "fal_imported",
      modelId: "fal-ai/kling-video/v2.6/pro/motion-control",
    };

    const createRes = await fetch(`${WEAVY_API}/api/v1/recipes/create`, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });

    if (createRes.ok) {
      const data = await createRes.json();
      const newId = data.id || data.recipeId;
      log(`✅ Recipe baru dibuat: ${newId}`);
      return newId;
    }

    log(`⚠️ Gagal membuat recipe: ${createRes.status}`, "warn");
    return defaultId; // fallback
  } catch (err) {
    log(`❌ Gagal membuat recipe: ${err.message}`, "error");
    return defaultId;
  }
}

// ─── SINGLE ACCOUNT FLOW ────────────────────────────────────────────

export async function runFullFlow(onLog = null) {
  const log = makeLogger(onLog);
  log("🚀 runFullFlow() tidak digunakan lagi. Gunakan runBulkFlow(emails, onLog).", "warn");
  return { ok: false, error: "runFullFlow deprecated — gunakan runBulkFlow" };
}

// ─── FIGMA SIGN-UP + WEAVY LOGIN (1 akun baru) ─────────────────────
//
// DUA TAHAP TERPISAH:
//   Phase 1 — Daftar akun Figma via figma.com/signup:
//     → Isi email + password → Submit
//     → Verifikasi email via link dari ICLOUD inbox
//     → Handle Figma onboarding (skip, free plan)
//
//   Phase 2 — Login ke Weavy via Figma:
//     Buka app.weavy.ai/signin → Login with Figma
//     → Figma OAuth (auto-authorize) → Tangkap Bearer token
//
//   Password: Tekno@Kling2026 + random 4-char suffix

function generateFigmaPassword() {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let suffix = "";
  for (let i = 0; i < 4; i++) suffix += chars.charAt(Math.floor(Math.random() * chars.length));
  return `Tekno@Kling2026${suffix}`;
}

// Fetch Figma verification link from iCloud inbox
async function fetchFigmaVerificationLink(email, log) {
  log(`📨 Polling ICLOUD API untuk link verifikasi Figma...`);

  const maxAttempts = 20;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    await sleep(5000);
    log(`🔍 Poll Figma link #${attempt + 1}...`);

    try {
      // Gunakan server API untuk cari email Figma
      const resp = await fetch(`http://localhost:${SERVER_PORT}/api/admin/icloud/find-code`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "x-admin-password": OWNER_PASSWORD,
        },
        body: JSON.stringify({
          email,
          skipThreadIds: [],
          searchKeyword: "figma",
        }),
      });
      const data = await resp.json();

      if (data.ok && data.rawContent) {
        // Cari link verifikasi Figma di raw content
        const raw = data.rawContent;
        const linkMatch =
          raw.match(/https?:\/\/[^\s"<>]*figma\.com\/email\/[^\s"<>]+/i) ||
          raw.match(/https?:\/\/[^\s"<>]*figma\.com[^\s"<>]*(?:verify|confirm|email)[^\s"<>]*/i);
        if (linkMatch) {
          log(`🎯 Link verifikasi Figma ditemukan: ${linkMatch[0].substring(0, 80)}...`);
          return linkMatch[0];
        }
      }
    } catch (e) {
      log(`⚠️ Poll #${attempt + 1} error: ${e.message}`, "warn");
    }
  }

  // Fallback: direct ICLOUD API
  const cfg = getIcloudConfig();
  if (cfg.session) {
    const headers = {
      Authorization: `Bearer ${cfg.session}`,
      "x-device-id": cfg.device,
      "x-account-id": cfg.account,
      "Content-Type": "application/json",
    };

    for (let attempt = 0; attempt < 15; attempt++) {
      await sleep(5000);
      try {
        const listResp = await fetch(`${cfg.apiUrl}/api/inbox/universal`, { headers });
        const listData = await listResp.json();
        let threads = listData.data || listData || [];
        if (!Array.isArray(threads) || !threads.length) continue;

        threads = threads.sort((a, b) => {
          const ad = new Date(a.date || a.timestamp || 0).getTime();
          const bd = new Date(b.date || b.timestamp || 0).getTime();
          return bd - ad;
        });

        const figmaThread = threads.find(t => {
          const s = [t.subject, t.from, t.to].join(" ").toLowerCase();
          return s.includes("figma");
        });

        if (figmaThread) {
          const threadResp = await fetch(`${cfg.apiUrl}/api/inbox/universal/thread`, {
            method: "POST",
            headers,
            body: JSON.stringify({ threadId: figmaThread.id || figmaThread.threadId }),
          });
          const threadData = await threadResp.json();
          const msgs = threadData.data || threadData || [];
          const msgArray = Array.isArray(msgs) ? msgs : (msgs.messages || []);
          const rawContent = msgArray.map((m) => m.text || m.body || m.html || "").join("\n");
          const content = rawContent.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();

          const linkMatch =
            content.match(/https?:\/\/[^\s"<>]*figma\.com\/email\/[^\s"<>]+/i) ||
            content.match(/https?:\/\/[^\s"<>]*figma\.com[^\s"<>]*(?:verify|confirm|email)[^\s"<>]*/i);
          if (linkMatch) {
            log(`🎯 Link verifikasi Figma ditemukan via direct API!`);
            return linkMatch[0];
          }
        }
      } catch (e) {
        log(`⚠️ Direct poll #${attempt + 1} error: ${e.message}`, "warn");
      }
    }
  }

  throw new Error("Link verifikasi Figma tidak ditemukan di inbox.");
}

async function signUpFigma(email, password, context, log) {
  const page = await context.newPage();

  try {
    // ── 1. Navigasi ke figma.com/signup ──
    log("📍 Navigasi ke figma.com/signup...");
    await page.goto("https://www.figma.com/signup", { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(3000);

    if (await detectCaptchaChallenge(page)) {
      log("⚠️ CAPTCHA terdeteksi di halaman signup Figma!", "captcha");
    }

    // ── 2. Isi email ──
    log(`📝 Mengisi email: ${email}...`);
    let emailFilled = false;
    for (const sel of [
      "input[type='email']",
      "input[name='email']",
      "input[placeholder*='Email' i]",
      "input[placeholder*='email' i]",
      "#email",
    ]) {
      try {
        const loc = page.locator(sel);
        if (await loc.count() > 0) {
          await loc.first().click();
          await sleep(200);
          await loc.first().fill(email);
          log(`✅ Email diisi via: ${sel}`);
          emailFilled = true;
          break;
        }
      } catch (_e) {}
    }

    if (!emailFilled) throw new Error("Input email tidak ditemukan di form signup Figma.");

    await sleep(1000);

    // ── 3. Klik Continue ──
    log("🖱️ Klik Continue...");
    let continueClicked = false;
    for (const sel of [
      "button[type='submit']",
      "button:has-text('Continue')",
      "button:has-text('Create account')",
      "button:has-text('Sign up')",
      "button:has-text('Get started')",
    ]) {
      try {
        const loc = page.locator(sel);
        if (await loc.count() > 0) {
          const text = (await loc.first().textContent()).toLowerCase();
          if (!text.includes("google") && !text.includes("saml") && !text.includes("sso")) {
            await loc.first().click();
            log(`✅ Continue diklik via: ${sel}`);
            continueClicked = true;
            break;
          }
        }
      } catch (_e) {}
    }

    if (!continueClicked) {
      // Fallback: klik button pertama yang bukan Google
      const buttons = await page.locator("button");
      const cnt = await buttons.count();
      for (let i = 0; i < cnt; i++) {
        const text = (await buttons.nth(i).textContent()).toLowerCase();
        if (!text.includes("google") && !text.includes("saml")) {
          await buttons.nth(i).click();
          log(`✅ Continue via button index ${i}`);
          continueClicked = true;
          break;
        }
      }
    }

    await sleep(3000);

    // ── 4. Isi password ──
    log(`📝 Mengisi password...`);
    let passFilled = false;
    for (const sel of [
      "input[type='password']",
      "input[name='password']",
      "input[placeholder*='Password' i]",
      "#password",
    ]) {
      try {
        const loc = page.locator(sel);
        if (await loc.count() > 0) {
          await loc.first().click();
          await sleep(200);
          await loc.first().fill(password);
          log(`✅ Password diisi via: ${sel}`);
          passFilled = true;
          break;
        }
      } catch (_e) {}
    }

    if (passFilled) {
      await sleep(1000);

      // ── 5. Klik Create account ──
      log("🖱️ Klik Create account...");
      for (const sel of [
        "button[type='submit']",
        "button:has-text('Create account')",
        "button:has-text('Sign up')",
        "button:has-text('Continue')",
        "button:has-text('Log in')",
      ]) {
        try {
          const loc = page.locator(sel);
          if (await loc.count() > 0) {
            const text = (await loc.first().textContent()).toLowerCase();
            if (!text.includes("google") && !text.includes("saml") && !text.includes("sso")) {
              await loc.first().click();
              log(`✅ Create account diklik via: ${sel}`);
              break;
            }
          }
        } catch (_e) {}
      }
    }

    // ── 6. Verifikasi email Figma via link dari inbox ──
    log("📨 Menunggu email verifikasi Figma...");
    log("⏳ Jeda 15 detik (nunggu email verifikasi terkirim)...");
    await sleep(15000);

    const verifyLink = await fetchFigmaVerificationLink(email, log);
    log(`🔗 Membuka link verifikasi Figma: ${verifyLink.substring(0, 80)}...`);

    // Buka link verifikasi di tab baru
    const verifyPage = await context.newPage();
    await verifyPage.goto(verifyLink, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(5000);
    log(`📍 URL setelah verifikasi: ${verifyPage.url().substring(0, 80)}...`);

    // ── 7. Handle Figma onboarding ──
    // Cek apakah perlu isi nama
    try {
      const nameInput = verifyPage.locator("input[placeholder*='name'], input[type='text']");
      if (await nameInput.count() > 0) {
        const val = await nameInput.first().inputValue().catch(() => "");
        if (!val.trim()) {
          await nameInput.first().fill("Kling User");
          log("📝 Nama diisi: Kling User");
        }
        const continueBtn = verifyPage.locator("button:has-text('Continue')");
        if (await continueBtn.count() > 0) {
          await continueBtn.first().click();
          log("✅ Onboarding Continue diklik.");
          await sleep(2000);
        }
      }
    } catch (_e) {}

    // Skip questions
    for (let i = 0; i < 3; i++) {
      try {
        const skipBtn = verifyPage.locator("button:has-text('Skip'), a:has-text('Skip'), button:has-text('Do this later')");
        if (await skipBtn.count() > 0) {
          await skipBtn.first().click();
          log("✅ Skip onboarding diklik.");
          await sleep(1500);
        }
      } catch (_e) {}
    }

    // Start for free
    try {
      const freeBtn = verifyPage.locator("button:has-text('Start for free'), span:has-text('Start for free'), button:has-text('Choose starter')");
      if (await freeBtn.count() > 0) {
        await freeBtn.first().click();
        log("✅ Free plan dipilih.");
        await sleep(2000);
      }
    } catch (_e) {}

    await verifyPage.close().catch(() => {});
    await page.close().catch(() => {});

    log("✅ [Phase 1] Akun Figma berhasil dibuat!");
    return true;
  } catch (err) {
    await page.close().catch(() => {});
    throw err;
  }
}

export async function signUpFigmaAndLoginWeavy(email, onLog = null) {
  const log = makeLogger(onLog);
  let browser = null;

  const password = generateFigmaPassword();
  log(`🆕 [Phase 1/2] Mendaftar akun Figma: ${email}`, "start");

  try {
    const hl = isHeadless();
    log(`🌐 Membuka browser (headless: ${hl})...`);
    browser = await chromium.launch(getLaunchOptions(hl));
    activeBrowser = browser;

    const context = await createStealthContext(browser);

    // ═══════════════════════════════════════════════════════════════════
    // PHASE 1: DAFTAR AKUN FIGMA
    // ═══════════════════════════════════════════════════════════════════
    await signUpFigma(email, password, context, log);

    // ═══════════════════════════════════════════════════════════════════
    // PHASE 2: LOGIN KE WEAVY VIA FIGMA
    // ═══════════════════════════════════════════════════════════════════
    log(`🔐 [Phase 2/2] Login ke Weavy via Figma...`);

    // Context browser masih sama, session Figma masih aktif
    const { token } = await signInWeavyViaFigma(context, log);

    log(`✅ [Phase 2] Login Weavy berhasil! Token: ${token.substring(0, 25)}...`, "success");
    return { token, password };
  } catch (err) {
    log(`❌ Signup gagal: ${err.message}`, "error");
    throw err;
  } finally {
    if (browser) {
      await browser.close().catch(() => {});
      activeBrowser = null;
    }
  }
}

// ─── BULK ACCOUNT FLOW (email list dari admin) ─────────────────────

/**
 * @param {string[]} emails - Daftar email yang akan diproses
 * @param {function} onLog - Callback SSE untuk log real-time
 * @returns {Promise<{ok: boolean, results: object[], total: number, success: number, failed: number}>}
 */
export async function runBulkFlow(accounts, onLog = null) {
  const log = makeLogger(onLog);
  let browser = null;
  const results = [];
  let success = 0;
  let failed = 0;

  log(`🤖 Memulai Bulk Auto Token Bot (Figma) — ${accounts.length} akun...`, "start");

  if (!accounts || !accounts.length) {
    const err = "❌ Daftar akun kosong.";
    log(err, "error");
    return { ok: false, error: err, results: [], total: 0, success: 0, failed: 0 };
  }

  try {
    const hl = isHeadless();
    log(`🌐 Membuka browser (headless: ${hl})...`);
    browser = await chromium.launch(getLaunchOptions(hl));
    activeBrowser = browser;

    const context = await createStealthContext(browser);

    // Proses akun satu per satu (Figma sign-up/login + Weavy login)
    for (let i = 0; i < accounts.length; i++) {
      const acc = accounts[i];
      const email = (acc.email || "").trim();
      if (!email || !email.includes("@")) continue;

      const password = acc.password || generateFigmaPassword();
      const label = email.split("@")[0];

      log(`\n━━━ [${i + 1}/${accounts.length}] Memproses: ${email} ━━━`);

      const result = {
        email,
        label,
        ok: false,
        error: null,
        accountId: null,
        tokenPreview: null,
        recipeId: null,
      };

      try {
        if (acc.password) {
          // Akun Figma sudah ada → login ke Figma dulu
          log(`🔐 Login ke Figma: ${email}...`);
          await signInFigma(email, password, await context.newPage(), log);
        } else {
          // Buat akun Figma baru → signup
          log(`🆕 Mendaftar akun Figma: ${email}...`);
          await signUpFigma(email, password, context, log);
        }

        // Login ke Weavy via Figma (session Figma sudah aktif)
        log(`🔐 Login ke Weavy via Figma OAuth...`);
        const { token } = await signInWeavyViaFigma(context, log);

        // Ensure Kling 2.6 recipe
        log("📋 Memastikan recipe Kling 2.6...");
        const recipeId = await ensureKling26Recipe(token, null, log);

        // Simpan ke pool
        log(`💾 Menyimpan akun ke pool: ${email}...`);
        const savedAcc = Accounts.addAccount({ token, recipeId, label, email, password });

        result.ok = true;
        result.accountId = savedAcc.id;
        result.tokenPreview = token.substring(0, 25) + "...";
        result.recipeId = recipeId;

        log(`✅ BERHASIL: ${label} (${savedAcc.id})`, "success");
        success++;
      } catch (err) {
        log(`❌ GAGAL: ${email} — ${err.message}`, "error");
        result.error = err.message;
        failed++;
      }

      results.push(result);

      // Jeda antar akun
      if (i < accounts.length - 1) {
        log("⏳ Jeda 5 detik sebelum akun berikutnya...");
        await sleep(5000);
      }
    }

    await browser.close();
    activeBrowser = null;

    const summary = `Selesai: ${success} berhasil, ${failed} gagal dari ${accounts.length} akun.`;
    log(`\n${summary}`, success === accounts.length ? "success" : "warn");

    return {
      ok: true,
      results,
      total: accounts.length,
      success,
      failed,
      msg: summary,
    };
  } catch (err) {
    log(`❌ ERROR FATAL: ${err.message}`, "error");
    if (browser) await browser.close().catch(() => {});
    activeBrowser = null;
    return { ok: false, error: err.message, results, total: accounts.length, success, failed };
  }
}

// ─── REVIVE: Refresh token akun existing via Google ─────────────────

export async function refreshExistingAccountToken(accountId, email, _unusedPassword, onLog = null) {
  const log = makeLogger(onLog);
  let browser = null;

  const reviveStartedAt = new Date().toISOString();
  log(`🔄 [REVIVE] Memulai revive akun: ${email} (${accountId})`, "start");
  log(`⏱️ [REVIVE] Waktu mulai: ${reviveStartedAt}`);

  try {
    // Validasi: ambil password dari store — WAJIB ada, tidak boleh generate random
    const accounts = Accounts.listAccounts();
    const storedAcc = accounts.find(a => a.id === accountId || a.email === email);
    
    if (!storedAcc) {
      throw new Error(`Akun ${email} tidak ditemukan di account store.`);
    }
    
    const password = storedAcc.password;
    if (!password) {
      throw new Error(`Password tidak tersimpan untuk akun ${email}. Akun ini dibuat tanpa password Figma — tidak bisa di-revive.`);
    }
    
    log(`🔍 [REVIVE] Status akun: expired=${storedAcc.expired}, runs=${storedAcc.runs || 0}/${storedAcc.maxRuns || 3}, figmaBlocked=${storedAcc.figmaBlocked || storedAcc.googleBlocked || false}`);

    // Cek apakah akun sudah di-block (Figma / Google)
    if (storedAcc.figmaBlocked || storedAcc.googleBlocked) {
      const reason = storedAcc.figmaBlockedReason || storedAcc.googleBlockedReason || "tidak diketahui";
      throw new Error(`Akun ${email} sudah di-block (${reason}). Tidak bisa di-revive.`);
    }

    const hl = isHeadless();
    browser = await chromium.launch(getLaunchOptions(hl));
    activeBrowser = browser;

    const context = await createStealthContext(browser);

    // Login ke Figma dengan email + password (pakai session existing)
    log(`🔐 [REVIVE] Login ke Figma: ${email}...`);
    const figmaPage = await context.newPage();
    await signInFigma(email, password, figmaPage, log);
    await figmaPage.close().catch(() => {});

    // Sign-in via Figma OAuth (session Figma sudah aktif)
    log(`🔐 [REVIVE] Login ke Weavy via Figma OAuth...`);
    const { token } = await signInWeavyViaFigma(context, log);

    // Ensure recipe
    const recipeId = await ensureKling26Recipe(token, null, log);

    // Update account — reset status expired & perpanjang masa aktif
    const updatedFields = {
      token,
      recipeId,
      expired: false,
      expiredAt: null,
      expireReason: null,
      figmaBlocked: false,
      googleBlocked: false,
      lastRevivedAt: new Date().toISOString(),
    };
    Accounts.updateAccount(accountId, updatedFields);
    
    log(`✅ [REVIVE] SUKSES: ${email}`, "success");
    log(`📋 [REVIVE] Token baru: ${token.substring(0, 25)}...`);
    log(`📋 [REVIVE] Recipe: ${recipeId}`);
    log(`⏱️ [REVIVE] Selesai: ${new Date().toISOString()}`);

    await browser.close();
    activeBrowser = null;

    return { ok: true, token, recipeId };
  } catch (err) {
    const failedAt = new Date().toISOString();
    log(`❌ [REVIVE] GAGAL: ${email} — ${err.message}`, "error");
    log(`⏱️ [REVIVE] Gagal pada: ${failedAt}`);

    // Tandai akun sebagai Figma-blocked jika login gagal karena kredensial / verifikasi
    const blockKeywords = [
      "memblokir", "blocked", "tidak bisa login", "password salah",
      "invalid credentials", "authentication failed", "kredensial tidak valid",
      "session invalid", "tidak ditemukan di account store", "sudah di-block",
    ];
    const shouldBlock = blockKeywords.some(kw => err.message.toLowerCase().includes(kw.toLowerCase()));

    if (shouldBlock) {
      try {
        await Accounts.updateAccount(accountId, {
          figmaBlocked: true,
          googleBlocked: true,
          figmaBlockedAt: failedAt,
          figmaBlockedReason: err.message,
          googleBlockedAt: failedAt,
          googleBlockedReason: err.message,
        });
        log(`🚫 [REVIVE] Akun ${email} ditandai FIGMA_BLOCKED — tidak akan dipilih lagi.`, "error");
      } catch (markErr) {
        log(`⚠️ [REVIVE] Gagal tandai figmaBlocked: ${markErr.message}`, "warn");
      }
    }

    if (browser) await browser.close().catch(() => {});
    activeBrowser = null;
    return { ok: false, error: err.message };
  }
}
