'use strict';

const $ = (id) => document.getElementById(id);

let mode = 'manual';
let device = 'desktop';
let locPicker = null;
let lastLocation = null;

const MODE_HINTS = {
  manual: 'You click “Search next keyword” and “Check next page”, going as deep as you like with a Skip button. Fewest CAPTCHAs.',
  watch: 'Auto-checks each keyword in a visible tab so you can watch it happen (first 3 pages).',
  background: 'Auto-checks silently in a hidden window and stays out of your way (first 3 pages).'
};

locPicker = LocationPicker.mount($('loc'), (sel) => {
  lastLocation = sel;
  chrome.storage.local.set({ 'ui.location': locPicker.uiState() });
});

chrome.storage.local.get(['ui.location', 'globalLoc']).then((d) => {
  if (d.globalLoc && d.globalLoc.on && d['ui.location']) locPicker.set(d['ui.location']);
});

function setMode(m) {
  mode = MODE_HINTS[m] ? m : 'manual';
  Array.from($('mode-seg').children).forEach((b) => b.classList.toggle('is-on', b.dataset.mode === mode));
  $('mode-hint').textContent = MODE_HINTS[mode];
  chrome.storage.local.set({ 'ui.mode': mode });
}
$('mode-seg').addEventListener('click', (e) => {
  const btn = e.target.closest('.seg-btn');
  if (btn) setMode(btn.dataset.mode);
});

const kwUI = BKS.mountKeywordSummary({
  textarea: $('kw'), summary: $('kw-summary'),
  count: $('kw-summary-count'), detail: $('kw-summary-detail'), edit: $('kw-edit-btn')
});

function setDevice(d) {
  device = (d === 'mobile') ? 'mobile' : 'desktop';
  Array.from($('device-seg').children).forEach((b) => b.classList.toggle('is-on', b.dataset.device === device));
  chrome.storage.local.set({ 'ui.device': device });
}
$('device-seg').addEventListener('click', (e) => {
  const btn = e.target.closest('[data-device]');
  if (btn) setDevice(btn.dataset.device);
});

chrome.storage.local.get(['ui.hl', 'ui.device', 'ui.mode']).then((d) => {
  if (d['ui.hl']) $('hl-select').value = d['ui.hl'];
  if (d['ui.device']) setDevice(d['ui.device']);
  setMode(d['ui.mode'] || 'manual');
});
$('hl-select').addEventListener('change', () => chrome.storage.local.set({ 'ui.hl': $('hl-select').value }));

$('pause-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'pause' }));
$('resume-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'resume' }));
$('resume-skip-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'skip' }));
$('next-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'next' }));
$('nextpage-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'nextpage' }));
$('skip-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'skip' }));

$('run-mode-seg').addEventListener('click', (e) => {
  const btn = e.target.closest('[data-mode]');
  if (!btn) return;
  const m = btn.dataset.mode;
  setMode(m);
  chrome.runtime.sendMessage({ type: 'set-mode', manual: (m === 'manual'), watch: (m !== 'background') });
});

function doSave(silent) {
  const domain = BKS.cleanDomain($('domain').value);
  const keywords = BKS.parseKeywords($('kw').value);

  if (!domain) { if (!silent) showMsg('Enter your domain first — saved checks are named by domain.'); return Promise.resolve(false); }
  return BKS.saveQuery({
    id: 'q' + Date.now().toString(36),
    name: domain,
    keywords: keywords,
    target: domain,
    hl: $('hl-select').value,
    device: device,
    location: locPicker.uiState()
  }).then(() => refreshSavedList()).then(() => {
    if (!silent) {
      const b = $('save-icon-btn'); b.classList.add('is-ok'); setTimeout(() => b.classList.remove('is-ok'), 1200);
      showMsg('Saved “' + domain + '” to your Saved list.', true);
    }
    return true;
  });
}
$('save-icon-btn').addEventListener('click', () => doSave(false));

$('domain').addEventListener('keydown', (e) => {
  if (e.key === 'Enter') { e.preventDefault(); doSave(false); }
});

let savedCache = [];

function faviconUrl(domain) {
  return 'https://www.google.com/s2/favicons?sz=64&domain=' + encodeURIComponent(domain);
}
function refreshSavedList() {
  return BKS.loadQueries().then((list) => {
    savedCache = list;
    const wrap = $('saved-wrap');
    const box = $('saved-list');
    wrap.hidden = false;
    $('saved-toggle').hidden = !list.length;
    if (!list.length) { box.innerHTML = ''; closeSavedMenu(); return list; }
    $('saved-toggle-label').textContent = 'Saved (' + list.length + ')';
    box.innerHTML = list.map((q) => {
      const dom = q.target || q.name || '';
      return '<div class="pop-srow" data-id="' + q.id + '" title="Load “' + BKS.escapeHtml(dom) + '” (' + q.keywords.length + ' keywords)">'
        + '<img class="pop-chip-fav" src="' + faviconUrl(dom) + '" alt="" width="16" height="16" data-fallback="1">'
        + '<span class="pop-srow-name">' + BKS.escapeHtml(dom || '(no domain)') + '</span>'
        + '<span class="pop-chip-count">' + q.keywords.length + '</span>'
        + '<button class="pop-chip-del" type="button" data-del="' + q.id + '" title="Delete this saved check">&times;</button>'
        + '</div>';
    }).join('');

    box.querySelectorAll('img[data-fallback]').forEach((img) => {
      img.addEventListener('error', () => {
        img.replaceWith(Object.assign(document.createElement('span'), {
          className: 'pop-srow-globe', innerHTML: '<svg class="ic" aria-hidden="true"><use href="#i-globe"></use></svg>'
        }));
      }, { once: true });
    });
    return list;
  });
}
refreshSavedList();

function openSavedMenu() { $('saved-list').hidden = false; $('saved-toggle').setAttribute('aria-expanded', 'true'); }
function closeSavedMenu() { $('saved-list').hidden = true; $('saved-toggle').setAttribute('aria-expanded', 'false'); }
$('saved-toggle').addEventListener('click', () => {
  if ($('saved-list').hidden) openSavedMenu(); else closeSavedMenu();
});

function loadQuery(id) {
  const q = savedCache.find((x) => x.id === id);
  if (!q) return;
  $('kw').value = q.keywords.join('\n');
  $('domain').value = q.target || '';
  if (q.hl) $('hl-select').value = q.hl;
  setDevice(q.device);
  if (q.location) locPicker.set(q.location);
  closeSavedMenu();
  kwUI.collapse();
  showMsg('Loaded “' + (q.target || q.name) + '”.', true);
}
$('saved-list').addEventListener('click', (e) => {
  const del = e.target.closest('.pop-chip-del');
  if (del) { e.stopPropagation(); BKS.deleteQuery(del.dataset.del).then(() => refreshSavedList()); return; }
  const row = e.target.closest('.pop-srow');
  if (row) loadQuery(row.dataset.id);
});

$('clear-form-btn').addEventListener('click', () => {
  $('kw').value = '';
  $('domain').value = '';
  locPicker.clear();
  showMsg('');
  closeSavedMenu();
  kwUI.expand(true);
});

$('rerun-btn').addEventListener('click', () => {
  BKS.openRunTabChooser({ allowCurrent: false }).then((sel) => {
    if (!sel) return;
    advanceTip();
    withJob((job) => chrome.runtime.sendMessage({
      type: 'start',
      keywords: job.keywords, target: job.target, gl: job.gl, hl: job.hl, uule: job.uule,
      xgeo: job.spoof ? UULE.xgeo(job.spoof.lat, job.spoof.lng) : '',
      lat: job.spoof ? job.spoof.lat : null, lng: job.spoof ? job.spoof.lng : null,
      device: job.device, locationLabel: job.locationLabel, watch: job.watch, manual: job.manual,
      incognito: sel.incognito,
      runTabId: sel.tabId,
      runTabCurrent: sel.isCurrent
    }));
  });
});

let runTipIndex = 0;
function renderTip() {
  const el = $('run-tip-text');
  if (el) el.innerHTML = BKS.tipAt(runTipIndex);
}
chrome.storage.local.get('ui.runTip').then((d) => { runTipIndex = d['ui.runTip'] || 0; renderTip(); });
function advanceTip() {
  return chrome.storage.local.get('ui.tipIndex').then((d) => {
    const i = d['ui.tipIndex'] || 0;
    runTipIndex = i; renderTip();
    return chrome.storage.local.set({ 'ui.tipIndex': i + 1, 'ui.runTip': i });
  });
}

let msgTimer = 0;
function showMsg(text, ok) {
  const el = $('setup-msg');
  clearTimeout(msgTimer);
  el.classList.remove('is-hide');
  if (!text) { el.hidden = true; el.classList.remove('is-ok'); return; }
  el.textContent = text;
  el.classList.toggle('is-ok', !!ok);
  el.hidden = false;
  if (ok) {
    msgTimer = setTimeout(() => {
      el.classList.add('is-hide');
      msgTimer = setTimeout(() => { el.hidden = true; el.classList.remove('is-hide', 'is-ok'); }, 350);
    }, 2200);
  }
}

function renderJob(job) {
  const hasJob = job && job.keywords && job.keywords.length;

  if (hasJob && job.locationLabel) { $('run-loc').hidden = false; $('run-loc-text').textContent = job.locationLabel; }
  else $('run-loc').hidden = true;

  const done = hasJob ? job.idx : 0;
  const total = hasJob ? (job.total || job.keywords.length) : 0;
  const pct = total ? Math.round((done / total) * 100) : 0;
  $('prog-fill').style.width = pct + '%';
  $('prog-count').textContent = done + ' / ' + total;

  if (!hasJob) {
    $('prog-label').textContent = 'Waiting to start…';
    $('prog-current').textContent = '';
    $('stop-btn').hidden = true; $('pause-btn').hidden = true; $('new-btn').hidden = true; $('rerun-btn').hidden = true;
    $('next-bar').hidden = true;
    $('runmode').hidden = true;
    $('copy-btn').disabled = true; $('csv-btn').disabled = true; $('snap-btn').disabled = true;
    $('rows').innerHTML = '<tr class="db-empty"><td colspan="5">Your keywords and their live rankings will appear here.</td></tr>';
    return;
  }

  const banner = $('resume-banner');
  if (job.running && job.paused) {
    banner.hidden = false;
    const reason = job.blockedReason;
    let rt = reason === 'manual' ? 'Paused. Click Resume when you’re ready to continue.'
      : reason === 'consent' ? 'Google is showing a cookie-consent screen. Accept it in the open tab, then resume.'
      : reason === 'forbidden' ? 'Google blocked this request with a 403 (“forbidden”) — a temporary anti-scraping block, common on deep pages or cross-country checks from a flagged IP. Wait ~30–60s and Retry, or Skip this keyword. It usually clears on its own; a VPN in the target country + Incognito + smaller batches helps.'
      : 'Google is asking you to confirm you’re human. Solve the CAPTCHA in the open tab, then resume.';
    if (reason === 'captcha' && (job.captchaStreak || 0) >= 2) {
      rt += ' Repeated CAPTCHAs? Try picking an open tab you use daily on your next run (a trusted session), use 10–15 keywords per run, or connect from the country you’re checking.';
    }
    $('resume-text').textContent = rt;
    $('resume-btn').textContent = reason === 'forbidden' ? 'Retry now'
      : reason === 'consent' ? 'I’ve accepted it — Resume'
      : reason === 'manual' ? 'Resume'
      : 'I’ve solved it — Resume';
    $('resume-skip-btn').hidden = !(reason === 'captcha' || reason === 'forbidden');
  } else {
    banner.hidden = true;
  }

  renderTip();

  $('runmode').hidden = !job.running;
  const curMode = job.manual ? 'manual' : (job.watch ? 'watch' : 'background');
  Array.from($('run-mode-seg').children).forEach((b) => b.classList.toggle('is-on', b.dataset.mode === curMode));

  if (job.running) {
    const paused = job.paused;
    const awaitingPage = job.awaitingPage;
    const awaitingNext = job.awaitingNext;
    const waiting = awaitingPage || awaitingNext;
    $('next-bar').hidden = !waiting;
    $('nextpage-btn').hidden = !awaitingPage;
    $('skip-btn').hidden = !awaitingPage;
    $('next-btn').hidden = !awaitingNext;
    if (awaitingPage) {
      $('next-msg').textContent = '“' + (job.currentKw || '') + '” isn’t in the first ' + job.accumCount + ' results yet — keep going or skip.';
      $('prog-label').textContent = 'Your move — go deeper or skip';
      $('prog-current').textContent = 'Checked ' + job.accumCount + ' results';
    } else if (awaitingNext) {
      $('next-msg').textContent = 'Ready for the next keyword.';
      $('prog-label').textContent = 'Ready for the next keyword';
      $('prog-current').textContent = 'Next: ' + (job.keywords[job.idx] || '');
    } else {
      $('prog-label').textContent = paused
        ? (job.blockedReason === 'manual' ? 'Paused' : 'Paused — action needed')
        : (job.watch ? 'Watching…' : 'Checking…');
      $('prog-current').textContent = job.currentKw ? ('Now: ' + job.currentKw) : '';
    }
    $('stop-btn').hidden = false; $('pause-btn').hidden = paused || waiting; $('new-btn').hidden = true; $('rerun-btn').hidden = true;
  } else {
    $('next-bar').hidden = true;
    $('prog-label').textContent = (done >= total) ? 'Done' : 'Stopped';
    $('prog-current').textContent = '';
    $('stop-btn').hidden = true; $('pause-btn').hidden = true; $('new-btn').hidden = false; $('rerun-btn').hidden = false;
  }

  const idle = !job.running;

  let order = job.keywords;
  if (job.running && job.currentKw && job.keywords.indexOf(job.currentKw) !== -1) {
    order = [job.currentKw].concat(job.keywords.filter((k) => k !== job.currentKw));
  }
  const rows = order.map((kw) => {
    const e = job.results[kw];
    const cur = job.running && kw === job.currentKw;
    let urlCell = '';
    if (e && e.status === 'Found' && e.url) {
      urlCell = '<a href="' + BKS.escapeHtml(e.url) + '" target="_blank" rel="noopener">' + BKS.escapeHtml(e.url) + '</a>';
    } else if (e && e.status && e.status !== 'Found') {
      urlCell = '<span class="db-muted">' + BKS.escapeHtml(e.status) + '</span>';
    }
    return '<tr' + (cur ? ' class="is-current"' : '') + '>'
      + '<td class="c-num">' + (job.keywords.indexOf(kw) + 1) + '</td>'
      + '<td class="kw-cell">' + BKS.escapeHtml(kw) + '</td>'
      + '<td class="c-rank">' + BKS.rankCellHtml(e) + '</td>'
      + '<td class="url-cell">' + urlCell + '</td>'
      + '<td class="c-act">' + (idle ? rowRetryBtn(kw) : '') + '</td>'
      + '</tr>';
  }).join('');
  $('rows').innerHTML = rows;

  const hasResults = Object.keys(job.results || {}).length > 0;
  $('copy-btn').disabled = !hasResults;
  $('csv-btn').disabled = !hasResults;
  $('snap-btn').disabled = !hasResults;
}

async function withJob(fn) {
  const d = await chrome.storage.local.get('job');
  if (d.job) fn(d.job);
}

$('start-btn').addEventListener('click', () => {
  showMsg('');
  const keywords = BKS.parseKeywords($('kw').value);
  const target = BKS.cleanDomain($('domain').value);
  const loc = (lastLocation || locPicker.value());

  if (!keywords.length) { showMsg('Add at least one keyword.'); return; }
  if (!target) { showMsg('Enter your domain (e.g. example.com).'); return; }
  if (keywords.length > 100) { showMsg('Please keep it to 100 keywords or fewer per run.'); return; }

  doSave(true);

  BKS.openRunTabChooser({ allowCurrent: false }).then((sel) => {
    if (!sel) return;
    advanceTip();
    chrome.runtime.sendMessage({
      type: 'start',
      keywords: keywords,
      target: target,
      gl: loc.gl,
      hl: $('hl-select').value,
      uule: loc.uule,
      xgeo: loc.xgeo || '',
      lat: loc.lat, lng: loc.lng,
      device: device,
      locationLabel: loc.label,
      watch: (mode !== 'background'),
      manual: (mode === 'manual'),
      incognito: sel.incognito,
      runTabId: sel.tabId,
      runTabCurrent: sel.isCurrent
    });
  });
});

$('stop-btn').addEventListener('click', () => chrome.runtime.sendMessage({ type: 'stop' }));

$('new-btn').addEventListener('click', () => {
  chrome.runtime.sendMessage({ type: 'clear' }, () => {
    $('kw').value = ''; $('domain').value = '';
    kwUI.expand(false);
    renderJob(null);
  });
});

$('copy-btn').addEventListener('click', () => {
  withJob((job) => {
    const rows = BKS.jobToRows(job);
    const text = rows.map((r) => r.keyword + '\t' + (r.rank === '' ? 'Not found' : r.rank)).join('\n');
    navigator.clipboard.writeText(text).then(() => BKS.flash($('copy-btn'), 'Copied'));
  });
});

$('csv-btn').addEventListener('click', () => {
  withJob((job) => {
    const csv = BKS.toCsv(BKS.jobToRows(job), job.locationLabel);
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    BKS.triggerDownload(url, 'keyword-rankings.csv');
    setTimeout(() => URL.revokeObjectURL(url), 4000);
    BKS.flash($('csv-btn'), 'Saved');
  });
});

function rowRetryBtn(kw) {
  return '<button class="row-retry" type="button" data-kw="' + BKS.escapeHtml(kw) +
    '" title="Re-check just this keyword"><svg class="ic" aria-hidden="true"><use href="#i-refresh"></use></svg></button>';
}
$('rows').addEventListener('click', (e) => {
  const btn = e.target.closest('.row-retry');
  if (btn) chrome.runtime.sendMessage({ type: 'recheck', keyword: btn.dataset.kw });
});

let histCache = [];

function seriesFor(target, kw) {
  return histCache
    .filter((s) => s.target === target)
    .slice().reverse()
    .map((s) => { const e = s.results[kw]; return (e && e.status === 'Found' && e.rank !== '') ? Number(e.rank) : null; });
}

function sparklineSvg(series) {
  const w = 76, h = 22, pad = 3;
  const nums = series.filter((r) => r != null);
  if (!nums.length) return '<span class="db-muted">—</span>';
  const min = Math.min(...nums), max = Math.max(...nums), span = (max - min) || 1;
  const n = series.length;
  const xAt = (i) => pad + (n <= 1 ? (w - 2 * pad) / 2 : i * (w - 2 * pad) / (n - 1));
  const yAt = (r) => pad + ((r - min) / span) * (h - 2 * pad);
  const coords = [];
  series.forEach((r, i) => { if (r != null) coords.push([xAt(i), yAt(r)]); });
  const title = 'Ranks over ' + nums.length + ' saved run' + (nums.length > 1 ? 's' : '') +
    ': ' + nums.join(', ') + (min === nums[nums.length - 1] ? '' : '');
  if (coords.length === 1) {
    return '<svg class="spark" viewBox="0 0 ' + w + ' ' + h + '" width="' + w + '" height="' + h + '" aria-hidden="true"><title>' +
      BKS.escapeHtml(title) + '</title><circle cx="' + coords[0][0].toFixed(1) + '" cy="' + coords[0][1].toFixed(1) + '" r="2.5" fill="currentColor"/></svg>';
  }
  const d = coords.map((c, i) => (i ? 'L' : 'M') + c[0].toFixed(1) + ' ' + c[1].toFixed(1)).join(' ');
  const last = coords[coords.length - 1];
  return '<svg class="spark" viewBox="0 0 ' + w + ' ' + h + '" width="' + w + '" height="' + h + '" aria-hidden="true"><title>' +
    BKS.escapeHtml(title) + '</title>' +
    '<path d="' + d + '" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>' +
    '<circle cx="' + last[0].toFixed(1) + '" cy="' + last[1].toFixed(1) + '" r="2.2" fill="currentColor"/></svg>';
}

function rankBadge(rank) {
  if (rank === '' || rank == null) return '<span class="rank-badge rank-miss">—</span>';
  return '<span class="rank-badge rank-hit">#' + rank + '</span>';
}
function deltaHtml(d, hasPrev) {
  if (!hasPrev) return '<span class="db-muted">—</span>';
  switch (d.dir) {
    case 'up':   return '<span class="delta delta-up">▲ ' + d.by + '</span>';
    case 'down': return '<span class="delta delta-down">▼ ' + d.by + '</span>';
    case 'new':  return '<span class="delta delta-new">New</span>';
    case 'lost': return '<span class="delta delta-lost">Dropped</span>';
    case 'same': return '<span class="delta delta-same">No change</span>';
    default:     return '<span class="db-muted">—</span>';
  }
}

function refreshHistory(selectId) {
  return BKS.loadHistory().then((list) => {
    histCache = list;
    const sel = $('hist-select');
    if (!list.length) {
      sel.innerHTML = '<option value="">No saved runs yet</option>';
      $('hist-del-btn').disabled = true;
      renderComparison(null);
      return;
    }
    sel.innerHTML = list.map((s) => {
      const loc = s.locationLabel ? ' · ' + s.locationLabel : '';
      const label = BKS.formatWhen(s.savedAt) + ' — ' + (s.target || '(no domain)') + loc;
      return '<option value="' + s.id + '">' + BKS.escapeHtml(label) + '</option>';
    }).join('');
    const pick = (selectId && list.some((s) => s.id === selectId)) ? selectId : list[0].id;
    sel.value = pick;
    $('hist-del-btn').disabled = false;
    renderComparison(pick);
  });
}

function renderComparison(id) {
  const tbody = $('hist-rows');
  const note = $('hist-compare-note');
  const snap = histCache.find((s) => s.id === id);
  if (!snap) {
    tbody.innerHTML = '<tr class="db-empty"><td colspan="6">Finish a check and click <strong>Save to history</strong> to start tracking your rankings over time.</td></tr>';
    note.hidden = true;
    return;
  }
  const prev = BKS.previousSnapshot(histCache, snap);
  note.hidden = false;
  note.innerHTML = prev
    ? 'Comparing <strong>' + BKS.escapeHtml(BKS.formatWhen(snap.savedAt)) + '</strong> with the previous saved run for '
      + BKS.escapeHtml(snap.target || 'this domain') + ' (' + BKS.escapeHtml(BKS.formatWhen(prev.savedAt)) + ').'
    : 'First saved run for <strong>' + BKS.escapeHtml(snap.target || 'this domain') + '</strong> — save another run later to see how ranks move.';

  const keys = Object.keys(snap.results);
  if (!keys.length) { tbody.innerHTML = '<tr class="db-empty"><td colspan="6">This run has no results.</td></tr>'; return; }
  tbody.innerHTML = keys.map((kw, i) => {
    const cur = snap.results[kw] || {};
    const p = prev ? (prev.results[kw] || {}) : {};
    const curRank = cur.status === 'Found' ? cur.rank : '';
    const prevRank = (p && p.status === 'Found') ? p.rank : '';
    const d = BKS.rankDelta(curRank, prevRank);
    return '<tr>'
      + '<td class="c-num">' + (i + 1) + '</td>'
      + '<td class="kw-cell">' + BKS.escapeHtml(kw) + '</td>'
      + '<td class="c-rank">' + rankBadge(curRank) + '</td>'
      + '<td class="c-rank">' + (prev ? rankBadge(prevRank) : '<span class="db-muted">—</span>') + '</td>'
      + '<td>' + deltaHtml(d, !!prev) + '</td>'
      + '<td class="spark-cell">' + sparklineSvg(seriesFor(snap.target, kw)) + '</td>'
      + '</tr>';
  }).join('');
}

$('hist-select').addEventListener('change', () => renderComparison($('hist-select').value));
$('hist-del-btn').addEventListener('click', () => {
  const id = $('hist-select').value;
  if (id) BKS.deleteSnapshot(id).then(() => refreshHistory());
});
$('snap-btn').addEventListener('click', () => {
  withJob((job) => {
    if (!job || !Object.keys(job.results || {}).length) return;
    BKS.saveSnapshot(job).then((snap) => { refreshHistory(snap.id); BKS.flash($('snap-btn'), 'Saved ✓'); });
  });
});

chrome.storage.onChanged.addListener((changes, area) => {
  if (area !== 'local') return;
  if (changes['ui.runTip']) { runTipIndex = changes['ui.runTip'].newValue || 0; renderTip(); }
  if (changes.rankHistory) refreshHistory($('hist-select').value);
  if (changes.job) renderJob(changes.job.newValue);
});

chrome.storage.local.get('job').then((d) => renderJob(d.job || null));
refreshHistory();
