/**
 * visibility-manager.js — Advanced visibility management with scroll prediction
 * 
 * Uses IntersectionObserver for accurate page visibility detection
 * Implements scroll prediction for smooth pre-loading
 * Manages buffer zones intelligently
 */

class VisibilityManager {
  constructor(container, pageElements, totalPages) {
    this.container = container;
    this.pageElements = pageElements;
    this.totalPages = totalPages;
    
    // Visibility state
    this.visiblePages = new Set();
    this.bufferPages = new Set();
    this.observer = null;
    this.bufferSize = 2; // Minimum pages to buffer
    this.scrollHistory = [];
    this.maxScrollHistory = 10;
    
    // Scroll prediction
    this.scrollVelocity = 0;
    this.scrollDirection = 0; // 1 for down, -1 for up, 0 for stationary
    this.lastScrollTime = 0;
    this.lastScrollPosition = 0;
    
    // Performance tracking
    this.visibilityChanges = 0;
    this.lastVisibilityTime = 0;
    
    // Setup observers
    this.setupIntersectionObserver();
    this.setupScrollMonitoring();
  }

  setupIntersectionObserver() {
    this.observer = new IntersectionObserver(
      (entries) => this.handleIntersectionEntries(entries),
      {
        root: this.container,
        rootMargin: '100% 0px 100% 0px', // Large margin for extensive buffering
        threshold: [0, 0.25, 0.5, 0.75, 1.0] // Multiple thresholds for smooth transitions
      }
    );
    
    // Observe all page elements
    this.pageElements.forEach(element => {
      this.observer.observe(element);
    });
  }

  setupScrollMonitoring() {
    // Track scroll events for velocity calculation
    this.container.addEventListener('scroll', (event) => {
      this.trackScrollEvent(event);
    }, { passive: true });
    
    // Periodic scroll prediction
    setInterval(() => {
      this.updateScrollPrediction();
    }, 100);
  }

  trackScrollEvent(event) {
    const currentTime = performance.now();
    const currentPosition = this.container.scrollTop;
    
    // Calculate velocity if we have previous data
    if (this.lastScrollPosition !== 0 && this.lastScrollTime !== 0) {
      const timeDelta = currentTime - this.lastScrollTime;
      if (timeDelta > 0) {
        this.scrollVelocity = (currentPosition - this.lastScrollPosition) / timeDelta;
        this.scrollDirection = this.scrollVelocity > 0 ? 1 : (this.scrollVelocity < 0 ? -1 : 0);
      }
    }
    
    // Store scroll history
    this.scrollHistory.push({
      position: currentPosition,
      time: currentTime,
      velocity: this.scrollVelocity
    });
    
    if (this.scrollHistory.length > this.maxScrollHistory) {
      this.scrollHistory.shift();
    }
    
    this.lastScrollPosition = currentPosition;
    this.lastScrollTime = currentTime;
  }

  updateScrollPrediction() {
    if (this.scrollHistory.length < 3) return;
    
    // Calculate average velocity from recent history
    const recentHistory = this.scrollHistory.slice(-5); // Last 5 entries
    if (recentHistory.length < 2) return;
    
    const totalDelta = recentHistory[recentHistory.length - 1].position -
                       recentHistory[0].position;
    const totalTime = recentHistory[recentHistory.length - 1].time -
                      recentHistory[0].time;
    
    if (totalTime > 0) {
      this.scrollVelocity = totalDelta / totalTime;
      this.scrollDirection = this.scrollVelocity > 5 ? 1 : 
                            (this.scrollVelocity < -5 ? -1 : 0);
    }
  }

  handleIntersectionEntries(entries) {
    const newVisiblePages = new Set();
    const newBufferPages = new Set();
    
    // Process intersection entries
    entries.forEach(entry => {
      const pageNum = parseInt(entry.target.dataset.page);
      if (isNaN(pageNum)) return;
      
      if (entry.isIntersecting) {
        // Page is visible or in buffer zone
        if (entry.intersectionRatio >= 0.5) {
          newVisiblePages.add(pageNum);
        } else {
          newBufferPages.add(pageNum);
        }
      }
    });
    
    // Add intelligent buffering based on scroll prediction
    this.addPredictiveBuffering(newVisiblePages, newBufferPages);
    
    // Update state if changed
    if (!this.setsEqual(this.visiblePages, newVisiblePages) ||
        !this.setsEqual(this.bufferPages, newBufferPages)) {
      
      this.visiblePages = newVisiblePages;
      this.bufferPages = newBufferPages;
      this.visibilityChanges++;
      
      // Notify about visibility changes
      this.notifyVisibilityChange();
    }
  }

  addPredictiveBuffering(visiblePages, bufferPages) {
    if (visiblePages.size === 0) return;
    
    const visibleArray = Array.from(visiblePages).sort((a, b) => a - b);
    const firstVisible = visibleArray[0];
    const lastVisible = visibleArray[visibleArray.length - 1];
    
    // Add adjacent pages (minimum buffer)
    for (let i = 1; i <= this.bufferSize; i++) {
      if (firstVisible - i >= 1) bufferPages.add(firstVisible - i);
      if (lastVisible + i <= this.totalPages) bufferPages.add(lastVisible + i);
    }
    
    // Add scroll prediction-based buffering
    if (Math.abs(this.scrollVelocity) > 10) { // Significant scrolling
      const predictionDistance = Math.min(5, Math.floor(Math.abs(this.scrollVelocity) / 2));
      
      if (this.scrollDirection === 1) { // Scrolling down
        // Add more pages below
        for (let i = 1; i <= predictionDistance; i++) {
          const nextPage = lastVisible + this.bufferSize + i;
          if (nextPage <= this.totalPages) {
            bufferPages.add(nextPage);
          }
        }
      } else if (this.scrollDirection === -1) { // Scrolling up
        // Add more pages above
        for (let i = 1; i <= predictionDistance; i++) {
          const prevPage = firstVisible - this.bufferSize - i;
          if (prevPage >= 1) {
            bufferPages.add(prevPage);
          }
        }
      }
    }
  }

  setsEqual(a, b) {
    if (a.size !== b.size) return false;
    return Array.from(a).every(item => b.has(item));
  }

  notifyVisibilityChange() {
    // Throttle visibility notifications to prevent excessive rendering
    const now = performance.now();
    if (now - this.lastVisibilityTime < 50) return; // Max 20 updates per second
    
    this.lastVisibilityTime = now;
    
    // Notify the render system
    if (window.renderSystem) {
      window.renderSystem.setVisibility(
        Array.from(this.visiblePages),
        Array.from(this.bufferPages)
      );
    }
  }

  getScrollPrediction() {
    return {
      velocity: this.scrollVelocity,
      direction: this.scrollDirection,
      confidence: Math.min(1.0, Math.abs(this.scrollVelocity) / 20)
    };
  }

  cleanup() {
    if (this.observer) {
      this.observer.disconnect();
    }
    
    this.visiblePages.clear();
    this.bufferPages.clear();
    this.scrollHistory = [];
  }

  getVisibilityStats() {
    return {
      visiblePages: Array.from(this.visiblePages).sort((a, b) => a - b),
      bufferPages: Array.from(this.bufferPages).sort((a, b) => a - b),
      visibilityChanges: this.visibilityChanges,
      scrollVelocity: this.scrollVelocity,
      scrollDirection: this.scrollDirection
    };
  }
}

// Export for ES6 modules
export { VisibilityManager };