'use strict';

const SEARCH = 'https://www.google.com/search';
const PAGES = 3;
const RESULTS_PER_PAGE = 10;
const PER_PAGE_TIMEOUT = 22000;

const GAP_BACKGROUND = 3200;
const GAP_WATCH = 3800;
const GAP_JITTER = 2500;
const UA_RULE_ID = 1;
const ANON_RULE_ID = 2;
const RUN_SHIELD_RULE_ID = 3;

const GLOBAL_UULE_RULE_ID = 11;
const GLOBAL_GUARD_RULE_ID = 12;
const GLOBAL_XGEO_RULE_ID = 13;
const GLOBAL_RULE_IDS = [GLOBAL_UULE_RULE_ID, GLOBAL_GUARD_RULE_ID, GLOBAL_XGEO_RULE_ID];

const RUN_RULE_PRIORITY = 10;
const RUN_SHIELD_PRIORITY = 5;

const CHROME_MAJOR = (function () {
  const m = (typeof navigator !== 'undefined' && navigator.userAgent || '').match(/Chrome\/(\d+)/);
  return m ? m[1] : '126';
})();
const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) ' +
  'Chrome/' + CHROME_MAJOR + '.0.0.0 Mobile Safari/537.36';

const SOCS_VALUE = 'CAESEwgDEgk0ODE3Nzk3MjQaAmVuIAEaBgiA_LyaBg';

const DROP_COOKIES = ['SID', 'HSID', 'SSID', 'APISID', 'SAPISID', 'LSID', 'ACCOUNT_CHOOSER',
  '__Secure-1PSID', '__Secure-3PSID', '__Secure-1PAPISID', '__Secure-3PAPISID',
  '__Secure-1PSIDTS', '__Secure-3PSIDTS', '__Secure-1PSIDCC', '__Secure-3PSIDCC',
  '__Host-1PLSID', '__Host-3PLSID', '__Host-GAPS', 'SIDCC', 'OSID', 'LSOLH', 'SSOM'];

let timeoutHandle = null;
let cookieRefreshTimer = null;

function isIncognitoAllowed() {
  return new Promise((res) => {
    try { chrome.extension.isAllowedIncognitoAccess((ok) => res(!!ok)); } catch (e) { res(false); }
  });
}
function getJob() { return chrome.storage.local.get('job').then((d) => d.job || null); }
function setJob(job) { return chrome.storage.local.set({ job: job }); }

const POST_BLOCK_EXTRA = 12000;
const POST_BLOCK_WINDOW = 5;
function gap(job) {
  var extra = (job.postBlockSlow > 0)
    ? POST_BLOCK_EXTRA * Math.min(job.captchaStreak || 1, 5)
    : 0;
  return (job.watch ? GAP_WATCH : GAP_BACKGROUND)
    + Math.floor(Math.random() * GAP_JITTER)
    + extra;
}

function buildUrl(kw, job, start) {
  let url = SEARCH + '?q=' + encodeURIComponent(kw) +
    '&hl=' + encodeURIComponent(job.hl || 'en') +
    '&pws=0&gl=' + encodeURIComponent(job.gl || 'us');
  if (start) url += '&start=' + start;
  if (job.uule) url += '&uule=' + encodeURIComponent(job.uule);
  return url;
}

async function ensureConsentCookie(tabId) {
  try {
    let storeId;
    if (tabId != null) {
      const stores = await chrome.cookies.getAllCookieStores();
      const store = stores.find((s) => s.tabIds && s.tabIds.indexOf(tabId) !== -1);
      if (store) storeId = store.id;
    }
    const cookie = {
      url: 'https://www.google.com/',
      name: 'SOCS',
      value: SOCS_VALUE,
      domain: '.google.com',
      path: '/',
      secure: true,
      expirationDate: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 180
    };
    if (storeId) cookie.storeId = storeId;
    await chrome.cookies.set(cookie);
  } catch (e) {   }
}

async function buildCookieHeader() {
  let pairs = [], seen = {};
  try {
    const all = await chrome.cookies.getAll({ domain: 'google.com' });
    for (const c of all) {
      if (DROP_COOKIES.indexOf(c.name) === -1 && !seen[c.name]) { seen[c.name] = 1; pairs.push(c.name + '=' + c.value); }
    }
  } catch (e) {   }
  if (!seen.SOCS) pairs.push('SOCS=' + SOCS_VALUE);
  return pairs.join('; ');
}

async function applyTabRules(tabId, device, incognito) {
  try {
    const job = await getJob() || {};

    const cond = {
      urlFilter: '||google.com/',
      resourceTypes: ['main_frame', 'sub_frame', 'xmlhttprequest', 'script', 'image', 'font', 'ping', 'other'],
      tabIds: [tabId]
    };

    const requestHeaders = [

      { header: 'referer', operation: 'set', value: 'https://www.google.com/' },
      job.xgeo
        ? { header: 'x-geo', operation: 'set', value: job.xgeo }
        : { header: 'x-geo', operation: 'remove' }
    ];

    if (!incognito && job.pickedTabId == null) {
      requestHeaders.push({ header: 'cookie', operation: 'set', value: await buildCookieHeader() });
    }
    const addRules = [{
      id: ANON_RULE_ID,
      priority: RUN_RULE_PRIORITY,
      action: { type: 'modifyHeaders', requestHeaders: requestHeaders },
      condition: cond
    }, {

      id: RUN_SHIELD_RULE_ID,
      priority: RUN_SHIELD_PRIORITY,
      action: { type: 'allow' },
      condition: cond
    }];
    if (device === 'mobile') {

      addRules.push({
        id: UA_RULE_ID,
        priority: RUN_RULE_PRIORITY,
        action: {
          type: 'modifyHeaders',
          requestHeaders: [
            { header: 'user-agent', operation: 'set', value: MOBILE_UA },
            { header: 'sec-ch-ua', operation: 'set', value: '"Chromium";v="' + CHROME_MAJOR + '", "Google Chrome";v="' + CHROME_MAJOR + '", "Not-A.Brand";v="99"' },
            { header: 'sec-ch-ua-mobile', operation: 'set', value: '?1' },
            { header: 'sec-ch-ua-platform', operation: 'set', value: '"Android"' },
            { header: 'sec-ch-ua-platform-version', operation: 'set', value: '"14.0.0"' },
            { header: 'sec-ch-ua-model', operation: 'set', value: '"Pixel 8"' },
            { header: 'sec-ch-ua-full-version-list', operation: 'remove' },
            { header: 'sec-ch-ua-arch', operation: 'remove' },
            { header: 'sec-ch-ua-bitness', operation: 'remove' },
            { header: 'sec-ch-ua-wow64', operation: 'remove' },
            { header: 'sec-ch-ua-form-factors', operation: 'remove' }
          ]
        },
        condition: cond
      });
    }
    await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [UA_RULE_ID, ANON_RULE_ID, RUN_SHIELD_RULE_ID], addRules });
  } catch (e) {   }
}
function clearTabRules() {
  return chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [UA_RULE_ID, ANON_RULE_ID, RUN_SHIELD_RULE_ID] }).catch(() => {});
}

async function applyGlobalLocation() {
  try {
    const d = await chrome.storage.local.get('globalLoc');
    const g = d.globalLoc;
    const addRules = [];
    if (g && g.on) {

      const param = g.uule ? 'uule=' + encodeURIComponent(g.uule)
        : (g.gl ? 'gl=' + encodeURIComponent(g.gl) : '');
      if (param) {
        const searchTypes = ['main_frame', 'xmlhttprequest'];
        addRules.push({
          id: GLOBAL_UULE_RULE_ID,
          priority: 1,
          action: { type: 'redirect', redirect: { regexSubstitution: '\\1&' + param } },
          condition: { regexFilter: '^(https://www\\.google\\.com/search\\?[^#]*)', resourceTypes: searchTypes }
        });
        addRules.push({

          id: GLOBAL_GUARD_RULE_ID,
          priority: 2,
          action: { type: 'allow' },
          condition: { urlFilter: '||google.com/search*^' + (g.uule ? 'uule=' : 'gl='), resourceTypes: searchTypes }
        });
      }
      if (g.xgeo) {
        addRules.push({
          id: GLOBAL_XGEO_RULE_ID,
          priority: 3,
          action: { type: 'modifyHeaders', requestHeaders: [{ header: 'x-geo', operation: 'set', value: g.xgeo }] },
          condition: {
            urlFilter: '||google.com/',
            resourceTypes: ['main_frame', 'sub_frame', 'xmlhttprequest', 'script', 'image', 'font', 'ping', 'other']
          }
        });
      }
    }
    await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: GLOBAL_RULE_IDS, addRules });
  } catch (e) {   }
}

chrome.storage.onChanged.addListener((changes, area) => {
  if (area === 'local' && changes.globalLoc) applyGlobalLocation();
});

async function setGoogleGeolocation(setting) {
  const patterns = ['https://www.google.com/*', 'https://*.google.com/*'];
  for (const p of patterns) {
    try { await chrome.contentSettings.location.set({ primaryPattern: p, setting: setting }); } catch (e) {}

    try { await chrome.contentSettings.location.set({ primaryPattern: p, setting: setting, scope: 'incognito_session_only' }); } catch (e) {}
  }
}
function clearGeolocationRules() {
  try { return chrome.contentSettings.location.clear({}); } catch (e) { return Promise.resolve(); }
}

async function startJob(payload) {
  const lat = parseFloat(payload.lat), lng = parseFloat(payload.lng);

  const wantIncognito = payload.incognito !== false;
  const incognito = wantIncognito && await isIncognitoAllowed();

  let picked = null;
  if (payload.runTabId != null) {
    try { picked = await chrome.tabs.get(payload.runTabId); } catch (e) { picked = null; }
  }
  const job = {
    incognito: picked ? !!picked.incognito : incognito,
    pickedTabId: picked ? picked.id : null,
    pickedCurrent: picked ? !!payload.runTabCurrent : false,
    runWindowId: null,
    keywords: payload.keywords || [],
    target: payload.target || '',
    gl: payload.gl || 'us',
    hl: payload.hl || 'en',
    uule: payload.uule || '',
    xgeo: payload.xgeo || '',
    spoof: (isFinite(lat) && isFinite(lng)) ? { lat: lat, lng: lng } : null,
    device: payload.device === 'mobile' ? 'mobile' : 'desktop',
    locationLabel: payload.locationLabel || '',
    watch: !!payload.watch,
    running: true,
    paused: false,
    blockedReason: null,
    idx: 0,
    total: (payload.keywords || []).length,
    results: {},
    postBlockSlow: 0,
    captchaStreak: 0,
    manual: !!payload.manual,
    awaitingNext: false,
    awaitingPage: false,
    recheck: false,
    currentTabId: null,
    currentKw: null,
    runTabId: null,
    pageIdx: 0,
    accumCount: 0,
    startedAt: Date.now()
  };
  await setJob(job);
  if (!job.incognito) await ensureConsentCookie(null);
  await setGoogleGeolocation('block');
  openPage();
}

const PHONE_W = 405, PHONE_H = 740;

async function createRunTab(job) {

  if (job.pickedTabId != null) {
    try {
      const t = await chrome.tabs.get(job.pickedTabId);

      if (job.watch && !job.pickedCurrent) {
        try {
          if (!t.active) await chrome.tabs.update(t.id, { active: true });
          const w = await chrome.windows.get(t.windowId);
          if (!w.focused) await chrome.windows.update(t.windowId, { focused: true });
        } catch (e) {}
      }
      return t.id;
    } catch (e) { job.pickedTabId = null; }
  }

  const mobile = job.device === 'mobile';
  let tabId = null;

  if (mobile) {
    for (const incog of (job.incognito ? [true, false] : [false])) {
      try {
        const w = await chrome.windows.create({
          url: 'about:blank', type: 'popup', width: PHONE_W, height: PHONE_H,
          incognito: incog, focused: !!job.watch
        });
        if (w && w.tabs && w.tabs[0]) { tabId = w.tabs[0].id; job.runWindowId = w.id; job.incognito = incog; break; }
      } catch (e) {   }
    }
  }

  if (tabId == null && job.incognito) {
    try {
      const w = await chrome.windows.create({
        url: 'about:blank', incognito: true, focused: !!job.watch, state: job.watch ? 'maximized' : 'minimized'
      });
      if (w && w.tabs && w.tabs[0]) { tabId = w.tabs[0].id; job.runWindowId = w.id; }
    } catch (e) { tabId = null; }
    if (tabId == null) { job.incognito = false; job.runWindowId = null; }
  }

  if (tabId == null) {
    const t = await chrome.tabs.create({ url: 'about:blank', active: !!job.watch });
    tabId = t.id;
  }
  return tabId;
}

async function wantedIncognito() {
  let want = true;
  try { const d = await chrome.storage.local.get('ui.incognito'); want = d['ui.incognito'] !== false; } catch (e) {}
  return want && await isIncognitoAllowed();
}
async function syncIncognito(job) {

  if (job.pickedTabId != null) return;
  const incognito = await wantedIncognito();
  if (incognito === !!job.incognito) return;
  const closeWin = job.runWindowId, closeTab = job.runTabId;
  job.incognito = incognito;
  job.runTabId = null;
  job.runWindowId = null;
  job.currentTabId = null;

  await setJob(job);
  if (closeWin != null) { try { await chrome.windows.remove(closeWin); } catch (e) {} }
  else if (closeTab != null) { try { await chrome.tabs.remove(closeTab); } catch (e) {} }
  if (!incognito) await ensureConsentCookie(null);
}

const WARMUP_TIMEOUT = 8000;
function warmUp(tabId) {
  return new Promise((resolve) => {
    let done = false;
    const listener = (id, info) => { if (id === tabId && info.status === 'complete') finish(); };
    const finish = () => {
      if (done) return;
      done = true;
      try { chrome.tabs.onUpdated.removeListener(listener); } catch (e) {}
      setTimeout(resolve, 1200 + Math.floor(Math.random() * 1800));
    };
    chrome.tabs.onUpdated.addListener(listener);
    setTimeout(finish, WARMUP_TIMEOUT);
    chrome.tabs.update(tabId, { url: 'https://www.google.com/' }).catch(finish);
  });
}

async function openPage() {
  const job = await getJob();
  if (!job || !job.running || job.paused) return;
  if (!job.recheck && job.idx >= job.keywords.length) { await endRun(job); return; }

  const kw = job.recheck ? job.currentKw : job.keywords[job.idx];
  if (!kw) { await endRun(job); return; }

  if (!job.recheck && job.currentKw == null) await syncIncognito(job);
  const url = buildUrl(kw, job, job.pageIdx * RESULTS_PER_PAGE);

  try {

    let tabId = job.runTabId, exists = false;
    if (tabId != null) { try { await chrome.tabs.get(tabId); exists = true; } catch (e) { exists = false; } }
    if (!exists) {

      tabId = await createRunTab(job);
      job.runTabId = tabId;
      await applyTabRules(tabId, job.device, job.incognito);
      if (job.incognito) await ensureConsentCookie(tabId);
      await warmUp(tabId);
    }
    await chrome.tabs.update(tabId, { url: url });
    job.currentTabId = tabId;
  } catch (e) {
    await recordAndAdvance(job, kw, { rank: null, error: 'Could not open tab' });
    return;
  }

  job.currentKw = kw;
  await setJob(job);

  clearTimeout(timeoutHandle);
  timeoutHandle = setTimeout(() => onPageTimeout(kw), PER_PAGE_TIMEOUT);
}

async function onPageTimeout(kw) {
  const job = await getJob();
  if (!job || !job.running || job.paused || job.currentKw !== kw) return;
  await recordAndAdvance(job, kw, { rank: null, error: 'Timed out' });
}

async function handlePage(msg, senderTabId) {
  const job = await getJob();
  if (!job || !job.running || job.paused) return;
  if (job.currentTabId !== senderTabId) return;
  clearTimeout(timeoutHandle);

  if (msg.blocked) { await pauseForBlock(job, msg.blocked); return; }

  const kw = job.currentKw;
  const urls = msg.urls || [];

  if (typeof msg.targetIndex === 'number' && msg.targetIndex >= 0) {
    const rank = job.accumCount + msg.targetIndex + 1;
    if (job.watch) {
      try { chrome.tabs.sendMessage(job.currentTabId, { type: 'highlight', index: msg.targetIndex, rank: rank }); }
      catch (e) {}
    }
    await recordAndAdvance(job, kw, { rank: rank, url: urls[msg.targetIndex] || '' });
    return;
  }

  job.accumCount += urls.length;
  job.pageIdx += 1;
  const emptyPage = urls.length === 0;

  if (job.manual) {
    if (emptyPage) {
      await recordAndAdvance(job, kw, { rank: null, error: 'Not found (no more results)' });
    } else {
      job.awaitingPage = true;
      await setJob(job);
    }
    return;
  }

  const morePages = job.pageIdx < PAGES && !emptyPage;
  if (morePages) {
    await setJob(job);
    setTimeout(openPage, gap(job));
  } else {
    await recordAndAdvance(job, kw, { rank: null, error: 'Not in top ' + (PAGES * RESULTS_PER_PAGE) });
  }
}

async function nextPage() {
  const job = await getJob();
  if (!job || !job.running || !job.awaitingPage) return;
  job.awaitingPage = false;
  await setJob(job);
  openPage();
}

async function skipKeyword() {
  const job = await getJob();
  if (!job || !job.running) return;
  const kw = job.currentKw;
  if (!kw) return;
  clearTimeout(timeoutHandle);
  job.awaitingPage = false;
  job.paused = false;
  job.blockedReason = null;
  await setJob(job);
  await recordAndAdvance(job, kw, { rank: null, error: 'Skipped' });
}

async function recordAndAdvance(job, kw, data) {
  clearTimeout(timeoutHandle);
  const fresh = await getJob();
  if (!fresh || fresh.currentKw !== kw) return;

  fresh.results[kw] = (data.rank != null)
    ? { rank: data.rank, url: data.url || '', status: 'Found' }
    : { rank: '', url: '', status: data.error || 'Not found' };

  if (fresh.recheck) {
    fresh.recheck = false;
    fresh.currentKw = null;
    fresh.pageIdx = 0;
    fresh.accumCount = 0;
    await finishRecheck(fresh);
    return;
  }

  fresh.idx += 1;
  fresh.pageIdx = 0;
  fresh.accumCount = 0;
  fresh.currentKw = null;

  if (fresh.postBlockSlow > 0) { fresh.postBlockSlow -= 1; if (fresh.postBlockSlow === 0) fresh.captchaStreak = 0; }

  const more = fresh.running && fresh.idx < fresh.keywords.length;
  if (!more) { await endRun(fresh); }
  else if (fresh.manual) { fresh.awaitingNext = true; await setJob(fresh); }
  else { await setJob(fresh); setTimeout(openPage, gap(fresh)); }
}

async function nextKeyword() {
  const job = await getJob();
  if (!job || !job.running || !job.awaitingNext) return;
  job.awaitingNext = false;
  await setJob(job);
  openPage();
}

async function setRunMode(payload) {
  const job = await getJob();
  if (!job || !job.running) return;
  const wasManual = !!job.manual;
  job.manual = !!payload.manual;
  job.watch = !!payload.watch;
  await setJob(job);

  try {
    if (job.runWindowId != null) {
      await chrome.windows.update(job.runWindowId, job.watch
        ? { state: 'normal', focused: false }
        : { state: 'minimized' });
    }
  } catch (e) {}

  if (wasManual && !job.manual) {
    const fresh = await getJob();
    if (fresh && fresh.running && !fresh.paused && (fresh.awaitingPage || fresh.awaitingNext)) {
      fresh.awaitingPage = false;
      fresh.awaitingNext = false;
      await setJob(fresh);
      setTimeout(openPage, gap(fresh));
    }
  }
}

async function recheckKeyword(kw) {
  let job = await getJob();
  if (!job || job.running || !job.keywords || job.keywords.indexOf(kw) === -1) return;

  await syncIncognito(job);
  job.running = true;
  job.paused = false;
  job.blockedReason = null;
  job.recheck = true;
  job.currentKw = kw;
  job.pageIdx = 0;
  job.accumCount = 0;
  job.postBlockSlow = 0;
  delete job.results[kw];
  await setJob(job);
  if (!job.incognito) await ensureConsentCookie(null);
  await setGoogleGeolocation('block');
  if (job.runTabId != null) await applyTabRules(job.runTabId, job.device, job.incognito);
  openPage();
}

async function finishRecheck(job) {
  const pinned = job.pickedTabId != null;
  const closeTab = (!job.watch && !pinned) ? job.runTabId : null;
  const closeWin = (!job.watch && !pinned) ? job.runWindowId : null;
  job.running = false;
  job.paused = false;
  job.blockedReason = null;
  job.currentTabId = null;
  if (!job.watch && !pinned) { job.runTabId = null; job.runWindowId = null; }
  await setJob(job);
  if (closeWin != null) { try { await chrome.windows.remove(closeWin); } catch (e) {} }
  else if (closeTab != null) { try { await chrome.tabs.remove(closeTab); } catch (e) {} }
  clearTabRules();
  clearGeolocationRules();
}

async function pauseForBlock(job, reason) {
  clearTimeout(timeoutHandle);
  job.paused = true;
  job.blockedReason = reason;
  if (reason === 'captcha' || reason === 'forbidden') job.captchaStreak = (job.captchaStreak || 0) + 1;
  await setJob(job);

  if (job.currentTabId != null) { try { await chrome.tabs.update(job.currentTabId, { active: true }); } catch (e) {} }
}

async function pauseManual() {
  const job = await getJob();
  if (!job || !job.running || job.paused) return;
  clearTimeout(timeoutHandle);
  job.paused = true;
  job.blockedReason = 'manual';
  await setJob(job);
}

async function resumeJob() {
  const job = await getJob();
  if (!job || !job.paused) return;
  const reason = job.blockedReason;
  const wasBlock = reason === 'captcha' || reason === 'consent' || reason === 'forbidden';
  job.paused = false;
  job.blockedReason = null;
  if (wasBlock) job.postBlockSlow = POST_BLOCK_WINDOW;
  await setJob(job);

  if (job.runTabId != null) await applyTabRules(job.runTabId, job.device, job.incognito);

  if (reason === 'forbidden') { openPage(); return; }

  let reread = false;
  if (job.currentTabId != null) {
    try { await chrome.tabs.sendMessage(job.currentTabId, { type: 'reread' }); reread = true; }
    catch (e) { reread = false; }
  }
  if (reread) {
    clearTimeout(timeoutHandle);
    timeoutHandle = setTimeout(() => onPageTimeout(job.currentKw), PER_PAGE_TIMEOUT);
  } else {
    openPage();
  }
}

async function endRun(job) {

  const pinned = job.pickedTabId != null;
  const closeTab = (!job.watch && !pinned) ? job.runTabId : null;
  const closeWin = (!job.watch && !pinned) ? job.runWindowId : null;
  job.running = false;
  job.paused = false;
  job.blockedReason = null;
  job.currentKw = null;
  job.currentTabId = null;
  if (!job.watch && !pinned) { job.runTabId = null; job.runWindowId = null; }
  await setJob(job);
  if (closeWin != null) { try { await chrome.windows.remove(closeWin); } catch (e) {} }
  else if (closeTab != null) { try { await chrome.tabs.remove(closeTab); } catch (e) {} }
  clearTabRules();
  clearGeolocationRules();
}

async function stopJob() {
  clearTimeout(timeoutHandle);
  const job = await getJob();
  if (!job) { clearTabRules(); clearGeolocationRules(); return; }
  const pinned = job.pickedTabId != null;
  const closeTab = (!job.watch && !pinned) ? job.runTabId : null;
  const closeWin = (!job.watch && !pinned) ? job.runWindowId : null;
  job.running = false;
  job.paused = false;
  job.blockedReason = null;
  job.currentTabId = null;
  job.currentKw = null;
  if (!job.watch && !pinned) { job.runTabId = null; job.runWindowId = null; }
  await setJob(job);
  if (closeWin != null) { try { await chrome.windows.remove(closeWin); } catch (e) {} }
  else if (closeTab != null) { try { await chrome.tabs.remove(closeTab); } catch (e) {} }
  clearTabRules();
  clearGeolocationRules();
}

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (!msg || !msg.type) return;

  if (msg.type === 'start') { startJob(msg); sendResponse({ ok: true }); return true; }
  if (msg.type === 'stop') { stopJob(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'recheck') { recheckKeyword(msg.keyword); sendResponse({ ok: true }); return true; }
  if (msg.type === 'next') { nextKeyword(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'set-mode') { setRunMode(msg); sendResponse({ ok: true }); return true; }
  if (msg.type === 'nextpage') { nextPage(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'skip') { skipKeyword(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'pause') { pauseManual(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'resume') { resumeJob(); sendResponse({ ok: true }); return true; }
  if (msg.type === 'clear') { clearTabRules(); clearGeolocationRules(); chrome.storage.local.remove('job'); sendResponse({ ok: true }); return true; }

  if (msg.type === 'serp-page') {
    if (sender.tab) handlePage(msg, sender.tab.id);
    return;
  }
});

chrome.tabs.onRemoved.addListener((tabId) => {
  getJob().then((job) => {
    if (!job || !job.running || job.runTabId !== tabId) return;
    clearTimeout(timeoutHandle);
    job.running = false; job.paused = false; job.blockedReason = null;
    job.currentTabId = null; job.currentKw = null; job.runTabId = null; job.runWindowId = null;
    setJob(job); clearTabRules(); clearGeolocationRules();
  });
});

chrome.cookies.onChanged.addListener((info) => {
  const c = info.cookie;
  if (!c || (c.domain || '').indexOf('google.com') === -1) return;
  if (DROP_COOKIES.indexOf(c.name) !== -1) return;
  clearTimeout(cookieRefreshTimer);
  cookieRefreshTimer = setTimeout(() => {
    getJob().then((job) => {

      if (job && job.running && !job.incognito && job.pickedTabId == null && job.runTabId != null) applyTabRules(job.runTabId, job.device, false);
    });
  }, 400);
});

function clearStaleRules() {
  chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: [UA_RULE_ID] }).catch(() => {});
  getJob().then((job) => { if (!job || !job.running) { clearTabRules(); clearGeolocationRules(); } });
  applyGlobalLocation();
}
chrome.runtime.onStartup.addListener(clearStaleRules);
clearStaleRules();

chrome.tabs.onUpdated.addListener((tabId, info, tab) => {
  if (info.status !== 'complete' || !tab || !tab.url) return;
  getJob().then((job) => {
    if (!job || !job.running || job.paused || job.currentTabId !== tabId) return;
    const u = tab.url;
    if (u.indexOf('consent.google.') !== -1 || u.indexOf('/sorry') !== -1) {
      pauseForBlock(job, u.indexOf('/sorry') !== -1 ? 'captcha' : 'consent');
    }
  });
});
