const I18N = {
  en: {
    pageTitle: 'Bulk Facebook UID Resolver',
    languageLabel: 'Language',
    profileUrls: 'Profile URLs',
    profilePlaceholder: 'Paste one Facebook profile URL per line',
    profileHint: 'Works only when Facebook exposes the numeric id in the profile response available to your logged-in account.',
    limit: 'Limit',
    pauseEveryUrls: 'Pause every URLs',
    pauseSeconds: 'Pause seconds',
    autoPause: 'Auto pause',
    useLocalServer: 'Use local server',
    localResolverUrl: 'Local resolver URL',
    startResolving: 'Start Resolving',
    stopExport: 'Stop and Export',
    exportCurrent: 'Export Current Result',
    clear: 'Clear',
    openNextProfile: 'Open Next Profile',
    markRequested: 'Mark Requested',
    markSkipped: 'Skip',
    results: 'Results',
    author: 'Author',
    userId: 'User ID',
    profileUrl: 'Profile URL',
    status: 'Status',
    friendWorkflow: 'Friend Workflow',
    friendPending: 'Pending',
    friendOpened: 'Opened',
    friendRequested: 'Requested',
    friendSkipped: 'Skipped',
    openRow: 'Open',
    markRowRequested: 'Requested',
    skipRow: 'Skip',
    initialStatus: 'Paste profile URLs and start resolving.',
    stopping: 'Stopping after current URL, then exporting...',
    noUrls: 'No valid Facebook profile URLs found.',
    resolving: (done, total) => `Resolving ${done}/${total}...`,
    autoPauseStatus: (seconds, processed) => `Auto pause ${seconds}s after ${processed} URLs...`,
    doneExported: (count) => `Done. Exported ${count} rows.`,
    doneNoRows: 'Done. No rows resolved.',
    cleared: 'Cleared.',
    debugFailed: 'Debug failed',
    noDebug: 'No debug response',
    checkpoint: 'Facebook checkpoint detected',
    login: 'Facebook returned login page',
    unsupported: 'Facebook returned unsupported/browser page',
    errorPage: 'Facebook returned Error page',
    noUidHtml: 'No UID in fetched HTML',
    matchedRejected: (id) => `Matched ${id} but resolver rejected it`,
    candidateIds: (ids) => `Candidate ids found: ${ids}`,
    titles: (value) => `No UID in HTML. Titles: ${value}`,
    resolvedVia: (source) => `Resolved via ${source || 'local server'}`,
    noProfileToOpen: 'No pending profile to open.',
    openedProfile: (name) => `Opened profile: ${name}`,
    markedRequested: 'Marked as requested.',
    markedSkipped: 'Skipped this profile.'
  },
  vi: {
    pageTitle: 'Đổi UID Facebook hàng loạt',
    languageLabel: 'Ngôn ngữ',
    profileUrls: 'Link profile',
    profilePlaceholder: 'Dán mỗi link Facebook profile trên một dòng',
    profileHint: 'Chỉ hoạt động khi Facebook trả về ID số trong dữ liệu profile mà tài khoản đang đăng nhập có thể xem.',
    limit: 'Giới hạn',
    pauseEveryUrls: 'Tạm dừng sau số link',
    pauseSeconds: 'Số giây tạm dừng',
    autoPause: 'Tự tạm dừng',
    useLocalServer: 'Dùng server local',
    localResolverUrl: 'URL server đổi UID',
    startResolving: 'Bắt đầu đổi UID',
    stopExport: 'Dừng và xuất',
    exportCurrent: 'Xuất kết quả hiện tại',
    clear: 'Xóa',
    openNextProfile: 'Mở profile tiếp theo',
    markRequested: 'Đã gửi lời mời',
    markSkipped: 'Bỏ qua',
    results: 'Kết quả',
    author: 'Tên',
    userId: 'User ID',
    profileUrl: 'Link profile',
    status: 'Trạng thái',
    friendWorkflow: 'Kết bạn thủ công',
    friendPending: 'Chờ xử lý',
    friendOpened: 'Đã mở',
    friendRequested: 'Đã gửi',
    friendSkipped: 'Bỏ qua',
    openRow: 'Mở',
    markRowRequested: 'Đã gửi',
    skipRow: 'Bỏ qua',
    initialStatus: 'Dán link profile rồi bắt đầu đổi UID.',
    stopping: 'Đang dừng sau link hiện tại, rồi xuất file...',
    noUrls: 'Không tìm thấy link Facebook profile hợp lệ.',
    resolving: (done, total) => `Đang đổi UID ${done}/${total}...`,
    autoPauseStatus: (seconds, processed) => `Tự tạm dừng ${seconds}s sau ${processed} link...`,
    doneExported: (count) => `Hoàn tất. Đã xuất ${count} dòng.`,
    doneNoRows: 'Hoàn tất. Không đổi được dòng nào.',
    cleared: 'Đã xóa.',
    debugFailed: 'Debug thất bại',
    noDebug: 'Không có phản hồi debug',
    checkpoint: 'Facebook yêu cầu checkpoint',
    login: 'Facebook trả về trang đăng nhập',
    unsupported: 'Facebook trả về trang trình duyệt không hỗ trợ',
    errorPage: 'Facebook trả về trang lỗi',
    noUidHtml: 'Không tìm thấy UID trong HTML đã tải',
    matchedRejected: (id) => `Tìm thấy ${id} nhưng bộ đổi UID đã loại bỏ`,
    candidateIds: (ids) => `Tìm thấy ID ứng viên: ${ids}`,
    titles: (value) => `Không có UID trong HTML. Tiêu đề: ${value}`,
    resolvedVia: (source) => `Đã đổi qua ${source || 'server local'}`,
    noProfileToOpen: 'Không còn profile chờ xử lý.',
    openedProfile: (name) => `Đã mở profile: ${name}`,
    markedRequested: 'Đã đánh dấu đã gửi lời mời.',
    markedSkipped: 'Đã bỏ qua profile này.'
  }
};

const LANGUAGE_KEY = 'fb_comments_export_language';
const FRIEND_WORKFLOW_KEY = 'fb_uid_resolver_friend_workflow';

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

let currentLanguage = getSavedLanguage();

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

function 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 (statusBox.dataset.key) {
    setStatus(statusBox.dataset.key, statusBox.dataset.state || 'idle', true);
  }
}

const languageSelect = document.getElementById('languageSelect');
const profileInput = document.getElementById('profileInput');
const limitInput = document.getElementById('limitInput');
const pauseEveryInput = document.getElementById('pauseEveryInput');
const pauseSecondsInput = document.getElementById('pauseSecondsInput');
const autoPauseInput = document.getElementById('autoPauseInput');
const useLocalResolverInput = document.getElementById('useLocalResolverInput');
const localResolverUrlInput = document.getElementById('localResolverUrlInput');
const startBtn = document.getElementById('startBtn');
const stopBtn = document.getElementById('stopBtn');
const exportBtn = document.getElementById('exportBtn');
const clearBtn = document.getElementById('clearBtn');
const openNextBtn = document.getElementById('openNextBtn');
const markRequestedBtn = document.getElementById('markRequestedBtn');
const markSkippedBtn = document.getElementById('markSkippedBtn');
const statusBox = document.getElementById('status');
const resultBody = document.getElementById('resultBody');

let isResolving = false;
let shouldStop = false;
let results = [];
let seenKeys = new Set();
let activeFriendIndex = -1;

useLocalResolverInput.checked = true;
restoreFriendWorkflow();

applyLanguage();
setStatus('initialStatus', 'idle', true);
renderResults();

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

startBtn.addEventListener('click', startResolving);
stopBtn.addEventListener('click', () => {
  shouldStop = true;
  setStatus('stopping', 'working', true);
});
exportBtn.addEventListener('click', () => exportFiles(results));
clearBtn.addEventListener('click', clearAll);
openNextBtn.addEventListener('click', openNextFriendProfile);
markRequestedBtn.addEventListener('click', () => markActiveFriendStatus('requested'));
markSkippedBtn.addEventListener('click', () => markActiveFriendStatus('skipped'));
resultBody.addEventListener('click', handleResultActionClick);

async function startResolving() {
  const urls = parseProfileUrls(profileInput.value);
  const limit = Math.max(1, Math.min(1000000, Number(limitInput.value || 1000)));
  const pauseEvery = Math.max(1, Math.min(500, Number(pauseEveryInput.value || 25)));
  const pauseMs = Math.max(1000, Math.min(120000, Number(pauseSecondsInput.value || 7) * 1000));
  const autoPause = autoPauseInput.checked;
  const useLocalResolver = useLocalResolverInput.checked !== false;
  const localResolverUrl = localResolverUrlInput.value.trim() || 'http://127.0.0.1:8787';

  if (urls.length === 0) {
    setStatus('noUrls', 'warning', true);
    return;
  }

  isResolving = true;
  shouldStop = false;
  startBtn.disabled = true;
  stopBtn.disabled = false;
  exportBtn.disabled = true;

  let processed = 0;

  for (const url of urls.slice(0, limit)) {
    if (shouldStop) {
      break;
    }

    processed += 1;
    setStatus(t('resolving', processed, Math.min(urls.length, limit)), 'working');

    let row;
    try {
      const directId = extractUserIdFromValue(url);
      row = directId
        ? await resolveProfileUrl(url)
        : useLocalResolver
          ? await resolveProfileUrlWithLocalServer(url, localResolverUrl)
          : await resolveProfileUrl(url);
    } catch (error) {
      row = {
        author: inferAuthorFromProfileUrl(url),
        userId: extractUserIdFromValue(url),
        profileUrl: normalizeProfileUrl(url) || url,
        status: error.message || String(error)
      };
    }
    addResult(row);
    renderResults();
    saveFriendWorkflow();

    await sleep(350);

    if (autoPause && processed % pauseEvery === 0 && processed < urls.length) {
      setStatus(t('autoPauseStatus', pauseMs / 1000, processed), 'working');
      await sleep(pauseMs);
    }
  }

  isResolving = false;
  startBtn.disabled = false;
  stopBtn.disabled = true;
  exportBtn.disabled = results.length === 0;

  if (results.length > 0) {
    exportFiles(results);
    setStatus(t('doneExported', results.length), 'success');
  } else {
    setStatus('doneNoRows', 'warning', true);
  }
}

async function resolveProfileUrlWithLocalServer(profileUrl, localResolverUrl) {
  const response = await chrome.runtime.sendMessage({
    action: 'RESOLVE_UID_BATCH',
    serverUrl: localResolverUrl,
    urls: [profileUrl],
    pauseMs: 0
  });

  if (!response?.ok) {
    throw new Error(response?.error || 'Local resolver failed');
  }

  const row = response.results?.[0] || { author: '', userId: '', profileUrl };
  row.author = row.author || inferAuthorFromProfileUrl(profileUrl);

  if (!row.userId) {
    const debug = await debugProfileUrlWithLocalServer(profileUrl, localResolverUrl);
    row.status = summarizeDebug(debug);
  } else {
    row.status = t('resolvedVia', row.source || 'local server');
  }

  return row;
}

async function debugProfileUrlWithLocalServer(profileUrl, localResolverUrl) {
  const response = await chrome.runtime.sendMessage({
    action: 'DEBUG_UID',
    serverUrl: localResolverUrl,
    url: profileUrl
  });

  if (!response?.ok) {
    return {
      error: response?.error || t('debugFailed')
    };
  }

  return response.result;
}

function summarizeDebug(debug) {
  if (!debug) {
    return t('noDebug');
  }

  if (debug.error) {
    return debug.error;
  }

  const candidates = debug.candidates || [];
  const firstWithId = candidates.find((candidate) => candidate.selectedUserId || (candidate.matchedIds || []).length > 0);
  if (firstWithId?.selectedUserId) {
    return t('matchedRejected', firstWithId.selectedUserId);
  }
  if (firstWithId?.matchedIds?.length) {
    return t('candidateIds', firstWithId.matchedIds.map((item) => item.id).join(', '));
  }

  const checkpoint = candidates.find((candidate) => candidate.checkpointDetected);
  if (checkpoint) {
    return t('checkpoint');
  }

  const login = candidates.find((candidate) => candidate.loginDetected);
  if (login) {
    return t('login');
  }

  const unsupported = candidates.find((candidate) => candidate.unsupportedDetected);
  if (unsupported) {
    return t('unsupported');
  }

  const errorTitle = candidates.find((candidate) => /^error$/i.test(candidate.title || ''));
  if (errorTitle) {
    return t('errorPage');
  }

  const titles = candidates.map((candidate) => candidate.title).filter(Boolean);
  return titles.length ? t('titles', titles.slice(0, 2).join(' | ')) : t('noUidHtml');
}

async function resolveProfileUrl(profileUrl) {
  const normalizedUrl = normalizeProfileUrl(profileUrl) || profileUrl;
  const directId = extractUserIdFromValue(normalizedUrl);
  const fallbackAuthor = inferAuthorFromProfileUrl(profileUrl);

  if (directId) {
    return {
      author: fallbackAuthor,
      userId: directId,
      profileUrl: `https://www.facebook.com/profile.php?id=${directId}`
    };
  }

  try {
    const response = await fetch(normalizedUrl, {
      credentials: 'include',
      cache: 'no-store'
    });

    if (!response.ok) {
      return { author: fallbackAuthor, userId: '', profileUrl: normalizedUrl };
    }

    const html = await response.text();
    const userId = extractTargetUserIdFromHtml(html, normalizedUrl);
    const author = extractProfileName(html);

    return {
      author: author || fallbackAuthor,
      userId,
      profileUrl: userId ? `https://www.facebook.com/profile.php?id=${userId}` : normalizedUrl
    };
  } catch (error) {
    return { author: fallbackAuthor, userId: '', profileUrl: normalizedUrl };
  }
}

function parseProfileUrls(value) {
  const urls = [];

  for (const line of String(value || '').split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed) {
      continue;
    }

    const parts = trimmed.split(/[,\t;]/).map((part) => part.trim()).filter(Boolean);
    for (const part of parts) {
      const url = normalizeProfileUrl(part);
      if (url) {
        urls.push(url);
      }
    }
  }

  return Array.from(new Set(urls));
}

function addResult(row) {
  const key = row.userId || row.profileUrl;
  if (!key || seenKeys.has(key)) {
    return;
  }

  seenKeys.add(key);
  results.push({
    author: row.author || inferAuthorFromProfileUrl(row.source || row.profileUrl) || '',
    userId: row.userId || '',
    profileUrl: row.profileUrl || '',
    status: row.status || row.source || row.error || '',
    friendStatus: row.friendStatus || 'pending',
    friendUpdatedAt: row.friendUpdatedAt || ''
  });
}

function renderResults() {
  const offset = Math.max(0, results.length - 200);
  const rows = results.slice(-200).map((row, visibleIndex) => {
    const index = offset + visibleIndex;
    return `
    <tr data-active="${index === activeFriendIndex}" data-index="${index}">
      <td>${escapeHtml(row.author)}</td>
      <td>${escapeHtml(row.userId)}</td>
      <td>${escapeHtml(row.profileUrl)}</td>
      <td>${escapeHtml(row.status)}</td>
      <td>
        <div>${escapeHtml(getFriendStatusLabel(row.friendStatus))}</div>
        <div class="mini-actions">
          <button type="button" data-action="open-profile" data-index="${index}">${escapeHtml(t('openRow'))}</button>
          <button type="button" data-action="mark-requested" data-index="${index}">${escapeHtml(t('markRowRequested'))}</button>
          <button type="button" data-action="mark-skipped" data-index="${index}">${escapeHtml(t('skipRow'))}</button>
        </div>
      </td>
    </tr>
  `;
  });

  resultBody.innerHTML = rows.join('');
  updateFriendButtons();
}

function exportFiles(rows) {
  if (!rows || rows.length === 0) {
    return;
  }

  const timestamp = getTimestamp();
  downloadBlob(toCsv(rows), `fb_uid_resolver_${timestamp}.csv`, 'text/csv;charset=utf-8');
}

function toCsv(rows) {
  const csvRows = ['Author,User ID,Profile URL,Resolve Status,Friend Status,Friend Updated At'];

  for (const row of rows) {
    csvRows.push([
      csvValue(row.author),
      csvValueForExcelId(row.userId),
      csvValue(row.profileUrl),
      csvValue(row.status),
      csvValue(getFriendStatusLabel(row.friendStatus, 'en')),
      csvValue(row.friendUpdatedAt || '')
    ].join(','));
  }

  return `\uFEFF${csvRows.join('\n')}\n`;
}

function normalizeProfileUrl(href) {
  if (!href) {
    return '';
  }

  try {
    const url = new URL(href, 'https://www.facebook.com/');
    const host = url.hostname.replace(/^m\./, 'www.');

    if (!host.includes('facebook.com')) {
      return '';
    }

    const userIdFromHref = extractUserIdFromValue(href);
    if (userIdFromHref) {
      return `https://www.facebook.com/profile.php?id=${userIdFromHref}`;
    }

    const profileId = url.searchParams.get('id');
    if (url.pathname.includes('/profile.php') && profileId) {
      return `https://www.facebook.com/profile.php?id=${profileId}`;
    }

    const pathParts = url.pathname.split('/').filter(Boolean);
    const firstPart = pathParts[0] || '';

    if (!firstPart || isFacebookSystemPath(firstPart)) {
      return '';
    }

    return `https://www.facebook.com/${firstPart}`;
  } catch (error) {
    return '';
  }
}

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

  const decoded = safeDecode(String(value));

  try {
    const url = new URL(decoded, 'https://www.facebook.com/');
    const groupUserPath = url.pathname.match(/\/groups\/[^/]+\/user\/(\d{5,})(?:\/|$)/i);
    if (groupUserPath) {
      return groupUserPath[1];
    }

    const userPath = url.pathname.match(/\/user\/(\d{5,})(?:\/|$)/i);
    if (userPath) {
      return userPath[1];
    }

    const directUserId = url.searchParams.get('user_id') ||
      url.searchParams.get('profile_id');

    if (isNumericId(directUserId)) {
      return directUserId;
    }

    const directId = url.searchParams.get('id');
    if (url.pathname.includes('/profile.php') && isNumericId(directId)) {
      return directId;
    }

    const rootNumericProfile = url.pathname.match(/^\/(\d{5,})\/?$/);
    if (rootNumericProfile) {
      return rootNumericProfile[1];
    }
  } catch (error) {
    // Continue with regex parsing below.
  }

  const patterns = [
    /\/groups\/[^/"'<>]+\/user\/(\d{5,})(?:\/|["'<>?&\s]|$)/i,
    /\/user\/(\d{5,})(?:\/|["'<>?&\s]|$)/i,
    /(?:[?&]|%3F|%26)(?:user_id|profile_id)(?:=|%3D)(\d{5,})/i,
    /\/profile\.php[^"'<>]*?(?:[?&]|%3F|%26)id(?:=|%3D)(\d{5,})/i
  ];

  for (const pattern of patterns) {
    const match = decoded.match(pattern);
    if (match && isNumericId(match[1])) {
      return match[1];
    }
  }

  return '';
}

function extractTargetUserIdFromHtml(html, profileUrl) {
  const decoded = safeDecode(String(html || ''));
  const slug = getProfileSlug(profileUrl);
  const candidates = [];

  collectCandidateIds(decoded, /"profile_owner"\s*:\s*\{[^{}]{0,500}?"id"\s*:\s*"(\d{5,})"/gi, candidates, 12);
  collectCandidateIds(decoded, /"profile_owner"\s*:\s*"(\d{5,})"/gi, candidates, 12);
  collectCandidateIds(decoded, /"profile_id"\s*:\s*"(\d{5,})"/gi, candidates, 10);
  collectCandidateIds(decoded, /"profile_id"\s*:\s*(\d{5,})/gi, candidates, 10);
  collectCandidateIds(decoded, /"entity_id"\s*:\s*"(\d{5,})"/gi, candidates, 6);
  collectCandidateIds(decoded, /"entity_id"\s*:\s*(\d{5,})/gi, candidates, 6);
  collectCandidateIds(decoded, /"owning_profile"\s*:\s*\{[^{}]{0,600}?"id"\s*:\s*"(\d{5,})"/gi, candidates, 8);
  collectCandidateIds(decoded, /\/profile\.php[^"'<>]{0,400}?(?:[?&]|%3F|%26)id(?:=|%3D)(\d{5,})/gi, candidates, 4);

  if (slug) {
    const escapedSlug = escapeRegExp(slug);
    collectCandidateIds(decoded, new RegExp(`(?:${escapedSlug})[^]{0,900}?"id"\\s*:\\s*"(\\d{5,})"`, 'gi'), candidates, 12);
    collectCandidateIds(decoded, new RegExp(`"id"\\s*:\\s*"(\\d{5,})"[^]{0,900}?(?:${escapedSlug})`, 'gi'), candidates, 12);
  }

  const scored = new Map();
  for (const candidate of candidates) {
    const previous = scored.get(candidate.id) || 0;
    scored.set(candidate.id, Math.max(previous, candidate.score));
  }

  const sorted = Array.from(scored.entries()).sort((a, b) => b[1] - a[1]);
  const best = sorted[0];

  return best && best[1] >= 8 ? best[0] : '';
}

function collectCandidateIds(text, pattern, candidates, baseScore) {
  let match;

  while ((match = pattern.exec(text)) !== null) {
    const id = match[1];
    if (!isNumericId(id)) {
      continue;
    }

    const start = Math.max(0, match.index - 500);
    const end = Math.min(text.length, match.index + match[0].length + 500);
    const context = text.slice(start, end);

    if (/"__user"|"actorID"|"viewer"|"currentUser"/i.test(context) && !/"profile_owner"|"profile_id"|"owning_profile"/i.test(context)) {
      continue;
    }

    let score = baseScore;
    if (/"profile_owner"|"profile_id"|"owning_profile"/i.test(context)) {
      score += 4;
    }
    if (/facebook\.com\\?\/profile\.php|facebook\.com\/profile\.php/i.test(context)) {
      score += 2;
    }

    candidates.push({ id, score });
  }
}

function getProfileSlug(profileUrl) {
  try {
    const url = new URL(profileUrl);
    const pathParts = url.pathname.split('/').filter(Boolean);
    const firstPart = pathParts[0] || '';
    return firstPart && firstPart !== 'profile.php' ? firstPart : '';
  } catch (error) {
    return '';
  }
}

function inferAuthorFromProfileUrl(profileUrl) {
  const slug = getProfileSlug(profileUrl);
  if (!slug || isFacebookSystemPath(slug)) {
    return '';
  }

  const parts = slug
    .replace(/[-_]+/g, '.')
    .split('.')
    .map((part) => part.trim())
    .filter(Boolean)
    .filter((part) => !/^\d+$/.test(part));

  if (parts.length === 0) {
    return '';
  }

  return parts
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(' ');
}

function escapeRegExp(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function extractProfileName(html) {
  const patterns = [
    /<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i,
    /<title[^>]*>([^<]+)<\/title>/i
  ];

  for (const pattern of patterns) {
    const match = String(html || '').match(pattern);
    if (match && match[1]) {
      return cleanCell(decodeHtml(match[1]).replace(/\s*\|\s*Facebook.*$/i, ''));
    }
  }

  return '';
}

function isFacebookSystemPath(pathPart) {
  return [
    'ajax',
    'groups',
    'hashtag',
    'help',
    'login',
    'marketplace',
    'messages',
    'notifications',
    'pages',
    'people',
    'permalink.php',
    'photo.php',
    'plugins',
    'profile.php',
    'reel',
    'share',
    'stories',
    'watch'
  ].includes(pathPart.toLowerCase());
}

function isNumericId(value) {
  return /^\d{5,}$/.test(String(value || ''));
}

function csvValue(value) {
  return `"${String(value ?? '').replace(/"/g, '""')}"`;
}

function csvValueForExcelId(value) {
  const text = String(value ?? '').trim();

  if (!text) {
    return '""';
  }

  return `="${text.replace(/"/g, '""')}"`;
}

function downloadBlob(content, filename, type) {
  const blob = new Blob([content], { type });
  const downloadUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');

  link.href = downloadUrl;
  link.download = filename;
  link.style.display = 'none';
  document.body.appendChild(link);
  link.click();

  setTimeout(() => {
    URL.revokeObjectURL(downloadUrl);
    link.remove();
  }, 1000);
}

function setStatus(messageOrKey, state = 'idle', isKey = false) {
  statusBox.textContent = isKey ? t(messageOrKey) : messageOrKey;
  statusBox.dataset.key = isKey ? messageOrKey : '';
  statusBox.dataset.state = state;
}

function clearAll() {
  if (isResolving) {
    return;
  }

  profileInput.value = '';
  results = [];
  seenKeys = new Set();
  activeFriendIndex = -1;
  localStorage.removeItem(FRIEND_WORKFLOW_KEY);
  renderResults();
  exportBtn.disabled = true;
  setStatus('cleared', 'idle', true);
}

function handleResultActionClick(event) {
  const button = event.target.closest('button[data-action]');
  if (!button) {
    return;
  }

  const index = Number(button.dataset.index);
  if (!Number.isInteger(index) || !results[index]) {
    return;
  }

  if (button.dataset.action === 'open-profile') {
    openFriendProfile(index);
  } else if (button.dataset.action === 'mark-requested') {
    markFriendStatus(index, 'requested');
  } else if (button.dataset.action === 'mark-skipped') {
    markFriendStatus(index, 'skipped');
  }
}

function openNextFriendProfile() {
  const start = activeFriendIndex >= 0 ? activeFriendIndex + 1 : 0;
  const index = findNextPendingFriendIndex(start) ?? findNextPendingFriendIndex(0);

  if (index === null || index === undefined) {
    setStatus('noProfileToOpen', 'warning', true);
    return;
  }

  openFriendProfile(index);
}

function findNextPendingFriendIndex(start) {
  for (let index = start; index < results.length; index += 1) {
    const status = results[index]?.friendStatus || 'pending';
    if (status !== 'requested' && status !== 'skipped') {
      return index;
    }
  }
  return null;
}

function openFriendProfile(index) {
  const row = results[index];
  const url = getOpenableProfileUrl(row);
  if (!url) {
    markFriendStatus(index, 'skipped');
    return;
  }

  activeFriendIndex = index;
  markFriendStatus(index, 'opened', { silent: true });
  chrome.tabs.create({ url });
  setStatus(t('openedProfile', row.author || row.profileUrl || row.userId), 'success');
}

function markActiveFriendStatus(status) {
  if (activeFriendIndex < 0 || !results[activeFriendIndex]) {
    const index = findNextPendingFriendIndex(0);
    if (index === null || index === undefined) {
      setStatus('noProfileToOpen', 'warning', true);
      return;
    }
    activeFriendIndex = index;
  }

  markFriendStatus(activeFriendIndex, status);
}

function markFriendStatus(index, status, options = {}) {
  if (!results[index]) {
    return;
  }

  results[index].friendStatus = status;
  results[index].friendUpdatedAt = new Date().toISOString();
  activeFriendIndex = index;
  saveFriendWorkflow();
  renderResults();

  if (!options.silent) {
    setStatus(status === 'requested' ? 'markedRequested' : 'markedSkipped', 'success', true);
  }
}

function getOpenableProfileUrl(row) {
  if (row?.profileUrl) {
    return row.profileUrl;
  }

  if (row?.userId) {
    return `https://www.facebook.com/profile.php?id=${encodeURIComponent(row.userId)}`;
  }

  return '';
}

function getFriendStatusLabel(status, language = currentLanguage) {
  const labels = {
    pending: language === 'vi' ? I18N.vi.friendPending : I18N.en.friendPending,
    opened: language === 'vi' ? I18N.vi.friendOpened : I18N.en.friendOpened,
    requested: language === 'vi' ? I18N.vi.friendRequested : I18N.en.friendRequested,
    skipped: language === 'vi' ? I18N.vi.friendSkipped : I18N.en.friendSkipped
  };
  return labels[status] || labels.pending;
}

function updateFriendButtons() {
  const hasRows = results.length > 0;
  openNextBtn.disabled = !hasRows;
  markRequestedBtn.disabled = !hasRows || activeFriendIndex < 0;
  markSkippedBtn.disabled = !hasRows || activeFriendIndex < 0;
  exportBtn.disabled = results.length === 0 || isResolving;
}

function saveFriendWorkflow() {
  try {
    localStorage.setItem(FRIEND_WORKFLOW_KEY, JSON.stringify({
      activeFriendIndex,
      results
    }));
  } catch (error) {
    // Ignore storage quota failures; CSV export still contains current state.
  }
}

function restoreFriendWorkflow() {
  try {
    const saved = JSON.parse(localStorage.getItem(FRIEND_WORKFLOW_KEY) || '{}');
    if (!Array.isArray(saved.results)) {
      return;
    }

    results = saved.results.map((row) => ({
      author: row.author || '',
      userId: row.userId || '',
      profileUrl: row.profileUrl || '',
      status: row.status || '',
      friendStatus: row.friendStatus || 'pending',
      friendUpdatedAt: row.friendUpdatedAt || ''
    }));
    seenKeys = new Set(results.map((row) => row.userId || row.profileUrl).filter(Boolean));
    activeFriendIndex = Number.isInteger(saved.activeFriendIndex) ? saved.activeFriendIndex : -1;
    exportBtn.disabled = results.length === 0;
  } catch (error) {
    results = [];
    seenKeys = new Set();
    activeFriendIndex = -1;
  }
}

function cleanCell(value) {
  return String(value || '').replace(/\s+/g, ' ').trim();
}

function escapeHtml(value) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

function decodeHtml(value) {
  const textarea = document.createElement('textarea');
  textarea.innerHTML = value;
  return textarea.value;
}

function safeDecode(value) {
  try {
    return decodeURIComponent(value);
  } catch (error) {
    return value;
  }
}

function getTimestamp() {
  const now = new Date();
  const pad = (value) => String(value).padStart(2, '0');
  return [
    now.getFullYear(),
    pad(now.getMonth() + 1),
    pad(now.getDate()),
    '-',
    pad(now.getHours()),
    pad(now.getMinutes()),
    pad(now.getSeconds())
  ].join('');
}

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