/**
 * Grax — Popup controller
 * --------------------------------
 * The popup is deliberately a thin, trusted client of background.js:
 *   - It never edits extension storage directly.
 *   - It sends only validated commands exposed by the background message API.
 *   - It displays real DNR/runtime state; it does not invent blocking totals.
 *   - All animation code is bundled locally to comply with Manifest V3 CSP.
 */

'use strict';

const MESSAGE = Object.freeze({
  getState: 'GRAX_GET_STATE',
  setEnabled: 'GRAX_SET_ENABLED',
  setSiteEnabled: 'GRAX_SET_SITE_ENABLED',
  updateSettings: 'GRAX_UPDATE_SETTINGS',
  checkUpdates: 'GRAX_CHECK_LIST_UPDATES'
});

const ui = Object.freeze({
  app: document.querySelector('#app'),
  brandButton: document.querySelector('#brandButton'),
  settingsButton: document.querySelector('#settingsButton'),
  diagnosticsButton: document.querySelector('#diagnosticsButton'),
  enginePill: document.querySelector('#enginePill'),
  engineText: document.querySelector('#engineText'),
  heroCard: document.querySelector('.hero-card'),
  graxStage: document.querySelector('#graxStage'),
  graxVisual: document.querySelector('#graxVisual'),
  protectionEyebrow: document.querySelector('#protectionEyebrow'),
  statusLabel: document.querySelector('#statusLabel'),
  statusDescription: document.querySelector('#statusDescription'),
  masterSwitch: document.querySelector('#masterSwitch'),
  masterSwitchValue: document.querySelector('#masterSwitchValue'),
  siteHeading: document.querySelector('#siteHeading'),
  siteSubtext: document.querySelector('#siteSubtext'),
  siteMonogram: document.querySelector('#siteMonogram'),
  siteSwitch: document.querySelector('#siteSwitch'),
  engineMetric: document.querySelector('#engineMetric'),
  listMetric: document.querySelector('#listMetric'),
  policyMetric: document.querySelector('#policyMetric'),
  cosmeticButton: document.querySelector('#cosmeticButton'),
  updateButton: document.querySelector('#updateButton'),
  updateDescription: document.querySelector('#updateDescription'),
  updateAction: document.querySelector('#updateAction'),
  toast: document.querySelector('#toast'),
  toastText: document.querySelector('#toastText')
});

const model = {
  activeTab: null,
  hostname: null,
  state: null,
  busy: new Set(),
  toastTimer: null,
  reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches
};

const gsapApi = globalThis.gsap ?? null;

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', () => void initialize(), { once: true });
} else {
  void initialize();
}

async function initialize() {
  if (model.reducedMotion) document.body.classList.add('reduced-motion');

  bindEvents();
  initializeMotion();

  try {
    model.activeTab = await getActiveTab();
    model.hostname = hostnameFromTab(model.activeTab);
    model.state = await sendMessage({
      type: MESSAGE.getState,
      hostname: model.hostname
    });

    render(model.state, { animate: false });
  } catch (error) {
    renderUnavailable(error);
    showToast(publicErrorMessage(error), { error: true });
  } finally {
    ui.app.setAttribute('aria-busy', 'false');
  }
}

function bindEvents() {
  ui.masterSwitch.addEventListener('click', () => void toggleGlobalProtection());
  ui.siteSwitch.addEventListener('click', () => void toggleSiteProtection());
  ui.cosmeticButton.addEventListener('click', () => void toggleCosmeticFiltering());
  ui.updateButton.addEventListener('click', () => void checkFilterUpdates());

  ui.settingsButton.addEventListener('click', openOptionsPage);
  ui.brandButton.addEventListener('click', (event) => {
    event.preventDefault();
    openOptionsPage();
  });

  ui.diagnosticsButton.addEventListener('click', () => {
    const url = chrome.runtime.getURL('options.html#diagnostics');
    void chrome.tabs.create({ url });
  });
}

async function getActiveTab() {
  const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
  return tabs[0] ?? null;
}

function hostnameFromTab(tab) {
  if (!tab || typeof tab.url !== 'string') return null;

  try {
    const url = new URL(tab.url);
    return ['http:', 'https:'].includes(url.protocol) ? url.hostname : null;
  } catch {
    return null;
  }
}

async function sendMessage(message) {
  const response = await chrome.runtime.sendMessage(message);

  if (!response || response.ok !== true) {
    const payload = response?.error;
    const error = new Error(payload?.message || 'Grax did not return a valid response.');
    error.code = payload?.code || 'INVALID_RESPONSE';
    throw error;
  }

  return response.result;
}

async function refreshState({ animate = true } = {}) {
  model.state = await sendMessage({
    type: MESSAGE.getState,
    hostname: model.hostname
  });
  render(model.state, { animate });
}

async function toggleGlobalProtection() {
  if (!model.state || isBusy('global')) return;

  const nextEnabled = !model.state.settings.enabled;
  setBusy('global', true);

  try {
    await sendMessage({ type: MESSAGE.setEnabled, enabled: nextEnabled });
    await refreshState({ animate: true });
    showToast(nextEnabled ? 'Protection activated' : 'Protection paused');
  } catch (error) {
    showToast(publicErrorMessage(error), { error: true });
  } finally {
    setBusy('global', false);
  }
}

async function toggleSiteProtection() {
  if (!model.state?.site || !model.hostname || isBusy('site')) return;

  const nextEnabled = !model.state.site.enabled;
  setBusy('site', true);

  try {
    await sendMessage({
      type: MESSAGE.setSiteEnabled,
      hostname: model.hostname,
      tabId: Number.isInteger(model.activeTab?.id) ? model.activeTab.id : null,
      enabled: nextEnabled
    });
    await refreshState({ animate: true });
    showToast(nextEnabled ? `Protection enabled on ${model.hostname}` : `${model.hostname} added to trusted sites`);
  } catch (error) {
    showToast(publicErrorMessage(error), { error: true });
  } finally {
    setBusy('site', false);
  }
}

async function toggleCosmeticFiltering() {
  if (!model.state || isBusy('cosmetic')) return;

  const nextEnabled = !model.state.settings.cosmeticFiltering;
  setBusy('cosmetic', true);

  try {
    await sendMessage({
      type: MESSAGE.updateSettings,
      patch: { cosmeticFiltering: nextEnabled }
    });
    await refreshState({ animate: true });
    showToast(nextEnabled ? 'Cosmetic cleanup enabled' : 'Cosmetic cleanup disabled');
  } catch (error) {
    showToast(publicErrorMessage(error), { error: true });
  } finally {
    setBusy('cosmetic', false);
  }
}

async function checkFilterUpdates() {
  if (isBusy('updates')) return;

  setBusy('updates', true);
  ui.updateAction.classList.add('is-spinning');
  ui.updateDescription.textContent = 'Checking official list metadata…';

  try {
    const result = await sendMessage({ type: MESSAGE.checkUpdates });
    await refreshState({ animate: false });

    showToast(
      result.updateAvailable
        ? 'A newer filter package is available'
        : 'Filter package is current'
    );
  } catch (error) {
    showToast(publicErrorMessage(error), { error: true });
    renderUpdateDescription(model.state?.diagnostics);
  } finally {
    ui.updateAction.classList.remove('is-spinning');
    setBusy('updates', false);
  }
}

function render(state, { animate }) {
  const globalEnabled = Boolean(state?.settings?.enabled);
  const siteAvailable = Boolean(state?.site?.hostname);
  const siteEnabled = siteAvailable ? Boolean(state.site.enabled) : false;
  const effectiveEnabled = globalEnabled && (!siteAvailable || siteEnabled);
  const cosmeticEnabled = Boolean(state?.settings?.cosmeticFiltering);
  const activeRulesets = Array.isArray(state?.runtime?.dynamicCoverage?.selectedSupplements)
    ? state.runtime.dynamicCoverage.selectedSupplements.length
    : Array.isArray(state?.runtime?.enabledRulesets)
      ? state.runtime.enabledRulesets.length
      : 0;
  const dynamicRuleCount = Number.isFinite(state?.runtime?.dynamicRuleCount)
    ? state.runtime.dynamicRuleCount
    : 0;

  ui.masterSwitch.disabled = false;
  ui.masterSwitch.setAttribute('aria-checked', String(globalEnabled));
  ui.masterSwitchValue.textContent = globalEnabled ? 'Active' : 'Paused';

  ui.heroCard.classList.toggle('is-paused', !effectiveEnabled);
  ui.protectionEyebrow.classList.toggle('is-paused', !effectiveEnabled);
  ui.statusLabel.textContent = effectiveEnabled
    ? 'Protection active'
    : globalEnabled && siteAvailable
      ? 'Trusted website'
      : 'Protection paused';

  ui.statusDescription.textContent = effectiveEnabled
    ? 'Ads, trackers, telemetry, and miners are filtered by Chrome’s native network engine.'
    : globalEnabled && siteAvailable
      ? 'Grax is paused only for this trusted website.'
      : 'Network filtering is currently disabled across the browser.';

  ui.enginePill.classList.toggle('is-offline', !globalEnabled);
  ui.engineText.textContent = globalEnabled ? 'DNR live' : 'DNR paused';

  renderSite(state.site, globalEnabled);

  ui.cosmeticButton.disabled = !globalEnabled;
  ui.cosmeticButton.setAttribute('aria-checked', String(cosmeticEnabled));
  ui.updateButton.disabled = false;

  ui.engineMetric.textContent = globalEnabled ? 'Native' : 'Paused';
  ui.listMetric.textContent = String(activeRulesets);
  ui.policyMetric.textContent = formatCompactNumber(dynamicRuleCount);
  renderUpdateDescription(state.diagnostics);

  if (animate) animateStateChange({ globalEnabled, effectiveEnabled, siteEnabled, cosmeticEnabled });
}

function renderSite(site, globalEnabled) {
  if (!site?.hostname) {
    ui.siteHeading.textContent = restrictedPageLabel(model.activeTab);
    ui.siteSubtext.textContent = 'Chrome does not expose protection controls on this page.';
    ui.siteMonogram.textContent = '—';
    ui.siteSwitch.disabled = true;
    ui.siteSwitch.setAttribute('aria-checked', 'false');
    return;
  }

  ui.siteHeading.textContent = site.hostname;
  ui.siteSubtext.textContent = site.enabled
    ? 'Network and cosmetic protection enabled'
    : 'Trusted site — filtering bypassed';
  ui.siteMonogram.textContent = monogramForHostname(site.hostname);
  ui.siteSwitch.disabled = !globalEnabled;
  ui.siteSwitch.setAttribute('aria-checked', String(Boolean(site.enabled)));
  ui.siteSwitch.setAttribute(
    'aria-label',
    `${site.enabled ? 'Disable' : 'Enable'} protection for ${site.hostname}`
  );
}

function renderUpdateDescription(diagnostics) {
  if (diagnostics?.updateAvailable) {
    ui.updateDescription.textContent = 'New packaged lists are available';
    return;
  }

  const value = diagnostics?.lastUpdateCheckAt;
  if (!value) {
    ui.updateDescription.textContent = 'Check filter package freshness';
    return;
  }

  ui.updateDescription.textContent = `Last checked ${formatRelativeTime(value)}`;
}

function renderUnavailable(error) {
  ui.masterSwitch.disabled = true;
  ui.siteSwitch.disabled = true;
  ui.cosmeticButton.disabled = true;
  ui.updateButton.disabled = true;
  ui.heroCard.classList.add('is-paused');
  ui.protectionEyebrow.classList.add('is-paused');
  ui.statusLabel.textContent = 'Engine unavailable';
  ui.statusDescription.textContent = publicErrorMessage(error);
  ui.masterSwitchValue.textContent = 'Offline';
  ui.enginePill.classList.add('is-offline');
  ui.engineText.textContent = 'Offline';
  ui.siteHeading.textContent = restrictedPageLabel(model.activeTab);
  ui.siteSubtext.textContent = 'Reload the extension to reconnect.';
  ui.engineMetric.textContent = 'Offline';
  ui.listMetric.textContent = '—';
  ui.policyMetric.textContent = '—';
}

function setBusy(scope, busy) {
  if (busy) model.busy.add(scope);
  else model.busy.delete(scope);

  if (scope === 'global') ui.masterSwitch.disabled = busy;
  if (scope === 'site') ui.siteSwitch.disabled = busy || !model.state?.settings?.enabled;
  if (scope === 'cosmetic') ui.cosmeticButton.disabled = busy || !model.state?.settings?.enabled;
  if (scope === 'updates') ui.updateButton.disabled = busy;
}

function isBusy(scope) {
  return model.busy.has(scope);
}

function openOptionsPage() {
  void chrome.runtime.openOptionsPage();
}

function initializeMotion() {
  if (!isGsapReady() || model.reducedMotion) return;

  safelyRunAnimation('entrance', runEntranceAnimation);
  safelyRunAnimation('ambient', startAmbientAnimation);
  safelyRunAnimation('parallax', setupGraxParallax);
}

function isGsapReady() {
  return Boolean(
    gsapApi
    && typeof gsapApi.to === 'function'
    && typeof gsapApi.fromTo === 'function'
    && typeof gsapApi.timeline === 'function'
  );
}

function safelyRunAnimation(name, callback) {
  try {
    callback();
  } catch (error) {
    // Motion is progressive enhancement. A visual failure must never break
    // protection controls or generate a Chrome extension runtime error.
  }
}

function runEntranceAnimation() {
  if (!isGsapReady() || model.reducedMotion) return;

  const revealItems = Array.from(document.querySelectorAll('.reveal-item'));
  const stage = ui.graxStage;
  if (revealItems.length === 0 && !stage) return;

  if (revealItems.length > 0) {
    gsapApi.set(revealItems, { opacity: 0, y: 16 });
  }
  if (stage) {
    gsapApi.set(stage, { opacity: 0, scale: 0.76, rotateY: -14 });
  }

  const timeline = gsapApi.timeline({ defaults: { ease: 'power3.out' } });
  if (revealItems.length > 0) {
    timeline.to(revealItems, {
      opacity: 1,
      y: 0,
      duration: 0.72,
      stagger: 0.07,
      clearProps: 'transform'
    });
  }
  if (stage) {
    timeline.to(stage, {
      opacity: 1,
      scale: 1,
      rotateY: 0,
      duration: 0.9,
      ease: 'back.out(1.45)'
    }, revealItems.length > 0 ? 0.08 : 0);
  }
}

function startAmbientAnimation() {
  if (!isGsapReady() || model.reducedMotion) return;

  const motions = [
    {
      target: document.querySelector('.ambient-a'),
      vars: { x: -18, y: 20, scale: 1.08, duration: 7, repeat: -1, yoyo: true, ease: 'sine.inOut' }
    },
    {
      target: document.querySelector('.ambient-b'),
      vars: { x: 22, y: -16, scale: 0.94, duration: 8.5, repeat: -1, yoyo: true, ease: 'sine.inOut' }
    },
    {
      target: document.querySelector('.ambient-c'),
      vars: { x: -10, y: 12, scale: 1.1, duration: 9.5, repeat: -1, yoyo: true, ease: 'sine.inOut' }
    },
    {
      target: document.querySelector('.surface-refraction'),
      vars: { x: -14, opacity: 0.72, duration: 6.5, repeat: -1, yoyo: true, ease: 'sine.inOut' }
    }
  ];

  for (const motion of motions) {
    if (motion.target instanceof Element) {
      gsapApi.to(motion.target, motion.vars);
    }
  }
}

function setupGraxParallax() {
  if (!isGsapReady() || model.reducedMotion || !ui.graxVisual || !ui.heroCard) return;
  if (typeof gsapApi.quickTo !== 'function') return;

  const moveX = gsapApi.quickTo(ui.graxVisual, 'rotationY', { duration: 0.45, ease: 'power3.out' });
  const moveY = gsapApi.quickTo(ui.graxVisual, 'rotationX', { duration: 0.45, ease: 'power3.out' });
  const translateX = gsapApi.quickTo(ui.graxVisual, 'x', { duration: 0.45, ease: 'power3.out' });
  const translateY = gsapApi.quickTo(ui.graxVisual, 'y', { duration: 0.45, ease: 'power3.out' });

  ui.heroCard.addEventListener('pointermove', (event) => {
    const bounds = ui.heroCard.getBoundingClientRect();
    if (bounds.width <= 0 || bounds.height <= 0) return;

    const normalizedX = (event.clientX - bounds.left) / bounds.width - 0.5;
    const normalizedY = (event.clientY - bounds.top) / bounds.height - 0.5;
    moveX(normalizedX * 13);
    moveY(normalizedY * -11);
    translateX(normalizedX * 4);
    translateY(normalizedY * 4);
  });

  ui.heroCard.addEventListener('pointerleave', () => {
    moveX(0);
    moveY(0);
    translateX(0);
    translateY(0);
  });
}

function animateStateChange({ globalEnabled, effectiveEnabled, siteEnabled, cosmeticEnabled }) {
  if (!isGsapReady() || model.reducedMotion) return;

  const masterThumb = ui.masterSwitch.querySelector('.master-switch-thumb');
  const masterEnergy = ui.masterSwitch.querySelector('.master-switch-energy');
  const siteThumb = ui.siteSwitch.querySelector('.compact-thumb');
  const cosmeticThumb = ui.cosmeticButton.querySelector('.mini-switch span');

  safelyRunAnimation('state change', () => {
    if (masterThumb) {
      gsapApi.fromTo(masterThumb,
        { scale: 0.76, rotate: globalEnabled ? -18 : 18 },
        { scale: 1, rotate: 0, duration: 0.52, ease: 'back.out(2.2)' }
      );
    }

    if (masterEnergy) {
      gsapApi.fromTo(masterEnergy,
        { xPercent: globalEnabled ? -90 : 60, opacity: 0.2 },
        { xPercent: globalEnabled ? 65 : -80, opacity: globalEnabled ? 1 : 0.15, duration: 0.7, ease: 'power2.out' }
      );
    }

    if (ui.graxVisual) {
      gsapApi.fromTo(ui.graxVisual,
        { scale: effectiveEnabled ? 0.82 : 1.08 },
        { scale: 1, duration: 0.64, ease: 'elastic.out(1, 0.55)' }
      );
    }

  if (siteThumb && model.state?.site) {
    gsapApi.fromTo(siteThumb,
      { scale: 0.7 },
      { scale: 1, duration: 0.42, ease: 'back.out(2)' }
    );
  }

  if (cosmeticThumb) {
    gsapApi.fromTo(cosmeticThumb,
      { scale: 0.7, rotate: cosmeticEnabled ? -25 : 25 },
      { scale: 1, rotate: 0, duration: 0.42, ease: 'back.out(2)' }
    );
  }

    const statusTargets = [ui.statusLabel, ui.statusDescription].filter(Boolean);
    if (statusTargets.length > 0) {
      gsapApi.fromTo(statusTargets,
        { opacity: 0.35, y: 4 },
        { opacity: 1, y: 0, duration: 0.42, stagger: 0.04, ease: 'power2.out' }
      );
    }
  });

  void siteEnabled; // Explicitly retained for future site-state motion variants.
}

function showToast(message, { error = false } = {}) {
  ui.toastText.textContent = message;
  ui.toast.classList.toggle('is-error', error);

  if (model.toastTimer) clearTimeout(model.toastTimer);

  if (!isGsapReady() || model.reducedMotion) {
    ui.toast.style.opacity = '1';
    ui.toast.style.transform = 'none';
    model.toastTimer = setTimeout(() => {
      ui.toast.style.opacity = '0';
    }, 2500);
    return;
  }

  gsapApi.killTweensOf(ui.toast);
  gsapApi.to(ui.toast, {
    opacity: 1,
    y: 0,
    scale: 1,
    duration: 0.38,
    ease: 'back.out(1.8)'
  });

  model.toastTimer = setTimeout(() => {
    gsapApi.to(ui.toast, {
      opacity: 0,
      y: 12,
      scale: 0.97,
      duration: 0.28,
      ease: 'power2.in'
    });
  }, 2500);
}

function monogramForHostname(hostname) {
  const normalized = hostname.replace(/^www\./, '');
  const meaningful = normalized.split('.').filter(Boolean);
  const label = meaningful.length > 1 ? meaningful.at(-2) : meaningful[0];
  return (label?.slice(0, 2) || '—').toUpperCase();
}

function restrictedPageLabel(tab) {
  const title = typeof tab?.title === 'string' ? tab.title.trim() : '';
  return title || 'Protected browser page';
}

function formatCompactNumber(value) {
  try {
    return new Intl.NumberFormat('en', {
      notation: 'compact',
      maximumFractionDigits: 1
    }).format(Math.max(0, value));
  } catch {
    return String(value);
  }
}

function formatRelativeTime(isoDate) {
  const timestamp = Date.parse(isoDate);
  if (!Number.isFinite(timestamp)) return 'recently';

  const elapsedSeconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
  if (elapsedSeconds < 60) return 'just now';
  if (elapsedSeconds < 3600) return `${Math.floor(elapsedSeconds / 60)}m ago`;
  if (elapsedSeconds < 86400) return `${Math.floor(elapsedSeconds / 3600)}h ago`;
  return `${Math.floor(elapsedSeconds / 86400)}d ago`;
}

function publicErrorMessage(error) {
  if (error?.code === 'UPSTREAM_CHECK_FAILED') return 'The official filter source could not be reached.';
  if (error?.code === 'NETWORK_TIMEOUT') return 'The filter check timed out.';
  if (error?.code === 'INVALID_HOSTNAME') return 'This page does not expose a controllable hostname.';
  return error?.message || 'Grax encountered an unexpected error.';
}
