/**
 * @file console-forwarder.js
 * @description Intercepts browser console logs and forwards them to the server.
 * Handles circular references, batches requests, and prevents infinite loops.
 */

(function () {
  // CONFIGURATION
  const LOG_ENDPOINT = '/api/logs/browser';
  const BATCH_SIZE = 50;
  const FLUSH_INTERVAL_MS = 2000;
  const MAX_RETRIES = 3;

  // STATE
  let logQueue = [];
  let flushTimer = null;
  let isSending = false;
  let retryCount = 0;

  // CIRCULAR REFERENCE SAFE SERIALIZER
  const safeStringify = (obj) => {
    const seen = new WeakSet();
    return JSON.stringify(obj, (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (seen.has(value)) {
          return '[Circular]';
        }
        seen.add(value);
      }
      // Handle Error objects explicitly as they don't stringify well by default
      if (value instanceof Error) {
        return {
          message: value.message,
          stack: value.stack,
          name: value.name
        };
      }
      return value;
    });
  };

  // LOG QUEUE MANAGER
  const flushQueue = async () => {
    if (logQueue.length === 0 || isSending) return;

    const batch = [...logQueue];
    logQueue = []; // Clear queue immediately to prevent duplicates
    isSending = true;

    try {
      const response = await fetch(LOG_ENDPOINT, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ logs: batch }),
        // critical: prevent this fetch from triggering global error handlers
        // if it fails, to avoid infinite loops
      });

      if (!response.ok) {
        throw new Error(`Server responded with ${response.status}`);
      }
      retryCount = 0;
    } catch (err) {
      // DO NOT log errors here to prevent infinite loops
      // If network fails, put specific logs back or drop them to prevent memory leaks
      if (retryCount < MAX_RETRIES) {
        retryCount++;
        // Re-queue logs at the front
        logQueue = [...batch, ...logQueue];
      }
    } finally {
      isSending = false;
    }
  };

  const scheduleFlush = () => {
    if (flushTimer) return;
    flushTimer = setTimeout(() => {
      flushTimer = null;
      flushQueue();
    }, FLUSH_INTERVAL_MS);
  };

  const pushLog = (level, args) => {
    // timestamp
    const timestamp = new Date().toISOString();

    // safe serialization of arguments
    const serializedArgs = args.map(arg => {
      try {
        if (typeof arg === 'string') return arg;
        return JSON.parse(safeStringify(arg));
      } catch (e) {
        return '[Unserializable]';
      }
    });

    logQueue.push({
      level,
      timestamp,
      message: serializedArgs.join(' '), // Flatten for simple viewing
      data: serializedArgs // Keep structured for deep inspection if backend supports it
    });

    if (logQueue.length >= BATCH_SIZE) {
      if (flushTimer) clearTimeout(flushTimer);
      flushTimer = null;
      flushQueue();
    } else {
      scheduleFlush();
    }
  };

  // INTERCEPTORS
  const originalConsole = {
    log: console.log,
    info: console.info,
    warn: console.warn,
    error: console.error,
    debug: console.debug,
  };

  const methods = ['log', 'info', 'warn', 'error', 'debug'];

  methods.forEach((method) => {
    console[method] = (...args) => {
      // 1. Call original method so developer tools still work
      originalConsole[method].apply(console, args);

      // 2. Push to queue
      try {
        pushLog(method, args);
      } catch (e) {
        // Silent fail - do not use console.error here!
      }
    };
  });

  // GLOBAL ERROR HANDLERS
  window.addEventListener('error', (event) => {
    pushLog('error', [`Uncaught Exception: ${event.message}`, event.filename, event.lineno, event.colno, event.error]);
  });

  window.addEventListener('unhandledrejection', (event) => {
    pushLog('error', ['Unhandled Promise Rejection:', event.reason]);
  });

  // PAGE UNLOAD FLUSH
  // Use sendBeacon for reliable "last gasp" logging
  window.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden' && logQueue.length > 0) {
      const batch = safeStringify({ logs: logQueue });
      // sendBeacon is more reliable on unload than fetch
      if (navigator.sendBeacon) {
        const blob = new Blob([batch], { type: 'application/json' });
        navigator.sendBeacon(LOG_ENDPOINT, blob);
        logQueue = [];
      }
    }
  });

  console.info('[Console Forwarder] Initialized. Monitoring active.');
})();
