/**
 * Frontend Performance Optimization Utilities
 * Provides utilities for optimizing frontend load time and performance
 */

/**
 * Lazy load images with IntersectionObserver
 * More efficient than scroll-based lazy loading
 */
class OptimizedLazyLoader {
  constructor(options = {}) {
    this.options = {
      rootMargin: options.rootMargin || "50px",
      threshold: options.threshold || 0.01,
      loadingClass: options.loadingClass || "lazy-loading",
      loadedClass: options.loadedClass || "lazy-loaded",
      errorClass: options.errorClass || "lazy-error",
    };

    this.observer = null;
    this.init();
  }

  init() {
    if ("IntersectionObserver" in window) {
      this.observer = new IntersectionObserver(
        this.handleIntersection.bind(this),
        {
          rootMargin: this.options.rootMargin,
          threshold: this.options.threshold,
        }
      );

      this.observeImages();
    } else {
      // Fallback for older browsers
      this.loadAllImages();
    }
  }

  handleIntersection(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        this.loadImage(entry.target);
        this.observer.unobserve(entry.target);
      }
    });
  }

  loadImage(img) {
    const src = img.dataset.src;
    const srcset = img.dataset.srcset;

    if (!src) return;

    img.classList.add(this.options.loadingClass);

    // Preload the image
    const tempImg = new Image();

    tempImg.onload = () => {
      img.src = src;
      if (srcset) img.srcset = srcset;
      img.classList.remove(this.options.loadingClass);
      img.classList.add(this.options.loadedClass);
      img.removeAttribute("data-src");
      img.removeAttribute("data-srcset");
    };

    tempImg.onerror = () => {
      img.classList.remove(this.options.loadingClass);
      img.classList.add(this.options.errorClass);
      console.warn("Failed to load image:", src);
    };

    tempImg.src = src;
    if (srcset) tempImg.srcset = srcset;
  }

  observeImages() {
    const images = document.querySelectorAll("img[data-src]");
    images.forEach((img) => {
      this.observer.observe(img);
    });
  }

  loadAllImages() {
    const images = document.querySelectorAll("img[data-src]");
    images.forEach((img) => this.loadImage(img));
  }

  destroy() {
    if (this.observer) {
      this.observer.disconnect();
    }
  }
}

/**
 * Resource Hints Manager
 * Adds DNS prefetch, preconnect, and preload hints
 */
class ResourceHints {
  static addDNSPrefetch(domains) {
    domains.forEach((domain) => {
      if (
        !document.querySelector(`link[rel="dns-prefetch"][href="${domain}"]`)
      ) {
        const link = document.createElement("link");
        link.rel = "dns-prefetch";
        link.href = domain;
        document.head.appendChild(link);
      }
    });
  }

  static addPreconnect(urls) {
    urls.forEach((url) => {
      if (!document.querySelector(`link[rel="preconnect"][href="${url}"]`)) {
        const link = document.createElement("link");
        link.rel = "preconnect";
        link.href = url;
        link.crossOrigin = "anonymous";
        document.head.appendChild(link);
      }
    });
  }

  static preloadImage(src, as = "image") {
    if (!document.querySelector(`link[rel="preload"][href="${src}"]`)) {
      const link = document.createElement("link");
      link.rel = "preload";
      link.as = as;
      link.href = src;
      document.head.appendChild(link);
    }
  }
}

/**
 * Debounce with leading edge option
 * Better for performance-critical events
 */
function optimizedDebounce(func, wait, options = {}) {
  let timeout;
  let lastCallTime = 0;
  const { leading = false, maxWait = 0 } = options;

  return function debounced(...args) {
    const now = Date.now();
    const timeSinceLastCall = now - lastCallTime;

    const callNow = leading && !timeout;
    const shouldInvokeFromMaxWait = maxWait > 0 && timeSinceLastCall >= maxWait;

    clearTimeout(timeout);

    if (callNow) {
      lastCallTime = now;
      return func.apply(this, args);
    }

    if (shouldInvokeFromMaxWait) {
      lastCallTime = now;
      return func.apply(this, args);
    }

    timeout = setTimeout(() => {
      lastCallTime = Date.now();
      func.apply(this, args);
    }, wait);
  };
}

/**
 * Request Animation Frame Throttle
 * Ensures maximum one execution per frame
 */
function rafThrottle(func) {
  let rafId = null;
  let lastArgs = null;

  return function throttled(...args) {
    lastArgs = args;

    if (rafId === null) {
      rafId = requestAnimationFrame(() => {
        func.apply(this, lastArgs);
        rafId = null;
        lastArgs = null;
      });
    }
  };
}

/**
 * Memory-efficient event delegation
 */
class EventDelegator {
  constructor(rootElement = document) {
    this.root = rootElement;
    this.handlers = new Map();
  }

  on(eventType, selector, handler) {
    if (!this.handlers.has(eventType)) {
      this.handlers.set(eventType, []);
      this.root.addEventListener(
        eventType,
        this.handleEvent.bind(this, eventType)
      );
    }

    this.handlers.get(eventType).push({ selector, handler });
  }

  handleEvent(eventType, event) {
    const handlers = this.handlers.get(eventType);
    if (!handlers) return;

    const target = event.target;

    handlers.forEach(({ selector, handler }) => {
      const element = target.closest(selector);
      if (element) {
        handler.call(element, event);
      }
    });
  }

  off(eventType, selector) {
    const handlers = this.handlers.get(eventType);
    if (!handlers) return;

    const filtered = handlers.filter((h) => h.selector !== selector);
    if (filtered.length === 0) {
      this.root.removeEventListener(eventType, this.handleEvent);
      this.handlers.delete(eventType);
    } else {
      this.handlers.set(eventType, filtered);
    }
  }
}

/**
 * Batch DOM updates to minimize reflows
 */
class DOMBatcher {
  constructor() {
    this.reads = [];
    this.writes = [];
    this.scheduled = false;
  }

  read(fn) {
    this.reads.push(fn);
    this.schedule();
  }

  write(fn) {
    this.writes.push(fn);
    this.schedule();
  }

  schedule() {
    if (this.scheduled) return;
    this.scheduled = true;

    requestAnimationFrame(() => {
      // Execute all reads first
      this.reads.forEach((fn) => fn());
      this.reads = [];

      // Then execute all writes
      this.writes.forEach((fn) => fn());
      this.writes = [];

      this.scheduled = false;
    });
  }
}

// Export for use
if (typeof module !== "undefined" && module.exports) {
  module.exports = {
    OptimizedLazyLoader,
    ResourceHints,
    optimizedDebounce,
    rafThrottle,
    EventDelegator,
    DOMBatcher,
  };
} else if (typeof window !== "undefined") {
  window.PerformanceUtils = {
    OptimizedLazyLoader,
    ResourceHints,
    optimizedDebounce,
    rafThrottle,
    EventDelegator,
    DOMBatcher,
  };
}
