const I18N = {
  en: {
    languageLabel: 'Language',
    productShort: 'FB COMMENT SCANNER',
    licenseTitle: 'License / VIP',
    emailLabel: 'Account email',
    licenseKeyLabel: 'License key',
    activateLicense: 'Activate',
    freePlanText: 'Free: 50 rows/day, 5 scans/day, UID resolver included, reactions locked.',
    vipPlanText: 'VIP: 199,000 VND/month, 1,490,000 VND/year, 2,990,000 VND lifetime.',
    contactUpgrade: 'Contact to upgrade VIP',
    upgradePageOpened: 'Upgrade page opened. Contact support there to receive your license key.',
    billingPlanLabel: 'Chọn gói VIP',
    planMonth: 'VIP tháng - 199.000đ',
    planYear: 'VIP năm - 1.490.000đ',
    planLifetime: 'VIP vĩnh viễn - 2.990.000đ',
    billingPlanLabel: 'VIP plan',
    planMonth: 'Monthly VIP - 199,000 VND',
    planYear: 'Yearly VIP - 1,490,000 VND',
    planLifetime: 'Lifetime VIP - 2,990,000 VND',
    deviceLabel: 'Device ID',
    copyDevice: 'Copy',
    licenseServerLabel: 'License server URL',
    licenseIdle: 'Enter email, then click Activate to check your account.',
    planFree: 'FREE',
    planVip: 'VIP',
    usageFree: (rowsLeft, scansLeft) => `Free remaining today: ${rowsLeft} rows, ${scansLeft} scans.`,
    usageVip: 'VIP active. Full feature access is enabled.',
    invalidEmail: 'Enter a valid email to activate your account.',
    emailSaved: 'Email saved. Server VIP verification will be connected in the payment step.',
    paymentNotConfigured: 'Open the upgrade page and contact support to receive a license key.',
    paymentEmailRequired: 'Enter a valid email before requesting an upgrade.',
    creatingPayment: 'Opening upgrade page...',
    paymentCreated: 'Upgrade page opened. Contact support there to receive your license key.',
    paymentFailed: 'Could not open upgrade page.',
    paymentPending: 'Payment is not confirmed yet. Ask admin to mark the order as paid, then activate again.',
    licenseKeyReceived: 'Payment confirmed. License key was filled automatically.',
    copiedDevice: 'Device ID copied.',
    copyFailed: 'Could not copy Device ID. Select it manually.',
    checkingLicense: 'Checking license server...',
    licenseVipActivated: 'VIP activated on this device.',
    licenseFreeActivated: 'Free account activated on this device.',
    licenseServerFailed: 'Could not reach license server. Local Free mode is still available.',
    licenseDeviceLimit: 'This email has reached the device limit.',
    licenseExpired: 'VIP expired. Free mode is still available.',
    licenseKeyRequired: 'This VIP account requires a license key.',
    licenseKeyInvalid: 'License key is incorrect for this email.',
    freeReactionLocked: 'Likes / reactions are VIP only. Free can scan comments and resolve UID.',
    freeRowsUsed: 'Free limit reached: 50 exported rows today. Upgrade to VIP for full scans.',
    freeScansUsed: 'Free limit reached: 5 scans today. Upgrade to VIP for full scans.',
    freeLimitApplied: (rows) => `Free limit applied. This scan is capped at ${rows} remaining rows today.`,
    postLabel: 'Enter Facebook Post Link / Post ID',
    postHelp: 'How to find a post link?',
    postPlaceholder: 'https://www.facebook.com/page/posts/123456789',
    filterLabel: 'Filter',
    mostRelevant: 'Most relevant',
    oldest: 'Oldest',
    legacyFilterLabel: 'Filter (Legacy)',
    legacyFilterHint: 'Legacy sorting is attempted when Facebook exposes matching buttons.',
    newest: 'Newest',
    allComments: 'All comments',
    scanTargetLabel: 'Scan Target',
    comments: 'Comments',
    likesReactions: 'Likes / reactions',
    scanTargetHint: 'Choose one mode. Selecting reactions turns off comment scanning.',
    nestedRepliesLabel: 'Nested Replies',
    fetchReplies: 'Fetch all nested replies',
    nestedRepliesHint: 'When enabled, reply groups under each visible comment will be expanded before export.',
    longScanLabel: 'Long Scan Protection',
    autoPauseLong: 'Auto pause during long scans',
    pauseEveryActions: 'Pause every actions',
    pauseSeconds: 'Pause seconds',
    longScanHint: 'The scraper waits after repeated scroll/click/fetch batches. Increase seconds if Facebook slows down loading.',
    uidResolverLabel: 'UID Resolver',
    resolveMissingUid: 'Resolve missing UID from profile URLs after scan',
    useLocalResolver: 'Use local resolver server',
    uidResolverHint: 'This fetches profile pages one by one. It is slower and only works when Facebook exposes the numeric id.',
    openResolver: 'Open Bulk UID Resolver',
    exportCount: 'Export Count',
    startExtracting: 'Start Extracting',
    stop: 'Stop',
    footer: 'Exports only Author, User ID, Profile URL, and Comment.',
    ready: 'Ready',
    running: 'Running',
    done: 'Done',
    attention: 'Attention',
    initialStatus: 'Open a Facebook post, or paste a post URL, then start extracting.',
    selectMode: 'Select Comments, Likes / reactions, or both.',
    openFacebookFirst: 'Open a Facebook post or enter a Facebook post URL first.',
    openingPost: 'Opening post and waiting for Facebook to load...',
    initializing: 'Initializing scraper...',
    scraperStarted: 'Scraper started. Keep this post tab open while it runs.',
    alreadyRunning: 'A scraper is already running on this tab.',
    connectFailed: 'Could not connect to Facebook page. Refresh the tab and try again.',
    stoppingExport: 'Stopping and exporting collected rows...',
    stopFailed: 'Stop request failed. The page may have been reloaded.',
    stopped: 'Stopped',
    finished: 'Finished',
    exportedUsers: (label, count) => `${label}. Exported ${count} users.`
  },
  vi: {
    productShort: 'FB COMMENT SCANNER',
    licenseTitle: 'Tài khoản / VIP',
    emailLabel: 'Email tài khoản',
    licenseKeyLabel: 'Mã license',
    activateLicense: 'Kích hoạt',
    freePlanText: 'Miễn phí: 50 dòng/ngày, 5 lần quét/ngày, có đổi UID, khóa reaction.',
    vipPlanText: 'VIP: 199.000đ/tháng, 1.490.000đ/năm, 2.990.000đ vĩnh viễn.',
    contactUpgrade: 'Liên hệ nâng cấp VIP',
    upgradePageOpened: 'Đã mở trang nâng cấp. Hãy liên hệ hỗ trợ tại đó để nhận mã license.',
    billingPlanLabel: 'Chọn gói VIP',
    planMonth: 'VIP tháng - 199.000đ',
    planYear: 'VIP năm - 1.490.000đ',
    planLifetime: 'VIP vĩnh viễn - 2.990.000đ',
    deviceLabel: 'Mã thiết bị',
    copyDevice: 'Sao chép',
    licenseServerLabel: 'URL server license',
    licenseIdle: 'Nhập email rồi bấm Kích hoạt để kiểm tra tài khoản.',
    planFree: 'Miễn phí',
    planVip: 'VIP',
    usageFree: (rowsLeft, scansLeft) => `Miễn phí còn lại hôm nay: ${rowsLeft} dòng, ${scansLeft} lần quét.`,
    usageVip: 'VIP đang hoạt động. Đã mở đầy đủ tính năng.',
    invalidEmail: 'Nhập email hợp lệ để kích hoạt tài khoản.',
    emailSaved: 'Đã lưu email. Bước sau sẽ nối server kiểm tra VIP.',
    paymentNotConfigured: 'Mở trang nâng cấp và liên hệ hỗ trợ để nhận mã license.',
    paymentEmailRequired: 'Nhập email hợp lệ trước khi yêu cầu nâng cấp.',
    creatingPayment: 'Đang mở trang nâng cấp...',
    paymentCreated: 'Đã mở trang nâng cấp. Hãy liên hệ hỗ trợ tại đó để nhận mã license.',
    paymentFailed: 'Không mở được trang nâng cấp.',
    paymentPending: 'Đơn chưa được xác nhận thanh toán. Hãy nhờ admin bấm Đã thanh toán rồi kích hoạt lại.',
    licenseKeyReceived: 'Đã xác nhận thanh toán. Mã license đã được tự điền.',
    copiedDevice: 'Đã sao chép mã thiết bị.',
    copyFailed: 'Không sao chép được mã thiết bị. Hãy copy thủ công.',
    checkingLicense: 'Đang kiểm tra server license...',
    licenseVipActivated: 'Đã kích hoạt VIP trên máy này.',
    licenseFreeActivated: 'Đã kích hoạt tài khoản miễn phí trên máy này.',
    licenseServerFailed: 'Không kết nối được server license. Vẫn có thể dùng chế độ miễn phí local.',
    licenseDeviceLimit: 'Email này đã vượt giới hạn số máy.',
    licenseExpired: 'VIP đã hết hạn. Vẫn có thể dùng chế độ miễn phí.',
    licenseKeyRequired: 'Tài khoản VIP này cần nhập mã license.',
    licenseKeyInvalid: 'Mã license không đúng với email này.',
    freeReactionLocked: 'Like / reaction chỉ dành cho VIP. Miễn phí chỉ quét bình luận và đổi UID.',
    freeRowsUsed: 'Đã hết giới hạn miễn phí: 50 dòng xuất hôm nay. Nâng cấp VIP để quét full.',
    freeScansUsed: 'Đã hết giới hạn miễn phí: 5 lần quét hôm nay. Nâng cấp VIP để quét full.',
    freeLimitApplied: (rows) => `Đang áp dụng giới hạn miễn phí. Lần này chỉ quét tối đa ${rows} dòng còn lại hôm nay.`,
    languageLabel: 'Ngôn ngữ',
    postLabel: 'Nhập link bài viết Facebook / ID bài viết',
    postHelp: 'Cách lấy link bài viết?',
    postPlaceholder: 'https://www.facebook.com/page/posts/123456789',
    filterLabel: 'Bộ lọc',
    mostRelevant: 'Phù hợp nhất',
    oldest: 'Cũ nhất',
    legacyFilterLabel: 'Bộ lọc cũ',
    legacyFilterHint: 'Chỉ áp dụng khi Facebook hiển thị nút sắp xếp tương ứng.',
    newest: 'Mới nhất',
    allComments: 'Tất cả bình luận',
    scanTargetLabel: 'Chế độ quét',
    comments: 'Bình luận',
    likesReactions: 'Like / cảm xúc',
    scanTargetHint: 'Chọn một chế độ. Chọn cảm xúc sẽ tắt quét bình luận.',
    nestedRepliesLabel: 'Phản hồi lồng nhau',
    fetchReplies: 'Lấy toàn bộ phản hồi',
    nestedRepliesHint: 'Khi bật, các nhóm phản hồi dưới bình luận đang thấy sẽ được mở trước khi xuất.',
    longScanLabel: 'Chống quét quá nhanh',
    autoPauseLong: 'Tự tạm dừng khi quét lâu',
    pauseEveryActions: 'Tạm dừng sau số thao tác',
    pauseSeconds: 'Số giây tạm dừng',
    longScanHint: 'Tool sẽ chờ sau nhiều lượt cuộn/click/tải. Tăng số giây nếu Facebook tải chậm.',
    uidResolverLabel: 'Đổi UID',
    resolveMissingUid: 'Đổi UID còn thiếu từ link profile sau khi quét',
    useLocalResolver: 'Dùng server đổi UID local',
    uidResolverHint: 'Tính năng này mở từng trang profile. Sẽ chậm hơn và chỉ hoạt động khi Facebook trả về ID số.',
    openResolver: 'Mở công cụ đổi UID hàng loạt',
    exportCount: 'Số lượng xuất',
    startExtracting: 'Bắt đầu quét',
    stop: 'Dừng',
    footer: 'Chỉ xuất Author, User ID, Profile URL và Comment.',
    ready: 'Sẵn sàng',
    running: 'Đang chạy',
    done: 'Xong',
    attention: 'Chú ý',
    initialStatus: 'Mở bài viết Facebook, hoặc dán link bài viết, rồi bắt đầu quét.',
    selectMode: 'Chọn Bình luận, Like / cảm xúc, hoặc cả hai.',
    openFacebookFirst: 'Hãy mở bài viết Facebook hoặc nhập link bài viết Facebook trước.',
    openingPost: 'Đang mở bài viết và chờ Facebook tải...',
    initializing: 'Đang khởi tạo bộ quét...',
    scraperStarted: 'Đã bắt đầu quét. Giữ tab bài viết đang mở trong lúc chạy.',
    alreadyRunning: 'Tab này đang có một phiên quét.',
    connectFailed: 'Không kết nối được tới trang Facebook. Hãy tải lại tab rồi thử lại.',
    stoppingExport: 'Đang dừng và xuất các dòng đã lấy...',
    stopFailed: 'Gửi lệnh dừng thất bại. Trang có thể đã được tải lại.',
    stopped: 'Đã dừng',
    finished: 'Hoàn tất',
    exportedUsers: (label, count) => `${label}. Đã xuất ${count} người dùng.`
  }
};

const LANGUAGE_KEY = 'fb_comments_export_language';
const LICENSE_KEY = 'fb_comments_export_license';
const USAGE_KEY = 'fb_comments_export_usage';
const DEVICE_ID_KEY = 'fb_comments_export_device_id';
const PRODUCT_CONFIG = {
  productName: 'FB Comment Scanner',
  freeDailyRows: 50,
  freeDailyScans: 5,
  freeAllowsUid: true,
  freeAllowsReactions: false,
  licenseApiUrl: 'https://toolfb.cloud',
  upgradePagePath: 'site/upgrade.html'
};

document.addEventListener('DOMContentLoaded', () => {
  const params = new URLSearchParams(window.location.search);
  const initialTargetTabId = Number(params.get('targetTabId') || 0);
  let activeScrapeTabId = initialTargetTabId || 0;

  const languageSelect = document.getElementById('languageSelect');
  const licenseEmail = document.getElementById('licenseEmail');
  const licenseKeyInput = document.getElementById('licenseKeyInput');
  const activateLicenseBtn = document.getElementById('activateLicenseBtn');
  const upgradeContactBtn = document.getElementById('upgradeContactBtn');
  const planState = document.getElementById('planState');
  const usageState = document.getElementById('usageState');
  const licenseStatus = document.getElementById('licenseStatus');
  const deviceIdText = document.getElementById('deviceIdText');
  const copyDeviceBtn = document.getElementById('copyDeviceBtn');
  const licenseServerUrl = document.getElementById('licenseServerUrl');
  const postInput = document.getElementById('postInput');
  const startBtn = document.getElementById('startBtn');
  const stopBtn = document.getElementById('stopBtn');
  const closePopupBtn = document.getElementById('closePopupBtn');
  const includeReplies = document.getElementById('includeReplies');
  const scanComments = document.getElementById('scanComments');
  const scanLikes = document.getElementById('scanLikes');
  const autoPause = document.getElementById('autoPause');
  const pauseEvery = document.getElementById('pauseEvery');
  const pauseSeconds = document.getElementById('pauseSeconds');
  const resolveMissingUid = document.getElementById('resolveMissingUid');
  const useLocalResolver = document.getElementById('useLocalResolver');
  const localResolverUrl = document.getElementById('localResolverUrl');
  const resolverBtn = document.getElementById('resolverBtn');
  const maxComments = document.getElementById('maxComments');
  const progressRange = document.getElementById('progressRange');
  const statusText = document.getElementById('statusText');
  const runState = document.getElementById('runState');
  const countValue = document.getElementById('countValue');
  let currentLanguage = getSavedLanguage();
  let deviceId = '';

  const t = (key, ...args) => {
    const value = (I18N[currentLanguage] && I18N[currentLanguage][key]) || I18N.en[key] || key;
    return typeof value === 'function' ? value(...args) : value;
  };

  const applyLanguage = () => {
    document.documentElement.lang = currentLanguage;
    languageSelect.value = currentLanguage;

    document.querySelectorAll('[data-i18n]').forEach((element) => {
      element.textContent = t(element.dataset.i18n);
    });

    document.querySelectorAll('[data-i18n-placeholder]').forEach((element) => {
      element.placeholder = t(element.dataset.i18nPlaceholder);
    });

    if (statusText.dataset.key) {
      setStatus(statusText.dataset.key, statusText.dataset.state || 'idle', true);
    } else if (!statusText.textContent.trim()) {
      setStatus('initialStatus');
    }

    updateLicenseUi();
  };

  const updateLicenseUi = () => {
    const license = getLicenseState();
    const usage = getTodayUsage();
    const isVip = isVipLicense(license);
    const rowsLeft = Math.max(0, PRODUCT_CONFIG.freeDailyRows - usage.rows);
    const scansLeft = Math.max(0, PRODUCT_CONFIG.freeDailyScans - usage.scans);

    licenseEmail.value = license.email || licenseEmail.value || '';
    licenseKeyInput.value = license.licenseKey || licenseKeyInput.value || '';
    licenseServerUrl.value = license.licenseApiUrl || licenseServerUrl.value || PRODUCT_CONFIG.licenseApiUrl;
    deviceIdText.textContent = deviceId || license.deviceId || 'Loading...';
    planState.textContent = isVip ? t('planVip') : t('planFree');
    planState.dataset.plan = isVip ? 'vip' : 'free';
    usageState.textContent = isVip ? t('usageVip') : t('usageFree', rowsLeft, scansLeft);
  };

  const setLicenseStatus = (messageOrKey, state = 'idle', isKey = false) => {
    const message = isKey ? t(messageOrKey) : messageOrKey;
    licenseStatus.textContent = message;
    licenseStatus.dataset.state = state;
  };

  const setStatus = (messageOrKey, state = 'idle', isKey = false) => {
    const message = isKey ? t(messageOrKey) : localizeRuntimeStatus(messageOrKey, currentLanguage);
    statusText.textContent = message;
    statusText.dataset.key = isKey ? messageOrKey : '';
    statusText.dataset.state = state;
    runState.textContent = state === 'working'
      ? t('running')
      : state === 'success'
        ? t('done')
        : state === 'warning'
          ? t('attention')
          : t('ready');
  };

  const getSelectedFilter = () => {
    const checked = document.querySelector('input[name="filter"]:checked');
    return checked ? checked.value : 'relevant';
  };

  const syncCount = () => {
    const value = Number(progressRange.value || 1000);
    maxComments.value = String(value);
    countValue.textContent = value >= 1000000 ? '1M' : value.toLocaleString('en-US');
  };

  progressRange.addEventListener('input', syncCount);
  maxComments.addEventListener('input', () => {
    const value = Math.max(1, Math.min(1000000, Number(maxComments.value || 1)));
    progressRange.value = String(value);
    countValue.textContent = value >= 1000000 ? '1M' : value.toLocaleString('en-US');
  });
  scanComments.addEventListener('change', () => {
    if (scanComments.checked) {
      scanLikes.checked = false;
    } else if (!scanLikes.checked) {
      scanComments.checked = true;
    }
  });
  scanLikes.addEventListener('change', () => {
    if (scanLikes.checked) {
      scanComments.checked = false;
    } else if (!scanComments.checked) {
      scanLikes.checked = true;
    }
  });
  syncCount();
  applyLanguage();
  setStatus('initialStatus', 'idle', true);
  initializeDeviceId().then((value) => {
    deviceId = value;
    updateLicenseUi();
    sendTelemetryEvent('popup_open', { deviceId });
  });

  languageSelect.addEventListener('change', () => {
    currentLanguage = languageSelect.value === 'vi' ? 'vi' : 'en';
    localStorage.setItem(LANGUAGE_KEY, currentLanguage);
    applyLanguage();
  });

  activateLicenseBtn.addEventListener('click', async () => {
    const email = licenseEmail.value.trim();
    if (!isValidEmail(email)) {
      setStatus('invalidEmail', 'warning', true);
      setLicenseStatus('invalidEmail', 'warning', true);
      return;
    }

    if (!deviceId) {
      setStatus('initializing', 'working', true);
      setLicenseStatus('initializing', 'working', true);
      return;
    }

    setStatus('checkingLicense', 'working', true);
    setLicenseStatus('checkingLicense', 'working', true);
    activateLicenseBtn.disabled = true;

    try {
      const result = await callLicenseServer('/api/license/activate', {
        email,
        licenseKey: normalizeLicenseKey(licenseKeyInput.value),
        orderId: '',
        deviceId,
        product: PRODUCT_CONFIG.productName,
        extensionVersion: chrome.runtime.getManifest().version
      }, licenseServerUrl.value);

      saveLicenseFromServer(email, deviceId, result, licenseServerUrl.value);
      updateLicenseUi();

      if (result.plan === 'vip') {
        setStatus('licenseVipActivated', 'success', true);
        setLicenseStatus('licenseVipActivated', 'success', true);
      } else {
        setStatus('licenseFreeActivated', 'success', true);
        setLicenseStatus('licenseFreeActivated', 'success', true);
      }
    } catch (error) {
      saveLicenseState({
        email,
        plan: 'free',
        licenseKey: normalizeLicenseKey(licenseKeyInput.value),
        pendingOrderId: '',
        deviceId,
        licenseApiUrl: normalizeApiUrl(licenseServerUrl.value || PRODUCT_CONFIG.licenseApiUrl),
        updatedAt: new Date().toISOString(),
        serverError: error.message || String(error)
      });
      updateLicenseUi();
      const statusKey = classifyLicenseError(error);
      setStatus(statusKey, 'warning', true);
      setLicenseStatus(statusKey, 'warning', true);
    } finally {
      activateLicenseBtn.disabled = false;
    }
  });

  upgradeContactBtn.addEventListener('click', () => {
    const upgradeUrl = chrome.runtime.getURL(PRODUCT_CONFIG.upgradePagePath);
    chrome.tabs.create({ url: upgradeUrl });
    setStatus('upgradePageOpened', 'success', true);
    setLicenseStatus('upgradePageOpened', 'success', true);
  });

  copyDeviceBtn.addEventListener('click', async () => {
    try {
      await copyText(deviceId || deviceIdText.textContent || '');
      setStatus('copiedDevice', 'success', true);
      setLicenseStatus('copiedDevice', 'success', true);
    } catch (error) {
      setStatus('copyFailed', 'warning', true);
      setLicenseStatus('copyFailed', 'warning', true);
    }
  });

  closePopupBtn.addEventListener('click', () => {
    window.close();
  });

  startBtn.addEventListener('click', async () => {
    if (!deviceId) {
      deviceId = await initializeDeviceId();
      updateLicenseUi();
    }

    const refreshedLicense = await refreshLicenseFromServer({
      silent: true,
      email: licenseEmail.value.trim(),
      licenseKey: normalizeLicenseKey(licenseKeyInput.value),
      deviceId,
      serverUrl: licenseServerUrl.value
    });
    if (refreshedLicense) {
      updateLicenseUi();
    }

    const postUrl = normalizeFacebookUrl(postInput.value.trim());
    const options = {
      filter: getSelectedFilter(),
      includeReplies: includeReplies.checked,
      scanComments: scanComments.checked,
      scanLikes: scanLikes.checked,
      autoPause: autoPause.checked,
      pauseEvery: Math.max(5, Math.min(500, Number(pauseEvery.value || 25))),
      pauseMs: Math.max(1000, Math.min(120000, Number(pauseSeconds.value || 7) * 1000)),
      resolveMissingUid: resolveMissingUid.checked,
      useLocalResolver: useLocalResolver.checked,
      localResolverUrl: localResolverUrl.value.trim() || 'http://127.0.0.1:8787',
      maxComments: Math.max(1, Math.min(1000000, Number(maxComments.value || 1000))),
      language: currentLanguage,
      licenseEmail: licenseEmail.value.trim(),
      licenseKey: normalizeLicenseKey(licenseKeyInput.value),
      deviceId
    };

    if (!options.scanComments && !options.scanLikes) {
      setStatus('selectMode', 'warning', true);
      return;
    }

    const planCheck = applyPlanLimits(options);
    if (!planCheck.ok) {
      setStatus(planCheck.statusKey, 'warning', true);
      updateLicenseUi();
      return;
    }

    if (planCheck.capped) {
      setStatus(t('freeLimitApplied', options.maxComments), 'warning');
    }

    let tab = await getTargetTab(activeScrapeTabId, postUrl);

    if (!postUrl && (!tab?.url || !tab.url.includes('facebook.com'))) {
      setStatus('openFacebookFirst', 'warning', true);
      return;
    }

    startBtn.disabled = true;
    stopBtn.disabled = false;
    setStatus(postUrl ? 'openingPost' : 'initializing', 'working', true);

    try {
      if (postUrl) {
        await chrome.tabs.update(tab.id, { url: postUrl });
        await waitForTabLoad(tab.id, 30000);
        tab = await chrome.tabs.get(tab.id);
        await sleep(1500);
      }

      activeScrapeTabId = tab.id;
      const response = await sendStartMessage(tab.id, options);

      if (response?.started) {
        sendTelemetryEvent('scan_start', {
          deviceId,
          email: licenseEmail.value.trim()
        });
        recordScanStarted();
        updateLicenseUi();
        setStatus('scraperStarted', 'working', true);
      } else if (response?.reason === 'already_scraping') {
        setStatus('alreadyRunning', 'warning', true);
        startBtn.disabled = false;
      }
    } catch (error) {
      setStatus('connectFailed', 'warning', true);
      startBtn.disabled = false;
      stopBtn.disabled = true;
    }
  });

  resolverBtn.addEventListener('click', () => {
    sendTelemetryEvent('resolver_open', {
      deviceId,
      email: licenseEmail.value.trim()
    });
    chrome.tabs.create({ url: chrome.runtime.getURL('resolver.html') });
  });

  stopBtn.addEventListener('click', async () => {
    startBtn.disabled = true;
    stopBtn.disabled = true;
    setStatus('stoppingExport', 'working', true);

    try {
      const tab = await getTargetTab(activeScrapeTabId, '');
      await chrome.tabs.sendMessage(tab.id, { action: 'STOP_SCRAPING' });
    } catch (error) {
      setStatus('stopFailed', 'warning', true);
      startBtn.disabled = false;
    }
  });

  chrome.runtime.onMessage.addListener((message) => {
    if (message.action === 'UPDATE_STATUS') {
      const state = /export|finish|download/i.test(message.status)
        ? 'success'
        : /error|stop|failed|no comments|blocked/i.test(message.status)
          ? 'warning'
          : /start|scroll|expand|parse|sort|load/i.test(message.status)
            ? 'working'
            : 'idle';
      setStatus(message.status, state);
      if (typeof message.count === 'number') {
        runState.textContent = message.count.toLocaleString('en-US');
      }
    }

    if (message.action === 'SCRAPING_FINISHED') {
      const label = message.partial ? 'Stopped' : 'Finished';
      recordExportedRows(message.count || 0);
      updateLicenseUi();
      setStatus(t('exportedUsers', message.partial ? t('stopped') : t('finished'), message.count), 'success');
      startBtn.disabled = false;
      stopBtn.disabled = true;
    }
  });
});

function getSavedLanguage() {
  const saved = localStorage.getItem(LANGUAGE_KEY);
  return saved === 'en' ? 'en' : 'vi';
}

async function initializeDeviceId() {
  const stored = await getStoredDeviceId();
  if (stored) {
    return stored;
  }

  const generated = createDeviceId();
  await setStoredDeviceId(generated);
  return generated;
}

async function getStoredDeviceId() {
  if (chrome?.storage?.local) {
    const result = await chrome.storage.local.get(DEVICE_ID_KEY);
    if (result?.[DEVICE_ID_KEY]) {
      localStorage.setItem(DEVICE_ID_KEY, result[DEVICE_ID_KEY]);
      return result[DEVICE_ID_KEY];
    }
  }

  return localStorage.getItem(DEVICE_ID_KEY) || '';
}

async function setStoredDeviceId(value) {
  localStorage.setItem(DEVICE_ID_KEY, value);

  if (chrome?.storage?.local) {
    await chrome.storage.local.set({
      [DEVICE_ID_KEY]: value
    });
  }
}

function createDeviceId() {
  if (crypto?.randomUUID) {
    return crypto.randomUUID();
  }

  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
  const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}

async function copyText(value) {
  const text = String(value || '').trim();
  if (!text) {
    throw new Error('Empty copy value');
  }

  if (navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text);
    return;
  }

  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.setAttribute('readonly', '');
  textarea.style.position = 'fixed';
  textarea.style.opacity = '0';
  document.body.appendChild(textarea);
  textarea.select();

  const copied = document.execCommand('copy');
  textarea.remove();

  if (!copied) {
    throw new Error('Copy failed');
  }
}

function getTodayKey() {
  const now = new Date();
  return [
    now.getFullYear(),
    String(now.getMonth() + 1).padStart(2, '0'),
    String(now.getDate()).padStart(2, '0')
  ].join('-');
}

function getTodayUsage() {
  const today = getTodayKey();
  const usage = parseStoredJson(USAGE_KEY, {});

  if (usage.date !== today) {
    return {
      date: today,
      rows: 0,
      scans: 0
    };
  }

  return {
    date: today,
    rows: Math.max(0, Number(usage.rows || 0)),
    scans: Math.max(0, Number(usage.scans || 0))
  };
}

function saveTodayUsage(usage) {
  localStorage.setItem(USAGE_KEY, JSON.stringify({
    date: getTodayKey(),
    rows: Math.max(0, Number(usage.rows || 0)),
    scans: Math.max(0, Number(usage.scans || 0))
  }));
}

function recordScanStarted() {
  if (isVipLicense(getLicenseState())) {
    return;
  }

  const usage = getTodayUsage();
  usage.scans += 1;
  saveTodayUsage(usage);
}

function recordExportedRows(count) {
  if (isVipLicense(getLicenseState())) {
    return;
  }

  const usage = getTodayUsage();
  usage.rows += Math.max(0, Number(count || 0));
  saveTodayUsage(usage);
}

function applyPlanLimits(options) {
  if (isVipLicense(getLicenseState())) {
    return { ok: true, capped: false };
  }

  const usage = getTodayUsage();

  if (options.scanLikes && !PRODUCT_CONFIG.freeAllowsReactions) {
    return { ok: false, statusKey: 'freeReactionLocked' };
  }

  if (usage.scans >= PRODUCT_CONFIG.freeDailyScans) {
    return { ok: false, statusKey: 'freeScansUsed' };
  }

  if (usage.rows >= PRODUCT_CONFIG.freeDailyRows) {
    return { ok: false, statusKey: 'freeRowsUsed' };
  }

  const rowsLeft = PRODUCT_CONFIG.freeDailyRows - usage.rows;
  const requested = Number(options.maxComments || rowsLeft);
  options.maxComments = Math.max(1, Math.min(requested, rowsLeft));

  return {
    ok: true,
    capped: requested > rowsLeft
  };
}

function getLicenseState() {
  return parseStoredJson(LICENSE_KEY, {
    email: '',
    licenseKey: '',
    pendingOrderId: '',
    plan: 'free',
    licenseApiUrl: PRODUCT_CONFIG.licenseApiUrl
  });
}

function saveLicenseState(license) {
  localStorage.setItem(LICENSE_KEY, JSON.stringify(license));
}

function isVipLicense(license) {
  if (!license || license.plan !== 'vip') {
    return false;
  }

  if (!license.serverVerifiedAt || Date.now() - Date.parse(license.serverVerifiedAt) > 24 * 60 * 60 * 1000) {
    return false;
  }

  if (!license.expiresAt || license.expiresAt === 'lifetime') {
    return true;
  }

  return Date.parse(license.expiresAt) > Date.now();
}

async function refreshLicenseFromServer({ silent = false, email = '', licenseKey = '', deviceId = '', serverUrl = '' } = {}) {
  const license = getLicenseState();
  const accountEmail = (email || license.email || '').trim();
  const accountLicenseKey = normalizeLicenseKey(licenseKey || license.licenseKey || '');
  const currentDeviceId = deviceId || license.deviceId || '';

  if (!accountEmail || !currentDeviceId || !isValidEmail(accountEmail)) {
    return null;
  }

  try {
    const result = await callLicenseServer('/api/license/check', {
      email: accountEmail,
      licenseKey: accountLicenseKey,
      orderId: '',
      deviceId: currentDeviceId,
      product: PRODUCT_CONFIG.productName,
      extensionVersion: chrome.runtime.getManifest().version
    }, serverUrl || license.licenseApiUrl || PRODUCT_CONFIG.licenseApiUrl);

    saveLicenseFromServer(accountEmail, currentDeviceId, result, serverUrl || license.licenseApiUrl);

    return result;
  } catch (error) {
    return null;
  }
}

async function callLicenseServer(path, payload, serverUrl) {
  const baseUrl = normalizeApiUrl(serverUrl || PRODUCT_CONFIG.licenseApiUrl);
  const response = await fetch(`${baseUrl}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    cache: 'no-store',
    body: JSON.stringify(payload)
  });

  let data = {};
  try {
    data = await response.json();
  } catch (error) {
    data = {};
  }

  if (!response.ok || data.ok === false) {
    const error = new Error(data.error || `License server returned HTTP ${response.status}`);
    error.code = data.code || `HTTP_${response.status}`;
    throw error;
  }

  return data;
}

async function sendTelemetryEvent(event, data = {}) {
  try {
    const baseUrl = normalizeApiUrl(PRODUCT_CONFIG.licenseApiUrl);
    await fetch(`${baseUrl}/api/telemetry/event`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      cache: 'no-store',
      body: JSON.stringify({
        event,
        deviceId: data.deviceId || getLicenseState().deviceId || '',
        email: data.email || getLicenseState().email || '',
        extensionVersion: chrome.runtime.getManifest().version
      })
    });
  } catch (error) {
    // Usage metrics should never interrupt scanning or license activation.
  }
}

function saveLicenseFromServer(email, currentDeviceId, result, serverUrl) {
  saveLicenseState({
    email: result.email || email,
    licenseKey: result.licenseKey || normalizeLicenseKey(document.getElementById('licenseKeyInput')?.value || ''),
    pendingOrderId: '',
    plan: result.plan === 'vip' ? 'vip' : 'free',
    deviceId: result.deviceId || currentDeviceId,
    maxDevices: Number(result.maxDevices || 1),
    devicesUsed: Number(result.devicesUsed || 1),
    expiresAt: result.expiresAt || '',
    licenseApiUrl: normalizeApiUrl(serverUrl || PRODUCT_CONFIG.licenseApiUrl),
    serverVerifiedAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  });
}

function classifyLicenseError(error) {
  if (error?.code === 'DEVICE_LIMIT') {
    return 'licenseDeviceLimit';
  }

  if (error?.code === 'LICENSE_EXPIRED') {
    return 'licenseExpired';
  }

  if (error?.code === 'LICENSE_KEY_REQUIRED') {
    return 'licenseKeyRequired';
  }

  if (error?.code === 'BAD_LICENSE_KEY') {
    return 'licenseKeyInvalid';
  }

  if (error?.code === 'PAYMENT_PENDING') {
    return 'paymentPending';
  }

  return 'licenseServerFailed';
}

function normalizeLicenseKey(value) {
  return String(value || '').trim().toUpperCase();
}

function normalizeApiUrl(value) {
  try {
    const url = new URL(value || PRODUCT_CONFIG.licenseApiUrl);
    if (!['http:', 'https:'].includes(url.protocol)) {
      return PRODUCT_CONFIG.licenseApiUrl;
    }
    return `${url.protocol}//${url.host}`;
  } catch (error) {
    return PRODUCT_CONFIG.licenseApiUrl;
  }
}

function isValidEmail(value) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || '').trim());
}

function parseStoredJson(key, fallback) {
  try {
    const value = JSON.parse(localStorage.getItem(key) || 'null');
    return value && typeof value === 'object' ? value : fallback;
  } catch (error) {
    return fallback;
  }
}

function localizeRuntimeStatus(message, language) {
  if (language !== 'vi') {
    return message;
  }

  const text = String(message || '');
  const replacements = [
    [/^Starting scraper\.\.\.$/i, 'Đang bắt đầu quét...'],
    [/^Stopping\.\.\.$/i, 'Đang dừng...'],
    [/^Trying to apply selected filter\.\.\.$/i, 'Đang áp dụng bộ lọc đã chọn...'],
    [/^Scrolling to load comments\.\.\.$/i, 'Đang cuộn để tải bình luận...'],
    [/^Expanding comments and replies\.\.\.$/i, 'Đang mở bình luận và phản hồi...'],
    [/^Expanding comments\.\.\.$/i, 'Đang mở bình luận...'],
    [/^Parsing comment authors\.\.\.$/i, 'Đang đọc người bình luận...'],
    [/^Auto pause to reduce scan pressure\.\.\.$/i, 'Đang tạm dừng để giảm tốc độ quét...'],
    [/^Auto pause during reaction scan\.\.\.$/i, 'Đang tạm dừng khi quét cảm xúc...'],
    [/^No users found\. Open the post detail view and try again\.$/i, 'Không tìm thấy người dùng. Mở chi tiết bài viết rồi thử lại.'],
    [/^Stopped\. No users found\.$/i, 'Đã dừng. Không tìm thấy người dùng.'],
    [/^Opening Reels comments panel\.\.\.$/i, 'Đang mở bảng bình luận Reels...'],
    [/^Could not open Reels comments\. Click the comment button once, then start again\.$/i, 'Không mở được bình luận Reels. Hãy bấm nút bình luận một lần rồi bắt đầu lại.'],
    [/^Waiting for Reels comments to load\.\.\.$/i, 'Đang chờ bình luận Reels tải...'],
    [/^Reels comments did not finish loading\. Try again after the panel is visible\.$/i, 'Bình luận Reels chưa tải xong. Hãy thử lại sau khi bảng bình luận hiện ra.'],
    [/^Could not open reactions list\. Facebook may be hiding it for this post\.$/i, 'Không mở được danh sách cảm xúc. Facebook có thể đang ẩn danh sách này.']
  ];

  for (const [pattern, replacement] of replacements) {
    if (pattern.test(text)) {
      return replacement;
    }
  }

  let match = text.match(/^Loading\.\.\. found about (\d+) comment blocks$/i);
  if (match) {
    return `Đang tải... tìm thấy khoảng ${match[1]} khối bình luận`;
  }

  match = text.match(/^Exporting (\d+) users\.\.\.$/i);
  if (match) {
    return `Đang xuất ${match[1]} người dùng...`;
  }

  match = text.match(/^Stopped\. Exporting (\d+) users\.\.\.$/i);
  if (match) {
    return `Đã dừng. Đang xuất ${match[1]} người dùng...`;
  }

  match = text.match(/^Scanning (.+)\.\.\. collected (\d+) users$/i);
  if (match) {
    return `Đang quét ${match[1]}... đã lấy ${match[2]} người dùng`;
  }

  match = text.match(/^Resolving UID from (\d+) profile URLs\.\.\.$/i);
  if (match) {
    return `Đang đổi UID từ ${match[1]} link profile...`;
  }

  match = text.match(/^Resolving UID\.\.\. (\d+)\/(\d+)$/i);
  if (match) {
    return `Đang đổi UID... ${match[1]}/${match[2]}`;
  }

  match = text.match(/^Collected (\d+) users\.\.\.$/i);
  if (match) {
    return `Đã lấy ${match[1]} người dùng...`;
  }

  match = text.match(/^Reels comments loaded\. Found about (\d+) comment authors$/i);
  if (match) {
    return `Bình luận Reels đã tải. Tìm thấy khoảng ${match[1]} người bình luận`;
  }

  return text;
}

async function getTargetTab(preferredTabId, postUrl) {
  if (preferredTabId) {
    try {
      const tab = await chrome.tabs.get(preferredTabId);
      if (tab?.id && (postUrl || tab.url?.includes('facebook.com'))) {
        return tab;
      }
    } catch (error) {
      // Fall through to an open Facebook tab or a new tab for pasted URLs.
    }
  }

  const facebookTabs = await chrome.tabs.query({
    url: [
      '*://*.facebook.com/*'
    ]
  });

  const activeFacebookTab = facebookTabs.find((tab) => tab.active) || facebookTabs[0];
  if (activeFacebookTab) {
    return activeFacebookTab;
  }

  if (postUrl) {
    return chrome.tabs.create({
      url: postUrl,
      active: true
    });
  }

  return null;
}

function normalizeFacebookUrl(value) {
  if (!value) {
    return '';
  }

  if (/^\d+$/.test(value)) {
    return `https://www.facebook.com/${value}`;
  }

  try {
    const url = new URL(value);
    if (!url.hostname.includes('facebook.com')) {
      return '';
    }
    return url.toString();
  } catch (error) {
    return '';
  }
}

async function sendStartMessage(tabId, options) {
  try {
    return await chrome.tabs.sendMessage(tabId, {
      action: 'START_SCRAPING',
      options
    });
  } catch (error) {
    await chrome.scripting.executeScript({
      target: { tabId },
      files: ['content.js']
    });
    await sleep(700);
    return chrome.tabs.sendMessage(tabId, {
      action: 'START_SCRAPING',
      options
    });
  }
}

function waitForTabLoad(tabId, timeoutMs) {
  return new Promise((resolve) => {
    let settled = false;
    const timeout = setTimeout(() => {
      if (!settled) {
        settled = true;
        chrome.tabs.onUpdated.removeListener(listener);
        resolve();
      }
    }, timeoutMs);

    const listener = (updatedTabId, changeInfo) => {
      if (updatedTabId !== tabId || changeInfo.status !== 'complete') {
        return;
      }

      if (!settled) {
        settled = true;
        clearTimeout(timeout);
        chrome.tabs.onUpdated.removeListener(listener);
        resolve();
      }
    };

    chrome.tabs.onUpdated.addListener(listener);
  });
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
