'use client';

import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { ROUTES } from '@theme/routes';

const DASHBOARD_URL = process.env.NEXT_PUBLIC_PZ_DASHBOARD_URL;
const SERVICE = process.env.NEXT_PUBLIC_PZ_PROJECT_NAME || 'projectzeronext';
const SAMPLE_RATE = Number(process.env.NEXT_PUBLIC_PZ_DATALAYER_SAMPLE ?? '1');

// Sampling never drops these — the dashboard's silence alarms watch them.
const CRITICAL_EVENTS = ['purchase', 'begin_checkout'];

// Only these payload keys are forwarded; dataLayer objects can carry PII
// (email, phone) that must never leave the browser.
const PAYLOAD_WHITELIST = ['ecommerce', 'currency', 'value', 'items'];

const FLUSH_INTERVAL_MS = 10_000;
const MAX_BATCH = 200;

// GA4 ecommerce events imply the page type more reliably than the pretty
// URL (which only the backend can resolve to product/category).
const EVENT_PAGE_TYPES: Record<string, string> = {
  view_item: 'pdp',
  select_item: 'plp',
  view_item_list: 'plp',
  view_cart: 'basket',
  begin_checkout: 'checkout',
  add_shipping_info: 'checkout',
  add_payment_info: 'checkout',
  purchase: 'order-success'
};

type QueuedEvent = {
  event: string;
  pageType: string;
  pageUrl: string;
  sessionId: string;
  timestamp: number;
  payload?: Record<string, unknown>;
};

const queue: QueuedEvent[] = [];

// The observer remounts with the app template on every route change and
// replays the persistent dataLayer array; this keeps replays idempotent.
const captured = new WeakSet<object>();

const resolvePageTypeFromPath = (pathname: string): string => {
  if (pathname === ROUTES.HOME) return 'home';
  if (pathname.startsWith(ROUTES.LIST)) return 'plp';
  if (pathname.startsWith(ROUTES.BASKET)) return 'basket';
  if (pathname.startsWith(ROUTES.CHECKOUT_COMPLETED)) return 'order-success';
  if (pathname.startsWith(ROUTES.CHECKOUT)) return 'checkout';
  if (pathname.startsWith('/account') || pathname.startsWith('/users')) {
    return 'account';
  }

  return 'other';
};

const getSessionId = () => {
  try {
    let id = sessionStorage.getItem('pz.dl.sid');

    if (!id) {
      id = crypto.randomUUID();
      sessionStorage.setItem('pz.dl.sid', id);
    }

    return id;
  } catch {
    return 'no-session';
  }
};

const flush = () => {
  if (!queue.length || !DASHBOARD_URL) return;

  const events = queue.splice(0, MAX_BATCH);
  const body = JSON.stringify({ service: SERVICE, events });

  // String body → text/plain → no CORS preflight; survives page unload.
  if (!navigator.sendBeacon(`${DASHBOARD_URL}/api/datalayer`, body)) {
    fetch(`${DASHBOARD_URL}/api/datalayer`, {
      method: 'POST',
      body,
      keepalive: true
    }).catch(() => {
      // Observability must never break the storefront.
    });
  }
};

export const DataLayerObserver = () => {
  const pathname = usePathname();

  useEffect(() => {
    if (!DASHBOARD_URL) return;

    const sessionId = getSessionId();
    // Deterministic per-session sampling: same session, same decision.
    const sampled =
      sessionId === 'no-session' ||
      (parseInt(sessionId.slice(0, 8), 16) & 0xffff) / 0xffff < SAMPLE_RATE;

    const capture = (item: unknown) => {
      if (!item || typeof item !== 'object') return;
      if (captured.has(item)) return;
      captured.add(item);
      const record = item as Record<string, unknown>;
      if (typeof record.event !== 'string' || !record.event) return;
      // GTM internals (gtm.js, gtm.dom, gtm.load...) are noise, not tags.
      if (record.event.startsWith('gtm.')) return;
      if (!sampled && !CRITICAL_EVENTS.includes(record.event)) return;

      const payload: Record<string, unknown> = {};
      PAYLOAD_WHITELIST.forEach((key) => {
        if (record[key] !== undefined) payload[key] = record[key];
      });

      queue.push({
        event: record.event,
        pageType:
          (typeof record.page_type === 'string' && record.page_type) ||
          EVENT_PAGE_TYPES[record.event] ||
          resolvePageTypeFromPath(window.location.pathname),
        pageUrl: window.location.href,
        sessionId,
        timestamp: Date.now(),
        payload: Object.keys(payload).length ? payload : undefined
      });
    };

    window.dataLayer = window.dataLayer || [];
    // Replay events pushed before the observer mounted (early page_views).
    window.dataLayer.forEach(capture);

    const original = window.dataLayer.push.bind(window.dataLayer);
    window.dataLayer.push = (...items: unknown[]) => {
      items.forEach(capture);
      return original(...items);
    };

    const timer = setInterval(flush, FLUSH_INTERVAL_MS);
    const onVisibilityChange = () => {
      if (document.visibilityState === 'hidden') flush();
    };

    document.addEventListener('visibilitychange', onVisibilityChange);
    window.addEventListener('pagehide', flush);

    return () => {
      clearInterval(timer);
      document.removeEventListener('visibilitychange', onVisibilityChange);
      window.removeEventListener('pagehide', flush);
      window.dataLayer.push = original;
      flush();
    };
    // Mount once; route changes are read from window.location at capture time.
  }, []);

  // Route change = fresh page view context; flush the previous page's batch.
  useEffect(() => {
    flush();
  }, [pathname]);

  return null;
};
