// adeater-utils.js — Stats accumulator, sweep scheduler, and DOM utility functions.
// Depends on: adeater-selectors.js (SAFE_TAGS, PROTECTED_SELECTORS, TEXT_THRESHOLD, TABBY_FRAME_ID).

// ---------------------------------------------------------------------------
// Per-site trust set — populated from storage in adeater.js init().
// Any hostname in this set suppresses all threat reports for that site.
// ---------------------------------------------------------------------------
let trustedDomains = new Set();

function isTrustedDomain() {
  const host = location.hostname.toLowerCase();
  for (const d of trustedDomains) {
    if (host === d || host.endsWith("." + d)) return true;
  }
  return false;
}

// ---------------------------------------------------------------------------
// Stats accumulator
// ---------------------------------------------------------------------------
let stats = {
  adsRemoved: 0,
  trackersRemoved: 0,
  videosPaused: 0,
  heavyElementsRemoved: 0,
  estimatedBytesSaved: 0,
  canvasBlocks: 0,
  webrtcBlocks: 0,
  minersDetected: 0,
  redirectsBlocked: 0,
  baitRemoved: 0,
  threatsBlocked: 0,
  perfMetrics: null,
};

function reportStats() {
  try {
    const snapshot = { ...stats };
    stats = { adsRemoved: 0, trackersRemoved: 0, videosPaused: 0, heavyElementsRemoved: 0, estimatedBytesSaved: 0, canvasBlocks: 0, webrtcBlocks: 0, minersDetected: 0, redirectsBlocked: 0, baitRemoved: 0, threatsBlocked: 0, perfMetrics: null };
    chrome.runtime.sendMessage({
      type: "AD_EATER_STATS",
      ...snapshot,
      pagesProcessed: 1,
    }).catch(() => {});
  } catch (e) {}
}

let reportPending = false;
function scheduleReport() {
  if (reportPending) return;
  reportPending = true;
  setTimeout(() => {
    reportPending = false;
    reportStats();
  }, 2000);
}

// ---------------------------------------------------------------------------
// Sweep scheduling — uses requestIdleCallback for non-critical sweeps
// ---------------------------------------------------------------------------
let sweepScheduled = false;
let sweepCount = 0;
let consecutiveEmptySweeps = 0;

function scheduleSweep() {
  if (sweepScheduled) return;
  sweepScheduled = true;
  const doSweep = () => {
    sweepScheduled = false;
    const prevAds = stats.adsRemoved;
    const prevTrackers = stats.trackersRemoved;
    const prevHeavy = stats.heavyElementsRemoved;
    eatAll();
    if (stats.adsRemoved > prevAds || stats.trackersRemoved > prevTrackers || stats.heavyElementsRemoved > prevHeavy) {
      consecutiveEmptySweeps = 0;
    } else {
      if (consecutiveEmptySweeps < 10) consecutiveEmptySweeps++;
    }
  };
  // Back off on clean pages: after 5 empty sweeps use a 15s delay instead
  // of requesting idle time every 2s. Resets to normal as soon as ads appear.
  if (consecutiveEmptySweeps >= 5) {
    setTimeout(doSweep, 15000);
  } else {
    (requestIdleCallback || setTimeout)(doSweep, { timeout: 2000 });
  }
}

// ---------------------------------------------------------------------------
// Utility: querySelectorAll with a limit
// ---------------------------------------------------------------------------
function limitQuery(selector, max = 500) {
  try {
    const all = document.querySelectorAll(selector);
    return all.length > max ? Array.from(all).slice(0, max) : all;
  } catch {
    return [];
  }
}

// ---------------------------------------------------------------------------
// Utility: estimate byte size of an element's outerHTML
// ---------------------------------------------------------------------------
function estimateBytes(el) {
  // length * 2 approximates UTF-16 byte cost without allocating a Blob per element.
  return el.outerHTML ? el.outerHTML.length * 2 : 0;
}

// ---------------------------------------------------------------------------
// Utility: check whether element has user-interactive content
// ---------------------------------------------------------------------------
function hasUserInteraction(el) {
  const tag = el.tagName;
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tag === "BUTTON") return true;
  if (el.contains(document.activeElement)) return true;
  if (el.querySelector("input, textarea, select, button, [contenteditable]")) return true;
  return false;
}

// ---------------------------------------------------------------------------
// Utility: check whether element has substantial visible text
// ---------------------------------------------------------------------------
function visibleTextLength(el) {
  try {
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, {
      acceptNode: function(node) {
        const parent = node.parentElement;
        if (!parent) return NodeFilter.FILTER_REJECT;
        const tag = parent.tagName;
        if (tag === "SCRIPT" || tag === "STYLE" || tag === "NOSCRIPT") return NodeFilter.FILTER_REJECT;
        const style = getComputedStyle(parent);
        if (style.display === "none" || style.visibility === "hidden") return NodeFilter.FILTER_REJECT;
        return NodeFilter.FILTER_ACCEPT;
      },
    });
    let len = 0;
    while (walker.nextNode()) {
      len += (walker.currentNode.textContent || "").trim().length;
    }
    return len;
  } catch {
    return 0;
  }
}

// ---------------------------------------------------------------------------
// Utility: check if an element is hidden (display:none / visibility:hidden)
// ---------------------------------------------------------------------------
function isHidden(el) {
  try {
    const style = getComputedStyle(el);
    return style.display === "none" || style.visibility === "hidden";
  } catch {
    return false;
  }
}

// ---------------------------------------------------------------------------
// Utility: check if element is a fixed full-viewport overlay (likely ad)
// ---------------------------------------------------------------------------
function isViewportOverlay(el) {
  try {
    const style = getComputedStyle(el);
    if (style.position !== "fixed") return false;
    const z = parseInt(style.zIndex);
    if (isNaN(z) || z < 10000) return false;
    const rect = el.getBoundingClientRect();
    const vw = window.innerWidth;
    const vh = window.innerHeight;
    const areaCovered = (rect.width * rect.height) / (vw * vh);
    return areaCovered > 0.6;
  } catch {
    return false;
  }
}

// ---------------------------------------------------------------------------
// Safety: never remove the page's structural elements
// ---------------------------------------------------------------------------
function isProtected(el) {
  if (!el || !el.tagName) return true;
  if (SAFE_TAGS.has(el.tagName)) return true;
  if (el.id === TABBY_FRAME_ID) return true;
  if (el.closest && el.closest(PROTECTED_SELECTORS)) return true;
  if (hasUserInteraction(el)) return true;
  if (visibleTextLength(el) > TEXT_THRESHOLD) return true;
  return false;
}

// ---------------------------------------------------------------------------
// Core removal function
// ---------------------------------------------------------------------------
function safeRemove(el, logLabel) {
  if (!el || !el.parentNode) return false;
  if (isProtected(el)) return false;
  const bytes = estimateBytes(el);
  try {
    el.remove();
    stats.estimatedBytesSaved += bytes;
    return true;
  } catch {
    return false;
  }
}
