/**
 * auto-token.js — Kling Motion Auto Token Bot (v4 - FINAL)
 *
 * Fixes based on real execution logs + Puppeteer recording:
 * - Step 1/2: WORKING ✅
 * - Step 3: Fixed using page.locator() + frameLocator('iframe') for email iframe
 *   Puppeteer recording shows: click div[2]/div[1] in inbox, then iframe → tr:nth-of-type(3) a
 */

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

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

// iCloud email generator base URL — dibacanya dinamis dari settings store
// (env ICLOUD_BASE_URL atau settings.icloudBaseUrl), fallback ke default.
// Setiap kali bot akan navigate, panggil getIcloudBaseUrl() untuk ambil nilai terbaru.
const getICLOUDUrl = () => getIcloudBaseUrl();
const WEAVY_API   = process.env.WEAVY_API_URL || "https://api.weavy.ai";
const WEAVE_URL   = process.env.WEAVE_URL     || "https://weave.figma.com/";
const DEFAULT_RECIPE_ID = process.env.WEAVY_RECIPE_ID || "G6jw67AIcRhGA8eKDF0WEu";
const FIGMA_SIGNUP  = "https://www.figma.com/signup";
const isHeadless      = () => process.env.PLAYWRIGHT_HEADLESS !== "false";
const VOUCHER       = process.env.ICLOUD_SETAN_VOUCHER || "";
const DEFAULT_PASS  = process.env.FIGMA_PASSWORD_DEFAULT || "Tekno@Kling2026!";

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 });
  };
}

export 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("/cdn-cgi/") ||           // Cloudflare challenge pages
      url.includes("cf-chl") ||
      url.includes("hcaptcha") ||
      url.includes("arkose") ||
      url.includes("funcaptcha")
    ) {
      return true;
    }

    return await page.evaluate(() => {
      const text = (document.body?.innerText || "").toLowerCase();
      const hasCaptchaElement = Boolean(
        document.querySelector(
          [
            "iframe[src*='recaptcha']",
            "iframe[src*='turnstile']",
            "iframe[src*='hcaptcha']",
            "iframe[src*='arkose']",
            "iframe[src*='funcaptcha']",
            "iframe[src*='cloudflare']",
            "iframe[title*='captcha' i]",
            "iframe[title*='challenge' i]",
            "textarea[name='g-recaptcha-response']",
            "input[name='cf-turnstile-response']",
            "input[name='h-captcha-response']",
            "input[name='fc-token']",
            ".cf-turnstile",
            ".cf-chl-bypass",
            "#challenge-form",                  // Cloudflare challenge
            "#challenge-stage",                 // Cloudflare
            "[data-sitekey]",
            "[data-turnstile-sitekey]",
            "[data-hcaptcha-sitekey]",
            "[id*='captcha']",
            "[class*='captcha']",
            "[name*='captcha']",
            "[aria-label*='captcha' i]",
            "div#cf-challenge-running",        // Cloudflare "checking your browser"
            "div.cf-browser-verification",
          ].join(", ")
        )
      );
      const hasCaptchaText =
        text.includes("captcha") ||
        text.includes("recaptcha") ||
        text.includes("turnstile") ||
        text.includes("hcaptcha") ||
        text.includes("security check") ||
        text.includes("verify you are human") ||
        text.includes("verify that you are human") ||
        text.includes("i'm human") ||
        text.includes("i am human") ||
        text.includes("bukan robot") ||
        text.includes("not a robot") ||
        text.includes("checking your browser") ||
        text.includes("periksa browser anda") ||
        text.includes("periksa browser anda.") ||
        text.includes("just a moment") ||
        text.includes("sedang memeriksa") ||
        text.includes("sedang memverifikasi");
      return hasCaptchaElement || hasCaptchaText;
    });
  } catch {
    return false;
  }
}

// Helper to click "Allow access" / "Approve" button on Figma OAuth consent screen if visible
async function handleFigmaOauthConsentPage(page, log) {
  try {
    const url = page.url();
    if (url.includes("figma.com/oauth") || url.includes("figma.com/auth")) {
      log("🔑 Menemukan halaman persetujuan Figma OAuth. Mencoba menyetujui...");
      
      const selectors = [
        "button:has-text('Allow access')",
        "button:has-text('Izinkan akses')",
        "button:has-text('Allow')",
        "button:has-text('Izinkan')",
        "button[type='submit']",
        "button.oauth-button",
      ];

      for (const sel of selectors) {
        try {
          const loc = page.locator(sel);
          if (await loc.count() > 0 && await loc.first().isVisible()) {
            await loc.first().click();
            log(`✅ Tombol persetujuan diklik via: ${sel}`);
            await sleep(3000);
            return true;
          }
        } catch (e) {}
      }

      // Fallback: search all buttons for keywords
      const buttons = await page.locator("button").evaluateAll(els =>
        els.map(el => ({ text: el.textContent?.trim() }))
      ).catch(() => []);

      for (let idx = 0; idx < buttons.length; idx++) {
        const btn = buttons[idx];
        const text = (btn.text || "").toLowerCase();
        if (text.includes("allow") || text.includes("izinkan") || text.includes("approve") || text.includes("accept") || text.includes("setuju")) {
          await page.locator("button").nth(idx).click();
          log(`✅ Tombol persetujuan diklik via index: ${btn.text}`);
          await sleep(3000);
          return true;
        }
      }
    }
  } catch (err) {
    log(`⚠️ Gagal memproses halaman persetujuan OAuth: ${err.message}`, "warn");
  }
  return false;
}

// ────────────────────────────────────────────────────────────────────
// STEP 1: Login voucher, generate email, reserve — closes page after done
//         Returns: { email, password }  ← page is CLOSED
// ────────────────────────────────────────────────────────────────────
export async function generateAndOpenInbox(context, log) {
  log("📧 Step 1: Membuka iCloud Setan...");

  // Grant clipboard permissions to allow #btn-copy-and-inbox to work in headless browser
  await context.grantPermissions(["clipboard-read", "clipboard-write"]).catch(() => {});

  // Diagnosis IP Browser
  try {
    const ipPage = await context.newPage();
    await ipPage.goto("https://api.ipify.org?format=json", { timeout: 10000 });
    const ipJson = await ipPage.textContent("body");
    log(`📡 [Diagnosis] Browser IP aktif: ${ipJson.trim()}`);
    await ipPage.close();
  } catch (e) {
    log(`⚠️ [Diagnosis] Gagal memeriksa IP browser: ${e.message}`, "warn");
  }

  const page = await context.newPage();
  const hl = isHeadless();
  try {
    await page.goto(getICLOUDUrl(), { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(2000);

    // Check for CAPTCHA/Turnstile on page load
    let captchaDetected = false;
    let captchaStartAt = null;
    const CAPTCHA_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes max wait for manual solve
    while (await detectCaptchaChallenge(page)) {
      if (!captchaStartAt) captchaStartAt = Date.now();

      // In headless mode, we cannot solve CAPTCHA, but we MUST still alert the admin
      // so they can either:
      //  - abort the bot
      //  - switch to headful mode and rerun
      //  - fix the proxy / cookies
      if (hl) {
        if (Date.now() - captchaStartAt > CAPTCHA_TIMEOUT_MS) {
          throw new Error("Gagal karena terblokir CAPTCHA/Cloudflare Turnstile di iCloud Setan selama 3 menit (mode headless). Set PLAYWRIGHT_HEADLESS=false atau perbaiki proxy.");
        }
        // Keep detecting & alerting until timeout
        if (!captchaDetected) {
          captchaDetected = true;
          log("⚠️ CAPTCHA/Turnstile terdeteksi di iCloud Setan (mode headless)! Alarm dibunyikan ke admin. Bot akan retry otomatis setiap 2 detik selama 3 menit. Untuk selesaikan manual, restart bot dengan PLAYWRIGHT_HEADLESS=false.", "captcha");
        }
        await sleep(2000);
        continue;
      }

      // Headful mode — wait for manual solve
      if (!captchaDetected) {
        captchaDetected = true;
        log("⚠️ CAPTCHA/Turnstile terdeteksi di iCloud Setan! Silakan selesaikan CAPTCHA di browser yang terbuka. Bot akan otomatis melanjutkan setelah sukses.", "captcha");
      }
      await sleep(2000);
    }
    if (captchaDetected) {
      log("✅ CAPTCHA terselesaikan. Melanjutkan...");
      await sleep(1500);
    }

    // Login with voucher if overlay is visible
    const loginHidden = await page.locator("#login-overlay").evaluate(
      (el) => el.classList.contains("hidden")
    ).catch(() => true);

    if (!loginHidden) {
      log("🔐 Login dengan voucher...");
      await page.locator("#login-code").fill(VOUCHER);
      await sleep(300);
      await page.locator("#login-submit").click();
      // Wait for #btn-generate to be visible (ensures login completed successfully)
      await page.waitForSelector("#btn-generate", { state: "visible", timeout: 15000 });
      log("✅ Login voucher berhasil");
    } else {
      log("ℹ️ Session masih aktif");
    }

    // Get all server options from the #account-picker select element
    log("⏳ Menunggu list server iCloud terpopulasi...");
    await page.waitForSelector("#account-picker option", { timeout: 10000 }).catch(() => {});
    const serverOptions = await page.locator("#account-picker option").evaluateAll(
      (options) => options.map(opt => ({ value: opt.value, text: opt.textContent.trim() }))
    ).catch(() => []);

    log(`ℹ️ Menemukan ${serverOptions.length} server iCloud di picker.`);

    let email = null;
    let selectedIndex = 0;
    let success = false;
    let password = null;

    while (selectedIndex < Math.max(1, serverOptions.length)) {
      if (selectedIndex > 0) {
        log("🔄 Melakukan reload halaman untuk mereset state UI...");
        await page.reload({ waitUntil: "domcontentloaded", timeout: 30000 }).catch(() => {});
        await sleep(2000);
        await page.waitForSelector("#account-picker option", { timeout: 10000 }).catch(() => {});
      }

      if (serverOptions.length > 0) {
        const serverOpt = serverOptions[selectedIndex];
        log(`🔄 Memilih ${serverOpt.text} (ID: ${serverOpt.value})...`);
        await page.locator("#account-picker").selectOption(serverOpt.value);
        await sleep(1500);
      }

      // Check for CAPTCHA/Turnstile before generating email
      let generateCaptchaDetected = false;
      let generateCaptchaStartAt = null;
      while (await detectCaptchaChallenge(page)) {
        if (!generateCaptchaStartAt) generateCaptchaStartAt = Date.now();

        if (hl) {
          if (Date.now() - generateCaptchaStartAt > 3 * 60 * 1000) {
            throw new Error("Gagal karena terblokir CAPTCHA/Cloudflare Turnstile sebelum generate email (mode headless, 3 menit timeout).");
          }
          if (!generateCaptchaDetected) {
            generateCaptchaDetected = true;
            log("⚠️ CAPTCHA/Turnstile terdeteksi sebelum generate email (mode headless)! Alarm dibunyikan ke admin. Retry otomatis 2 detik.", "captcha");
          }
          await sleep(2000);
          continue;
        }

        if (!generateCaptchaDetected) {
          generateCaptchaDetected = true;
          log("⚠️ CAPTCHA/Turnstile terdeteksi sebelum generate email! Silakan selesaikan CAPTCHA di browser yang terbuka. Bot akan otomatis melanjutkan setelah sukses.", "captcha");
        }
        await sleep(2000);
      }
      if (generateCaptchaDetected) {
        log("✅ CAPTCHA terselesaikan. Melanjutkan...");
        await sleep(1500);
      }

      log("🎲 Generate email acak...");

      // Diagnostik status tombol sebelum click
      try {
        const btnState = await page.evaluate(() => {
          const btn = document.getElementById("btn-generate");
          if (!btn) return "not_found";
          return {
            visible: btn.offsetParent !== null,
            disabled: btn.disabled,
            classList: Array.from(btn.classList)
          };
        });
        log(`📡 [Diagnosis] Tombol generate: ${JSON.stringify(btnState)}`);
      } catch (diagErr) {
        log(`📡 [Diagnosis] Gagal memeriksa status tombol: ${diagErr.message}`, "warn");
      }

      await page.locator("#btn-generate").click({ timeout: 15000 });

      // Wait for either btn-copy-and-inbox to become visible (success) or limit error to show up
      let limitReached = false;
      let failReason = "Batas Harian Tercapai (Limit) atau Gagal";

      try {
        await page.waitForFunction(() => {
          const btn = document.getElementById("btn-copy-and-inbox");
          const msg = document.getElementById("message");
          const modal = document.getElementById("limit-modal");
          const btnVisible = btn && btn.offsetParent !== null;
          const isLimit = modal && !modal.classList.contains("hidden");
          const isError = msg && !msg.classList.contains("hidden") && msg.classList.contains("bg-red-100");
          return btnVisible || isLimit || isError;
        }, { timeout: 20000 });

        const isLimitVisible = await page.evaluate(() => {
          const modal = document.getElementById("limit-modal");
          return modal && !modal.classList.contains("hidden");
        });

        const errorMsg = await page.evaluate(() => {
          const msg = document.getElementById("message");
          if (msg && !msg.classList.contains("hidden") && msg.classList.contains("bg-red-100")) {
            return msg.textContent.trim();
          }
          return null;
        });

        if (isLimitVisible || errorMsg) {
          limitReached = true;
          failReason = errorMsg || "Batas Harian Tercapai (Limit)";
          if (isLimitVisible) {
            log("🖱️ Menutup modal limit...");
            await page.locator("#limit-close").click().catch(() => {});
            await sleep(500);
          }
        }
      } catch (raceErr) {
        log(`⚠️ Timeout/Error menunggu respon generate email: ${raceErr.message}`, "warn");
        limitReached = true;
        failReason = `Timeout/Error: ${raceErr.message}`;
      }

      if (limitReached) {
        log(`⚠️ Server ${selectedIndex + 1} limit/gagal: "${failReason}"`, "warn");
        if (serverOptions.length > 0 && selectedIndex < serverOptions.length - 1) {
          selectedIndex++;
          log(`🔄 Beralih ke server berikutnya (Server ${selectedIndex + 1})...`);
          continue;
        } else {
          throw new Error(`Semua server iCloud Setan limit atau gagal. Terakhir: ${failReason}`);
        }
      }

      // Ambil hasil email
      try {
        email = await page.locator("#generated-email").inputValue();
        if (!email || !email.includes("@")) {
          throw new Error("Format email tidak valid.");
        }
        log(`📬 Email berhasil dibuat: ${email}`);
      } catch (err) {
        log(`⚠️ Gagal mengambil email di Server ${selectedIndex + 1}: ${err.message}`, "warn");
        if (serverOptions.length > 0 && selectedIndex < serverOptions.length - 1) {
          selectedIndex++;
          log(`🔄 Beralih ke server berikutnya (Server ${selectedIndex + 1})...`);
          continue;
        } else {
          throw err;
        }
      }

      // Click copy and inbox to register/reserve email
      log("📋 Menyalin dan mengaktifkan email via Buka Inbox...");
      await page.locator("#btn-copy-and-inbox").click();
      await sleep(1500);

      // Verify that it copied/saved successfully
      const errorMsg = await page.evaluate(() => {
        const msg = document.getElementById("message");
        if (msg && !msg.classList.contains("hidden") && msg.classList.contains("bg-red-100")) {
          return msg.textContent.trim();
        }
        return null;
      });

      if (errorMsg) {
        log(`⚠️ Gagal mengaktifkan email di Server ${selectedIndex + 1}: ${errorMsg}`, "warn");
        if (serverOptions.length > 0 && selectedIndex < serverOptions.length - 1) {
          selectedIndex++;
          log(`🔄 Beralih ke server berikutnya (Server ${selectedIndex + 1})...`);
          continue;
        } else {
          throw new Error(`Gagal mengaktifkan email: ${errorMsg}`);
        }
      }

      log("✅ Email aktif & terdaftar di saved list");
      const suffix = Math.random().toString(36).slice(2, 6).toUpperCase();
      password = DEFAULT_PASS.replace(/!+$/, "") + suffix + "!";
      log(`🔑 Password Figma: ${password}`);
      success = true;
      break; // Keluar dari loop, sukses!
    }

    if (!success || !email || !password) {
      throw new Error("Gagal membuat email iCloud aktif.");
    }

    // Close page — Step 3 will open a FRESH page
    await page.close();
    return { email, password };
  } catch (err) {
    await page.close().catch(() => {});
    throw err;
  }
}

// ────────────────────────────────────────────────────────────────────
// STEP 2: Register Figma account (separate tab)
// ────────────────────────────────────────────────────────────────────
export async function registerFigmaAccount(context, { email, password }, log) {
  log("📝 Step 2: Daftar akun Figma...");
  const page = await context.newPage();

  try {
    await page.goto(FIGMA_SIGNUP, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(2000);

    // Fill email
    await page.locator("input[type='email'], input[name='email']").first().fill(email);
    await sleep(400);
    log("✅ Email diisi");

    // Click Continue
    for (const sel of [
      "button:has-text('Continue with email')",
      "button:has-text('Continue')",
      "button[type='submit']",
    ]) {
      if (await page.locator(sel).count() > 0) {
        await page.locator(sel).first().click();
        await sleep(2500);
        break;
      }
    }

    // Password if prompted
    if (await page.locator("input[type='password']").count() > 0) {
      await page.locator("input[type='password']").first().fill(password);
      log("✅ Password diisi");
      await sleep(400);
      for (const sel of ["button[type='submit']", "button:has-text('Continue')", "button:has-text('Create')"]) {
        if (await page.locator(sel).count() > 0) {
          await page.locator(sel).first().click();
          await sleep(3000);
          break;
        }
      }
    }

    // Wait for redirect to success page (up to 12 seconds in headless, or 2.5 minutes in headful to allow manual solving)
    const hl = isHeadless();
    const maxWaitSec = hl ? 12 : 150;
    const intervalMs = 1000;
    const maxLoops = Math.ceil((maxWaitSec * 1000) / intervalMs);
    let isSuccess = false;

    let captchaDetected = false;
    let captchaStartAt = null;
    const CAPTCHA_MAX_WAIT = hl ? 90 * 1000 : 150 * 1000; // headless: 90s, headful: 150s

    for (let i = 0; i < maxLoops; i++) {
      const url = page.url();
      if (url.includes("verify") || url.includes("files") || url.includes("team") || url.includes("recents")) {
        isSuccess = true;
        break;
      }

      // Detect CAPTCHA at any time during the wait loop
      if (!captchaDetected && await detectCaptchaChallenge(page)) {
        captchaDetected = true;
        captchaStartAt = Date.now();
        log("💡 CAPTCHA terdeteksi pada halaman Figma signup! " + (hl ? "(Mode headless - alarm dibunyikan ke admin panel.)" : "Jendela browser terbuka. Silakan selesaikan CAPTCHA di browser. Bot akan otomatis melanjutkan setelah sukses."), "captcha");
      }

      // If captcha was detected and still visible, keep waiting until timeout
      if (captchaDetected) {
        if (Date.now() - captchaStartAt > CAPTCHA_MAX_WAIT) {
          log(`⏱️ CAPTCHA Figma signup tidak terselesaikan dalam ${CAPTCHA_MAX_WAIT / 1000}s. Bot menyerah.`, "error");
          break;
        }
        // While captcha visible, re-verify if still on captcha page or already passed
        if (!(await detectCaptchaChallenge(page))) {
          log("✅ CAPTCHA Figma signup terselesaikan, melanjutkan...", "info");
        }
      }

      // Check for common error selectors
      const errorLoc = page.locator("[class*='error'], [id*='error'], [role='alert']").first();
      if (await errorLoc.count() > 0) {
        const errorText = await errorLoc.textContent().catch(() => "");
        if (errorText.trim()) {
          log(`❌ Error pada Figma signup: "${errorText.trim()}"`, "error");
          break;
        }
      }
      await sleep(intervalMs);
    }

    if (!isSuccess) {
      const screenshotPath = join(__dirname, "..", "public", "figma-signup-error.png");
      await page.screenshot({ path: screenshotPath }).catch(() => {});
      log(`⚠️ Figma signup tidak mengarah ke halaman verifikasi (URL: ${page.url()}). Screenshot disimpan di /figma-signup-error.png`, "warn");
      throw new Error(`Signup Figma gagal atau tertahan CAPTCHA. Silakan cek http://localhost:3001/figma-signup-error.png`);
    }

    log(`✅ Step 2 selesai (URL: ${page.url()})`);
  } finally {
    await page.close().catch(() => {});
  }
}

export async function getVerificationLink(context, { email }, log) {
  log("📨 Step 3: Cek inbox untuk email verifikasi Figma...");

  const MAX_WAIT_MS = 10 * 60 * 1000; // 10 minutes
  const POLL_MS     = 10000; // 10 seconds between refreshes
  const start       = Date.now();

  const page = await context.newPage();
  try {
    // ── 1. Fresh navigation (done once) ──
    await page.goto(getICLOUDUrl(), {
      waitUntil: "domcontentloaded",
      timeout: 30000,
    });
    await sleep(2000);

    // ── 2. Login with voucher if needed ──
    const loginHidden = await page.locator("#login-overlay")
      .evaluate((el) => el.classList.contains("hidden"))
      .catch(() => true);

    if (!loginHidden) {
      log("🔐 Re-login voucher...");
      await page.locator("#login-code").fill(VOUCHER);
      await page.locator("#login-submit").click();
      await page.waitForSelector("#btn-generate", { timeout: 15000 });
      await sleep(1000);
      log("✅ Re-login berhasil");
    }

    // ── 3. Open saved email list ──
    await page.locator("#btn-show-saved").waitFor({ state: "visible", timeout: 8000 });
    await page.locator("#btn-show-saved").click();
    await page.locator("#step-saved").waitFor({ state: "visible", timeout: 8000 });
    await sleep(2000);

    // ── 4. Find our email and click the CARD via DOM (page.evaluate) ──
    const responsePromise = page.waitForResponse(
      (res) => res.url().includes("path=inbox") && res.status() === 200,
      { timeout: 15000 }
    ).catch(() => null);

    const cardClicked = await page.evaluate((targetEmail) => {
      const copyBtns = document.querySelectorAll(".btn-copy-saved");
      for (const btn of copyBtns) {
        if (btn.dataset.email === targetEmail) {
          const card = btn.closest(".bg-white") || btn.parentElement;
          if (card) {
            card.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
            return true;
          }
        }
      }
      const spans = document.querySelectorAll("span.font-mono, span.text-blue-600");
      for (const span of spans) {
        if (span.textContent.trim() === targetEmail) {
          const card = span.closest(".bg-white") || span.parentElement;
          if (card) {
            card.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
            return true;
          }
        }
      }
      return false;
    }, email);

    if (!cardClicked) {
      throw new Error(`Email ${email} tidak ditemukan di saved list.`);
    }
    log(`✅ Card diklik via DOM evaluate: ${email}`);

    // Wait for the initial inbox response
    log("⏳ Menunggu respon API inbox...");
    await responsePromise;
    await sleep(1000); // Allow DOM to update

    // ── 5. Wait for inbox view ──
    try {
      await page.locator("#step-inbox").waitFor({ state: "visible", timeout: 15000 });
    } catch {
      const inboxMsgVisible = await page.locator("#inbox-messages").isVisible().catch(() => false);
      if (!inboxMsgVisible) {
        throw new Error("#step-inbox tidak muncul setelah klik card.");
      }
    }
    log("📬 Inbox view terbuka");

    // ── 6. Polling loop on the SAME page ──
    while (Date.now() - start < MAX_WAIT_MS) {
      const elapsed = Math.floor((Date.now() - start) / 1000);

      // Wait for "Memuat pesan..." to disappear
      try {
        await page.waitForFunction(() => {
          const el = document.getElementById("inbox-messages");
          if (!el) return false;
          const text = el.textContent || "";
          return !text.includes("Memuat pesan") && text.trim().length > 0;
        }, { timeout: 15000 });
      } catch (err) {
        log("⚠️ Timeout menunggu loading inbox selesai, lanjut cek...");
      }

      // Read inbox content
      const inboxText = await page.locator("#inbox-messages").textContent({ timeout: 3000 }).catch(() => "");
      const hasFigma = inboxText.toLowerCase().includes("figma");

      if (hasFigma) {
        log("✅ Email Figma terdeteksi di inbox!");

        // Click the message card
        let clicked = false;

        // Try XPath first (without offsets to be more robust)
        try {
          const xpathEl = page.locator("xpath=//*[@id='inbox-messages']/div/div[1]/div[2]/div[1]");
          if (await xpathEl.count() > 0) {
            await xpathEl.click({ timeout: 8000 });
            clicked = true;
            log("✅ Klik via XPath inbox-messages/div/div[1]/div[2]/div[1]");
          }
        } catch (e) {
          log(`⚠️ XPath click failed: ${e.message.split("\n")[0]}`, "warn");
        }

        // Fallback 1: Click the card container header directly (e.g. the clickable header element)
        if (!clicked) {
          try {
            const headerEl = page.locator("#inbox-messages div.cursor-pointer").first();
            if (await headerEl.count() > 0) {
              await headerEl.click({ timeout: 8000, force: true });
              clicked = true;
              log("✅ Klik via header card cursor-pointer");
            }
          } catch (e) {
            log(`⚠️ Header click failed: ${e.message.split("\n")[0]}`, "warn");
          }
        }

        // Fallback 2: Click text containing "Figma"
        if (!clicked) {
          try {
            await page.locator("#inbox-messages").locator("text=Figma").first()
              .click({ timeout: 5000, force: true });
            clicked = true;
            log("✅ Klik via text=Figma");
          } catch {}
        }

        if (!clicked) {
          log("⚠️ Tidak bisa klik pesan inbox, coba refresh dan ulangi...", "warn");
          await sleep(3000);
          continue;
        }

        // Wait for iframe/details to load
        await sleep(6000);

        // Get verify link
        let verifyUrl = null;
        let allFrames = [];
        for (let i = 0; i < 5; i++) {
          allFrames = page.frames().filter(f => f !== page.mainFrame());
          if (allFrames.length > 0) {
            log(`🖼️ Frames found: ${allFrames.length} (after ${(i + 1) * 2}s)`);
            break;
          }
          await sleep(2000);
        }

        if (allFrames.length > 0) {
          for (const frame of allFrames) {
            try {
              await frame.waitForLoadState("domcontentloaded", { timeout: 5000 }).catch(() => {});
              const linkEl = await frame.$(
                "tr:nth-of-type(3) a, " +
                "a[href*='figma.com/verify'], " +
                "a[href*='figma.com/email'], " +
                "a[href*='figma.com/auth'], " +
                "[aria-label='Verify email']"
              );
              if (linkEl) {
                verifyUrl = await linkEl.getAttribute("href");
                if (verifyUrl) {
                  log(`🔗 Link dari frame: ${verifyUrl.substring(0, 60)}...`);
                  break;
                }
              }
            } catch {}
          }
        }

        if (!verifyUrl) {
          try {
            await page.waitForSelector("iframe", { timeout: 5000 }).catch(() => {});
            for (const sel of [
              "tr:nth-of-type(3) a",
              "a:has-text('Verify email')",
              "a:has-text('Verify')",
              "a[href*='figma.com']",
            ]) {
              const fl = page.frameLocator("iframe").locator(sel).first();
              if (await fl.count().catch(() => 0) > 0) {
                verifyUrl = await fl.getAttribute("href", { timeout: 5000 });
                if (verifyUrl) {
                  log(`🔗 Link dari frameLocator (${sel}): ${verifyUrl.substring(0, 60)}...`);
                  break;
                }
              }
            }
          } catch {}
        }

        if (!verifyUrl) {
          try {
            const html = await page.content();
            const m = html.match(/href="(https?:\/\/[^"]*figma\.com\/email\/[^"]+)"/) ||
                      html.match(/href="(https?:\/\/[^"]*figma\.com[^"]*(?:verify|confirm)[^"]*)"/) ||
                      html.match(/(https?:\/\/www\.figma\.com\/[^\s"<>]*(?:verify|confirm)[^\s"<>]*)/);
            if (m) {
              verifyUrl = m[1];
              log(`🔗 Link dari HTML source: ${verifyUrl.substring(0, 60)}...`);
            }
          } catch {}
        }

        if (verifyUrl) {
          log("✅ Step 3 selesai — link verifikasi didapat!");
          await page.close();
          return verifyUrl;
        }

        log("⚠️ Iframe email tidak mengandung link Figma, coba lagi...");
      }

      // If Figma email is not found yet, click refresh and wait
      const preview = inboxText.trim().substring(0, 80) || "(kosong)";
      log(`⏳ Belum ada email Figma (${elapsed}s). Inbox: "${preview}". Refreshing...`);

      // Setup response promise before clicking refresh
      const nextResponsePromise = page.waitForResponse(
        (res) => res.url().includes("path=inbox") && res.status() === 200,
        { timeout: 15000 }
      ).catch(() => null);

      // Click "Refresh Inbox"
      await page.evaluate(() => {
        const btn = document.getElementById("btn-refresh-inbox");
        if (btn) btn.click();
      }).catch(() => {});

      // Wait for API response
      await nextResponsePromise;
      await sleep(3000); // Wait for DOM to render
    }

    throw new Error(`Timeout ${MAX_WAIT_MS / 1000}s: Email verifikasi tidak muncul.`);
  } catch (err) {
    await page.close().catch(() => {});
    throw err;
  }
}

// ────────────────────────────────────────────────────────────────────
// STEP 4: Open verification link
// ────────────────────────────────────────────────────────────────────
export async function verifyFigmaEmail(context, verifyUrl, log) {
  log("✔️ Step 4: Buka link verifikasi...");
  const page = await context.newPage();
  try {
    await page.goto(verifyUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(4000);
    log(`✅ Verifikasi selesai (URL: ${page.url()})`);
  } finally {
    await page.close().catch(() => {});
  }
}

// ────────────────────────────────────────────────────────────────────
// STEP 5: Login weave.figma.com + capture Bearer token
//
// Puppeteer recording flow:
//  1. Navigate to https://weave.figma.com/
//  2. Click "Try Now": #try_now_top > div.nav-button-spacer
//  3. Click "Log in with Figma": button:nth-of-type(2) > span
//  4. Figma OAuth auto-completes using existing session
//  5. Capture Bearer token from API requests
// ────────────────────────────────────────────────────────────────────
export async function captureWeavyToken(context, { email, password }, log) {
  log("🌐 Step 5: Login weave.figma.com & tangkap Bearer token...");
  const page = await context.newPage();
  let capturedToken = null;

  // Intercept all requests to capture Authorization header
  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. Navigate to weave.figma.com ──
    log("📍 Navigasi ke weave.figma.com...");
    await page.goto(WEAVE_URL, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(3000);

    const url1 = page.url();
    log(`📍 URL setelah navigate: ${url1}`);

    // If already inside Weave (dashboard), wait for token
    if (url1.includes("weave.figma.com") && !url1.includes("login") &&
        !url1.includes("signin") && url1 !== WEAVE_URL &&
        !url1.endsWith("/")) {
      log("✅ Sudah masuk Weave dashboard!");
      await sleep(8000);
    } else {
      // ── 2. Click "Try Now" button (from Puppeteer recording) ──
      // Selector: #try_now_top > div.nav-button-spacer
      log("🖱️ Klik tombol 'Try Now'...");
      const tryNowClicked = await page.evaluate(() => {
        const el = document.querySelector("#try_now_top > div.nav-button-spacer") ||
                   document.querySelector("#try_now_top .nav-button-spacer") ||
                   document.querySelector("[id*='try'] .nav-button-spacer");
        if (el) { el.click(); return true; }
        return false;
      });

      if (tryNowClicked) {
        log("✅ 'Try Now' diklik");
        await page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 15000 }).catch(() => {});
        await sleep(2000);
      } else {
        // Fallback selectors for Try Now
        for (const sel of [
          "#try_now_top > div.nav-button-spacer",
          "#try_now_top .nav-button-spacer",
          "a:has-text('Try now')",
          "a:has-text('Get started')",
          "button:has-text('Try')",
        ]) {
          if (await page.locator(sel).count() > 0) {
            await page.locator(sel).first().click({ timeout: 5000 });
            await page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 10000 }).catch(() => {});
            await sleep(2000);
            log(`✅ Try Now diklik via: ${sel}`);
            break;
          }
        }
      }

      const url2 = page.url();
      log(`📍 URL setelah Try Now: ${url2}`);
      await sleep(1500);

      // ── 3. Click "Log in with Figma" (from Puppeteer recording) ──
      // Selector: button:nth-of-type(2) > span  OR  text 'Log in with Figma'
      log("🖱️ Klik 'Log in with Figma'...");

      let loginClicked = false;

      // Try Puppeteer recording selectors first
      for (const sel of [
        "button:nth-of-type(2) > span",                          // exact from recording
        "[data-testid='signin-page-content'] button:nth-of-type(2)", // XPath parent context
        "button:has-text('Log in with Figma')",
        "button:has-text('Log in with')",
        "button:has-text('Login with Figma')",
        "a:has-text('Log in with Figma')",
      ]) {
        try {
          if (await page.locator(sel).count() > 0) {
            await page.locator(sel).first().click({ timeout: 6000 });
            loginClicked = true;
            log(`✅ 'Log in with Figma' diklik via: ${sel}`);
            await page.waitForNavigation({ waitUntil: "domcontentloaded", timeout: 20000 }).catch(() => {});
            await sleep(3000);
            break;
          }
        } catch {}
      }

      if (!loginClicked) {
        log("⚠️ Tombol 'Log in with Figma' tidak ditemukan.", "warn");
      }

      log(`📍 URL setelah login: ${page.url()}`);
    }

    // Wait for initial load + token (Bearer already captured during OAuth)
    log("⏳ Menunggu Bearer token...");
    for (let i = 0; i < 40; i++) {
      if (capturedToken) break;
      await handleFigmaOauthConsentPage(page, log);
      await sleep(1000);
    }

    if (!capturedToken) {
      throw new Error("Bearer token tidak tertangkap. Coba jalankan bot lagi.");
    }

    // Programmatically create and save the Kling 3.0 recipe under the new account
    let capturedRecipeId = await ensureKling3Recipe(capturedToken, null, log);

    await page.close();

    log(`✅ Step 5 selesai! RecipeId: ${capturedRecipeId}`);
    return { token: capturedToken, recipeId: capturedRecipeId };
  } catch (err) {
    await page.close().catch(() => {});
    throw err;
  }
}

// Helper untuk mendapatkan konfigurasi launching browser (termasuk proxy)
function getLaunchOptions(hl) {
  const options = {
    headless: hl,
    args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled"],
    slowMo: 50,
  };

  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;

    // Coba baca dari file proxies.txt di root directory terlebih dahulu
    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) {
        // Pilih satu proxy secara acak dari list
        const randomLine = lines[Math.floor(Math.random() * lines.length)];
        const parts = randomLine.split(":");
        if (parts.length >= 2) {
          const host = parts[0];
          const port = parts[1];
          const username = parts[2] || "";
          const password = parts[3] || "";
          proxySelected = {
            server: `http://${host}:${port}`,
            username,
            password
          };
          console.log(`[Proxy] Menggunakan proxy acak dari proxies.txt: http://${host}:${port}`);
        }
      }
    } catch (e) {
      // file tidak ditemukan atau error baca, lanjut ke fallback .env
    }

    // Jika tidak ada di proxies.txt, gunakan dari .env sebagai fallback
    if (!proxySelected && process.env.PROXY_SERVER) {
      proxySelected = {
        server: process.env.PROXY_SERVER,
        username: process.env.PROXY_USERNAME || "",
        password: process.env.PROXY_PASSWORD || ""
      };
      console.log(`[Proxy] Menggunakan proxy dari .env: ${process.env.PROXY_SERVER}`);
    }

    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;
}

// Helper to programmatically create and configure a Kling 3.0 recipe under the user's account if needed
async function ensureKling3Recipe(token, recipeId, log) {
  const apiHeaders = {
    authorization: `Bearer ${token}`,
    accept: "application/json, text/plain, */*",
    "content-type": "application/json",
    "x-weavy-auth-provider": "firebase",
    "x-app-version": "4.1.306",
    origin: "https://app.weavy.ai",
  };

  const WEAVY_API = process.env.WEAVY_API_URL || "https://api.weavy.ai";
  const DEFAULT_RECIPE_ID = process.env.WEAVY_RECIPE_ID || "G6jw67AIcRhGA8eKDF0WEu";

  // Auto-approve the unverified Kling 3.0 model for this workspace/account
  try {
    log("🔓 Auto-approve model unverified Kling 3.0 (kwaivgi/\\rkling-v3-motion-control)...");
    const approveRes = await fetch(`${WEAVY_API}/api/v1/workspaces/models/approve`, {
      method: "POST",
      headers: apiHeaders,
      body: JSON.stringify({ modelIds: ["kwaivgi/\\rkling-v3-motion-control"] }),
    });
    if (approveRes.ok) {
      log("✅ Model unverified berhasil disetujui.");
    } else {
      const approveText = await approveRes.text();
      log(`⚠️ Respon approve model unverified: ${approveRes.status} - ${approveText}`, "warn");
    }
  } catch (err) {
    log(`⚠️ Gagal melakukan approve model unverified: ${err.message}`, "warn");
  }

  // Check if current recipe is already Kling 3.0 (contains replicating replicate model name)
  if (recipeId && recipeId !== DEFAULT_RECIPE_ID && recipeId !== "G6jw67AIcRhGA8eKDF0WEu") {
    try {
      const r = await fetch(`${WEAVY_API}/api/v1/recipes/${recipeId}`, { headers: apiHeaders });
      if (r.ok) {
        const details = await r.json();
        const detailsStr = JSON.stringify(details);
        if (detailsStr.includes("kwaivgi")) {
          log(`✅ Recipe ${recipeId} terverifikasi Kling 3.0. Meng-update flow template terbaru...`);
          let FLOW_TEMPLATE = {};
          try {
            FLOW_TEMPLATE = JSON.parse(readFileSync(join(__dirname, "../data/flow-template.json"), "utf8"));
            await fetch(`${WEAVY_API}/api/v1/recipes/${recipeId}/save`, {
              method: "POST",
              headers: apiHeaders,
              body: JSON.stringify(FLOW_TEMPLATE)
            });
            log(`✅ Flow template recipe ${recipeId} berhasil diperbarui.`);
          } catch (err) {
            log(`⚠️ Gagal memperbarui flow template recipe ${recipeId}: ${err.message}`, "warn");
          }
          return recipeId;
        }
      }
    } catch (e) {
      log(`⚠️ Gagal fetch detail recipe: ${e.message}`);
    }
  }

  log("🔀 Recipe Kling 2.6 lama terdeteksi atau tidak valid. Membuat recipe Kling 3.0 baru via API...");

  let FLOW_TEMPLATE = {};
  try {
    FLOW_TEMPLATE = JSON.parse(readFileSync(join(__dirname, "../data/flow-template.json"), "utf8"));
  } catch (err) {
    log(`⚠️ Gagal membaca flow-template.json: ${err.message}`, "warn");
    return recipeId || DEFAULT_RECIPE_ID;
  }

  try {
    // 1. Create recipe
    const createRes = await fetch(`${WEAVY_API}/api/v1/recipes/create`, {
      method: "POST",
      headers: apiHeaders,
      body: "{}"
    });

    if (!createRes.ok) {
      throw new Error(`Create recipe failed: ${createRes.status}`);
    }

    const createData = await createRes.json();
    const newRecipeId = createData.id || createData.recipeId || createData.data?.id;
    if (!newRecipeId) {
      throw new Error("No recipe ID returned in create response");
    }

    // 2. Save template flow
    const saveRes = await fetch(`${WEAVY_API}/api/v1/recipes/${newRecipeId}/save`, {
      method: "POST",
      headers: apiHeaders,
      body: JSON.stringify(FLOW_TEMPLATE)
    });

    if (!saveRes.ok) {
      throw new Error(`Save flow template failed: ${saveRes.status}`);
    }

    log(`✅ Recipe Kling 3.0 berhasil dibuat via API: ${newRecipeId}`);
    return newRecipeId;

  } catch (err) {
    log(`❌ Gagal membuat recipe Kling 3.0 via API: ${err.message}. Pakai fallback template recipe ID.`, "warn");
    return recipeId || DEFAULT_RECIPE_ID;
  }
}

// ────────────────────────────────────────────────────────────────────
// REVIVE: Login back into an existing account to capture a fresh token
// ────────────────────────────────────────────────────────────────────
export async function refreshExistingAccountToken(accountId, email, password, onLog = null) {
  const log = makeLogger(onLog);
  let browser = null;

  log(`🔄 Memulai proses revive/login ulang untuk akun: ${email}...`, "start");

  try {
    const hl = isHeadless();
    log(`🌐 Membuka browser (headless: ${hl})...`);
    browser = await chromium.launch(getLaunchOptions(hl));
    activeBrowser = 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 },
    });
    context.setDefaultTimeout(30000);

    // Diagnosis IP Browser
    try {
      const ipPage = await context.newPage();
      await ipPage.goto("https://api.ipify.org?format=json", { timeout: 10000 });
      const ipJson = await ipPage.textContent("body");
      log(`📡 [Diagnosis] Browser IP aktif (Revive): ${ipJson.trim()}`);
      await ipPage.close();
    } catch (e) {
      log(`⚠️ [Diagnosis] Gagal memeriksa IP browser (Revive): ${e.message}`, "warn");
    }

    const page = await context.newPage();
    let capturedToken = null;

    // Intercept Bearer token
    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)}...)`);
        }
      }
    });

    log("📍 Navigasi ke weave.figma.com...");
    await page.goto(WEAVE_URL, { waitUntil: "domcontentloaded", timeout: 30000 });
    await sleep(3000);

    // Klik Try Now
    log("🖱️ Klik tombol 'Try Now'...");
    let tryNowClicked = await page.evaluate(() => {
      const el = document.querySelector("#try_now_top > div.nav-button-spacer") ||
                 document.querySelector("#try_now_top .nav-button-spacer") ||
                 document.querySelector("[id*='try'] .nav-button-spacer");
      if (el) { el.click(); return true; }
      return false;
    });

    if (!tryNowClicked) {
      for (const sel of [
        "#try_now_top > div.nav-button-spacer",
        "#try_now_top .nav-button-spacer",
        "a:has-text('Try now')",
        "button:has-text('Try')",
      ]) {
        if (await page.locator(sel).count() > 0) {
          await page.locator(sel).first().click({ timeout: 5000 });
          tryNowClicked = true;
          break;
        }
      }
    }
    await sleep(3000);

    // Klik Log in with Figma
    log("🖱️ Klik 'Log in with Figma'...");
    let loginClicked = false;
    for (const sel of [
      "button:nth-of-type(2) > span",
      "button:has-text('Log in with Figma')",
      "button:has-text('Login with Figma')",
    ]) {
      try {
        if (await page.locator(sel).count() > 0) {
          await page.locator(sel).first().click({ timeout: 6000 });
          loginClicked = true;
          break;
        }
      } catch {}
    }
    await sleep(5000);

    // Cek jika Figma meminta email & password (OAuth login page)
    let currentUrl = page.url();
    log(`📍 URL saat ini: ${currentUrl}`);

    // Jika masuk ke Weavy signin page, klik login Figma dulu
    if (currentUrl.includes("weavy.ai/signin") || currentUrl.includes("app.weavy.ai/signin")) {
      log("🖱️ Berada di halaman signin Weavy, klik 'Log in with Figma'...");
      let weavyLoginClicked = false;
      for (const sel of [
        "button:has-text('Log in with Figma')",
        "button:has-text('Login with Figma')",
        "button:nth-of-type(2)",
        "button",
      ]) {
        try {
          const el = page.locator(sel);
          if (await el.count() > 0) {
            await el.first().click({ timeout: 5000 });
            weavyLoginClicked = true;
            log(`✅ Tombol login Weavy diklik via: ${sel}`);
            await sleep(5000);
            break;
          }
        } catch {}
      }
      currentUrl = page.url();
      log(`📍 URL setelah klik login Weavy: ${currentUrl}`);
    }

    if (currentUrl.includes("figma.com")) {
      log("🔐 Memeriksa halaman login Figma...");
      
      let showLoginForm = false;
      try {
        await page.locator("input[name='email'], input[type='email']").first().waitFor({ state: "visible", timeout: 4000 });
        showLoginForm = true;
      } catch (e) {
        log("ℹ️ Halaman login Figma tidak mendeteksi field input email, mungkin langsung ke halaman otorisasi OAuth.");
      }

      if (showLoginForm) {
        log("🔐 Melakukan autentikasi di halaman login Figma...");
        await page.locator("input[name='email'], input[type='email']").first().fill(email);
        await sleep(500);

        // Cek password field
        if (await page.locator("input[type='password']").count() > 0) {
          await page.locator("input[type='password']").first().fill(password);
          await sleep(500);
          const submitBtn = page.locator("button[type='submit'], button:has-text('Log in'), button:has-text('Submit')").first();
          await submitBtn.click();
        } else {
          // Step-by-step
          const continueBtn = page.locator("button:has-text('Continue'), button[type='submit']").first();
          await continueBtn.click();
          await sleep(2000);
          
          await page.locator("input[type='password']").first().waitFor({ state: "visible", timeout: 10000 });
          await page.locator("input[type='password']").first().fill(password);
          await sleep(500);
          
          const submitBtn = page.locator("button[type='submit'], button:has-text('Log in'), button:has-text('Continue')").first();
          await submitBtn.click();
        }
        log("⏳ Menunggu verifikasi login figma...");
      }

      let captchaDetected = false;
      let captchaStartAt = null;
      const maxWaitMs = hl ? 15000 : 180000;
      const CAPTCHA_MAX_WAIT = hl ? 90 * 1000 : 150 * 1000;
      const loopCount = Math.ceil(maxWaitMs / 1000);
      for (let i = 0; i < loopCount; i++) {
        if (capturedToken) break;

        const currentLoginUrl = page.url().toLowerCase();

        // Handle OAuth consent page if redirected here
        await handleFigmaOauthConsentPage(page, log);

        const loginFinished =
          !currentLoginUrl.includes("figma.com/login") &&
          !currentLoginUrl.includes("figma.com/oauth") &&
          !currentLoginUrl.includes("figma.com/auth") &&
          !currentLoginUrl.includes("figma.com") &&
          !currentLoginUrl.includes("challenge") &&
          !currentLoginUrl.includes("captcha");

        if (loginFinished) break;

        // Detect CAPTCHA at any time (NOT just headful) so admin gets alerted
        if (!captchaDetected && await detectCaptchaChallenge(page)) {
          captchaDetected = true;
          captchaStartAt = Date.now();
          log("💡 CAPTCHA terdeteksi pada halaman login Figma (Revive)! " + (hl ? "(Mode headless - alarm dibunyikan ke admin panel.)" : "Jendela browser terbuka. Silakan selesaikan CAPTCHA di browser. Bot akan otomatis melanjutkan setelah sukses."), "captcha");
        }

        // If captcha was detected, keep waiting until resolved or timeout
        if (captchaDetected) {
          if (Date.now() - captchaStartAt > CAPTCHA_MAX_WAIT) {
            log(`⏱️ CAPTCHA Revive tidak terselesaikan dalam ${CAPTCHA_MAX_WAIT / 1000}s. Bot menyerah.`, "error");
            break;
          }
          if (!(await detectCaptchaChallenge(page))) {
            log("✅ CAPTCHA Revive terselesaikan, melanjutkan...", "info");
          }
        }

        await sleep(1000);
      }
    }

    log("⏳ Menunggu Bearer token ter-intercept...");
    await sleep(6000);

    if (!capturedToken) {
      throw new Error("Gagal menangkap Bearer token selama relogin.");
    }

    // Ambil/Query Recipe ID untuk memastikan recipeId valid
    let capturedRecipeId = DEFAULT_RECIPE_ID;
    const apiHeaders = {
      authorization: `Bearer ${capturedToken}`,
      accept: "application/json, text/plain, */*",
      "x-weavy-auth-provider": "firebase",
      origin: "https://app.weavy.ai",
    };

    try {
      const resp = await fetch(`${WEAVY_API}/api/v1/recipes`, { headers: apiHeaders });
      if (resp.ok) {
        const data = await resp.json();
        const recipes = Array.isArray(data) ? data : (data.items || data.data || []);
        if (recipes.length > 0) {
          const kl = recipes.find(r => r.name?.toLowerCase().includes("kling")) || recipes[0];
          capturedRecipeId = kl.id;
          log(`✅ Recipe ID terverifikasi: ${capturedRecipeId}`);
        }
      }
    } catch {}

    // Ensure Kling 3.0 recipe ID is created and saved
    capturedRecipeId = await ensureKling3Recipe(capturedToken, capturedRecipeId, log);

    // Update di database
    Accounts.updateAccount(accountId, { token: capturedToken, recipeId: capturedRecipeId, expired: false });
    log(`🎉 REVIVE SUKSES! Token baru disimpan untuk ${email}`, "success");

    await browser.close();
    activeBrowser = null;
    return { ok: true, token: capturedToken, recipeId: capturedRecipeId };
  } catch (err) {
    log(`❌ REVIVE GAGAL: ${err.message}`, "error");
    if (browser) await browser.close().catch(() => {});
    activeBrowser = null;
    return { ok: false, error: err.message };
  }
}

// ────────────────────────────────────────────────────────────────────
// MAIN: Full flow orchestration
// ────────────────────────────────────────────────────────────────────
export async function runFullFlow(onLog = null) {
  const log = makeLogger(onLog);
  let browser = null;

  log("🚀 Memulai Auto Token Bot...", "start");

  if (!VOUCHER) {
    const err = "❌ ICLOUD_SETAN_VOUCHER belum diset di .env!";
    log(err, "error");
    return { ok: false, error: err };
  }

  try {
    const hl = isHeadless();
    log(`🌐 Membuka browser (headless: ${hl})...`);
    browser = await chromium.launch(getLaunchOptions(hl));
    activeBrowser = 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 },
    });
    context.setDefaultTimeout(30000);

    // Step 1: Generate email (page closes after done)
    const { email, password } = await generateAndOpenInbox(context, log);

    // Step 2: Register Figma (separate tab, closes after done)
    await registerFigmaAccount(context, { email, password }, log);

    // Wait for Figma to send verification email (typically 30-90s)
    log("⏳ Menunggu email verifikasi Figma dikirim (20 detik)...");
    await sleep(20000);

    // Step 3: Fresh navigation to iCloud Setan inbox — matches Puppeteer recording
    const verifyUrl = await getVerificationLink(context, { email }, log);

    // Step 4: Verify email
    await verifyFigmaEmail(context, verifyUrl, log);
    await sleep(3000);

    // Step 5: Capture token
    const { token, recipeId } = await captureWeavyToken(context, { email, password }, log);

    // Save
    log("💾 Menyimpan token ke pool...");
    const label = email.split("@")[0];
    const acc = Accounts.addAccount({ token, recipeId, label, email, password });

    log(`🎉 SELESAI! Akun baru: ${acc.label} (${acc.id})`, "success");
    await browser.close();
    activeBrowser = null;
    return { ok: true, accountId: acc.id, label: acc.label, email };
  } catch (err) {
    log(`❌ ERROR: ${err.message}`, "error");
    if (browser) await browser.close().catch(() => {});
    activeBrowser = null;
    return { ok: false, error: err.message };
  }
}
