/**
 * FilterQueueManager - Request coordination and caching for filter operations
 * 
 * Handles:
 * - Debouncing with configurable delay
 * - Request batching for multiple parameter changes
 * - LRU cache with hash-based key generation
 * - Cache statistics tracking (hit rate, memory usage)
 * - Adaptive debounce delay based on device performance
 * 
 * Requirements: 1.2, 3.2, 4.3
 */
class FilterQueueManager {
  /**
   * @param {number} cacheSize - Maximum number of cached results (default: 20)
   * @param {number} initialDebounceDelay - Initial debounce delay in ms (default: 50)
   * @param {string} workerPath - Path to the filter worker file (default: 'filter-worker.js')
   */
  constructor(cacheSize = 20, initialDebounceDelay = 50, workerPath = 'filter-worker.js') {
    this.cacheSize = cacheSize;
    this.debounceDelay = initialDebounceDelay;
    this.workerPath = workerPath;
    
    // LRU Cache: Map of hash -> { imageData, timestamp, hitCount, memorySize }
    this.cache = new Map();
    
    // Cache statistics
    this.stats = {
      hits: 0,
      misses: 0,
      totalRequests: 0,
      evictions: 0,
      totalMemoryUsed: 0
    };
    
    // Debouncing state
    this.debounceTimer = null;
    this.pendingRequest = null;
    
    // Request queue for batching
    this.requestQueue = [];
    
    // Web Worker state
    this.worker = null;
    this.workerReady = false;
    this.workerInitializing = false;
    this.workerError = null;
    this.pendingWorkerRequests = new Map(); // Map of request ID -> { resolve, reject, timestamp }
    this.requestIdCounter = 0;
    this.workerRestartAttempts = 0;
    this.maxWorkerRestartAttempts = 3;
    this.workerRestartCooldown = 5000; // 5 seconds
    this.lastWorkerRestartTime = 0;
    
    // Device performance detection for adaptive delays
    this.deviceLevel = this._detectDevicePerformance();
    this._applyDeviceSpecificSettings();
    
    // Initialize Web Worker
    this._initializeWorker();
    
    console.log(`[FilterQueueManager] Initialized with cache size: ${cacheSize}, debounce: ${this.debounceDelay}ms, device: ${this.deviceLevel}`);
  }

  /**
   * Initialize Web Worker for parallel processing
   * Requirement 3.1: Execute filter operations in a Web Worker
   * @private
   */
  _initializeWorker() {
    try {
      if (this.workerInitializing || this.workerReady) {
        return;
      }
      
      this.workerInitializing = true;
      this.workerError = null;
      
      // Create worker
      this.worker = new Worker(this.workerPath);
      
      // Set up message handler
      this.worker.onmessage = (event) => {
        this._handleWorkerMessage(event.data);
      };
      
      // Set up error handler
      this.worker.onerror = (error) => {
        this._handleWorkerError(error);
      };
      
      // Send initialization message
      this.worker.postMessage({
        type: 'INITIALIZE'
      });
      
      console.log('[FilterQueueManager] Worker initialization started');
      
    } catch (error) {
      console.error('[FilterQueueManager] Failed to create worker:', error);
      this.workerError = error;
      this.workerInitializing = false;
      this.workerReady = false;
    }
  }

  /**
   * Handle messages from Web Worker
   * @private
   */
  _handleWorkerMessage(message) {
    const { type, id, imageData, processingTime, error } = message;
    
    switch (type) {
      case 'INITIALIZED':
        this.workerReady = true;
        this.workerInitializing = false;
        this.workerRestartAttempts = 0;
        console.log('[FilterQueueManager] Worker initialized and ready');
        break;
        
      case 'FILTER_COMPLETE':
        this._handleFilterComplete(id, imageData, processingTime);
        break;
        
      case 'ERROR':
        this._handleWorkerProcessingError(id, error);
        break;
        
      default:
        console.warn('[FilterQueueManager] Unknown worker message type:', type);
    }
  }

  /**
   * Handle filter completion from worker
   * @private
   */
  _handleFilterComplete(id, imageData, processingTime) {
    const request = this.pendingWorkerRequests.get(id);
    
    if (!request) {
      console.warn('[FilterQueueManager] Received result for unknown request:', id);
      return;
    }
    
    this.pendingWorkerRequests.delete(id);
    
    // Resolve the promise
    if (request.resolve) {
      request.resolve({ imageData, processingTime });
    }
    
    console.log(`[FilterQueueManager] Worker completed in ${processingTime.toFixed(2)}ms`);
  }

  /**
   * Handle processing error from worker
   * @private
   */
  _handleWorkerProcessingError(id, error) {
    const request = this.pendingWorkerRequests.get(id);
    
    if (request) {
      this.pendingWorkerRequests.delete(id);
      
      if (request.reject) {
        request.reject(new Error(`Worker processing error: ${error}`));
      }
    }
    
    console.error('[FilterQueueManager] Worker processing error:', error);
  }

  /**
   * Handle worker errors
   * @private
   */
  _handleWorkerError(error) {
    console.error('[FilterQueueManager] Worker error:', error);
    
    this.workerError = error;
    this.workerReady = false;
    this.workerInitializing = false;
    
    // Reject all pending requests
    for (const [id, request] of this.pendingWorkerRequests.entries()) {
      if (request.reject) {
        request.reject(new Error('Worker crashed'));
      }
    }
    this.pendingWorkerRequests.clear();
    
    // Attempt to restart worker after cooldown
    this._attemptWorkerRestart();
  }

  /**
   * Attempt to restart the worker after a failure
   * @private
   */
  _attemptWorkerRestart() {
    const now = Date.now();
    const timeSinceLastRestart = now - this.lastWorkerRestartTime;
    
    if (this.workerRestartAttempts >= this.maxWorkerRestartAttempts) {
      console.error('[FilterQueueManager] Max worker restart attempts reached. Worker disabled.');
      return;
    }
    
    if (timeSinceLastRestart < this.workerRestartCooldown) {
      console.log('[FilterQueueManager] Worker restart cooldown active. Waiting...');
      return;
    }
    
    this.workerRestartAttempts++;
    this.lastWorkerRestartTime = now;
    
    console.log(`[FilterQueueManager] Attempting worker restart (${this.workerRestartAttempts}/${this.maxWorkerRestartAttempts})`);
    
    setTimeout(() => {
      if (this.worker) {
        this.worker.terminate();
        this.worker = null;
      }
      this._initializeWorker();
    }, this.workerRestartCooldown);
  }

  /**
   * Send filter request to Web Worker
   * @private
   */
  _sendToWorker(imageData, adjustments, previewMode = true) {
    return new Promise((resolve, reject) => {
      if (!this.workerReady) {
        reject(new Error('Worker not ready'));
        return;
      }
      
      // Generate unique request ID
      const requestId = `req_${this.requestIdCounter++}_${Date.now()}`;
      
      // Store request promise handlers
      this.pendingWorkerRequests.set(requestId, {
        resolve,
        reject,
        timestamp: Date.now()
      });
      
      // Send message to worker
      try {
        this.worker.postMessage({
          type: 'PROCESS_FILTERS',
          data: {
            id: requestId,
            imageData: imageData,
            adjustments: adjustments,
            previewMode: previewMode
          }
        });
      } catch (error) {
        this.pendingWorkerRequests.delete(requestId);
        reject(error);
      }
    });
  }

  /**
   * Check if worker is available
   */
  isWorkerAvailable() {
    return this.workerReady && !this.workerError;
  }

  /**
   * Get worker status
   */
  getWorkerStatus() {
    return {
      ready: this.workerReady,
      initializing: this.workerInitializing,
      error: this.workerError ? this.workerError.message : null,
      pendingRequests: this.pendingWorkerRequests.size,
      restartAttempts: this.workerRestartAttempts
    };
  }

  /**
   * Terminate the worker
   */
  terminateWorker() {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
      this.workerReady = false;
      this.workerInitializing = false;
      
      // Reject all pending requests
      for (const [id, request] of this.pendingWorkerRequests.entries()) {
        if (request.reject) {
          request.reject(new Error('Worker terminated'));
        }
      }
      this.pendingWorkerRequests.clear();
      
      console.log('[FilterQueueManager] Worker terminated');
    }
  }

  /**
   * Detect device performance level
   * Requirement 4.3: Adaptive behavior based on device performance
   * @private
   */
  _detectDevicePerformance() {
    const cores = navigator.hardwareConcurrency || 2;
    const memory = navigator.deviceMemory || 4;
    
    if (cores >= 4 && memory >= 8) {
      return 'high';
    } else if (cores >= 2 && memory >= 4) {
      return 'medium';
    } else {
      return 'low';
    }
  }

  /**
   * Apply device-specific settings
   * Requirement 4.3: Adaptive debounce delay based on device
   * @private
   */
  _applyDeviceSpecificSettings() {
    const deviceSettings = {
      high: { debounceDelay: 50, cacheSize: 20 },
      medium: { debounceDelay: 100, cacheSize: 10 },
      low: { debounceDelay: 200, cacheSize: 5 }
    };
    
    const settings = deviceSettings[this.deviceLevel];
    this.debounceDelay = settings.debounceDelay;
    this.cacheSize = settings.cacheSize;
  }

  /**
   * Generate hash key for adjustment parameters
   * Used for cache key generation
   * @private
   */
  _generateHash(adjustments) {
    // Create a stable string representation of adjustments
    const keys = Object.keys(adjustments).sort();
    const values = keys.map(key => `${key}:${adjustments[key]}`);
    const hashString = values.join('|');
    
    // Simple hash function (djb2)
    let hash = 5381;
    for (let i = 0; i < hashString.length; i++) {
      hash = ((hash << 5) + hash) + hashString.charCodeAt(i);
    }
    
    return hash.toString(36);
  }

  /**
   * Queue a filter operation with debouncing
   * Requirement 1.2: Debounce rapid slider adjustments
   * Requirement 3.2: Batch multiple parameter changes
   * 
   * @param {Object} adjustments - Filter adjustment parameters
   * @param {Function} processingCallback - Function to call when processing is needed
   * @param {Object} options - Additional options (priority, quality, etc.)
   * @returns {Promise} Resolves with processed result
   */
  queueFilter(adjustments, processingCallback, options = {}) {
    const priority = options.priority || 'normal';
    
    this.stats.totalRequests++;
    
    // Check cache first
    const cacheKey = this._generateHash(adjustments);
    const cachedResult = this._getCached(cacheKey);
    
    if (cachedResult) {
      this.stats.hits++;
      console.log(`[FilterQueueManager] Cache hit (${this._getHitRate().toFixed(1)}% hit rate)`);
      return Promise.resolve(cachedResult);
    }
    
    this.stats.misses++;
    
    // Create request object
    const request = {
      adjustments,
      processingCallback,
      cacheKey,
      priority,
      timestamp: Date.now(),
      options
    };
    
    // High priority requests bypass debouncing
    if (priority === 'high') {
      return this._processRequest(request);
    }
    
    // Store pending request for batching
    this.pendingRequest = request;
    
    // Clear existing debounce timer
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
    
    // Return promise that resolves when processing completes
    return new Promise((resolve, reject) => {
      request.resolve = resolve;
      request.reject = reject;
      
      // Set debounce timer
      this.debounceTimer = setTimeout(() => {
        this._processPendingRequest();
      }, this.debounceDelay);
    });
  }

  /**
   * Process the pending request after debounce delay
   * Requirement 1.2: Debouncing mechanism
   * @private
   */
  _processPendingRequest() {
    if (!this.pendingRequest) {
      return;
    }
    
    const request = this.pendingRequest;
    this.pendingRequest = null;
    this.debounceTimer = null;
    
    this._processRequest(request)
      .then(result => {
        if (request.resolve) {
          request.resolve(result);
        }
      })
      .catch(error => {
        if (request.reject) {
          request.reject(error);
        }
      });
  }

  /**
   * Process a filter request
   * Uses Web Worker if available, falls back to callback
   * @private
   */
  async _processRequest(request) {
    try {
      let result;
      
      // Try to use Web Worker if available
      if (this.isWorkerAvailable() && request.options.imageData) {
        try {
          const workerResult = await this._sendToWorker(
            request.options.imageData,
            request.adjustments,
            request.options.previewMode !== false
          );
          result = workerResult.imageData;
        } catch (workerError) {
          console.warn('[FilterQueueManager] Worker processing failed, falling back to callback:', workerError);
          // Fall back to callback
          result = await request.processingCallback(request.adjustments);
        }
      } else {
        // Use callback directly
        result = await request.processingCallback(request.adjustments);
      }
      
      // Cache the result
      this._addToCache(request.cacheKey, result);
      
      return result;
    } catch (error) {
      console.error('[FilterQueueManager] Processing error:', error);
      throw error;
    }
  }

  /**
   * Get cached result
   * @private
   */
  _getCached(key) {
    const entry = this.cache.get(key);
    
    if (!entry) {
      return null;
    }
    
    // Update access timestamp and hit count
    entry.timestamp = Date.now();
    entry.hitCount++;
    
    // Move to end (most recently used)
    this.cache.delete(key);
    this.cache.set(key, entry);
    
    return entry.imageData;
  }

  /**
   * Add result to cache with LRU eviction
   * Requirement 3.2: LRU cache for filter results
   * @private
   */
  _addToCache(key, imageData) {
    // Calculate memory size (approximate)
    const memorySize = imageData.data ? imageData.data.length : 0;
    
    // Check if cache is full
    if (this.cache.size >= this.cacheSize) {
      // Evict least recently used (first entry)
      const firstKey = this.cache.keys().next().value;
      const evictedEntry = this.cache.get(firstKey);
      
      this.cache.delete(firstKey);
      this.stats.evictions++;
      this.stats.totalMemoryUsed -= evictedEntry.memorySize;
      
      console.log(`[FilterQueueManager] Cache eviction (size: ${this.cache.size}/${this.cacheSize})`);
    }
    
    // Add new entry
    this.cache.set(key, {
      imageData,
      timestamp: Date.now(),
      hitCount: 0,
      memorySize
    });
    
    this.stats.totalMemoryUsed += memorySize;
  }

  /**
   * Cancel pending operations
   * Requirement 1.2: Cancel pending operations
   */
  cancelPending() {
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
      this.debounceTimer = null;
    }
    
    if (this.pendingRequest) {
      if (this.pendingRequest.reject) {
        this.pendingRequest.reject(new Error('Request cancelled'));
      }
      this.pendingRequest = null;
    }
    
    this.requestQueue = [];
    
    console.log('[FilterQueueManager] Pending operations cancelled');
  }

  /**
   * Get cache statistics
   * Requirement 3.2: Cache statistics tracking (hit rate, memory usage)
   * 
   * @returns {Object} Cache statistics
   */
  getCacheStats() {
    const hitRate = this._getHitRate();
    const memoryUsageMB = this.stats.totalMemoryUsed / (1024 * 1024);
    
    return {
      size: this.cache.size,
      maxSize: this.cacheSize,
      hits: this.stats.hits,
      misses: this.stats.misses,
      hitRate: hitRate,
      totalRequests: this.stats.totalRequests,
      evictions: this.stats.evictions,
      memoryUsageBytes: this.stats.totalMemoryUsed,
      memoryUsageMB: memoryUsageMB.toFixed(2),
      entries: Array.from(this.cache.entries()).map(([key, entry]) => ({
        key,
        hitCount: entry.hitCount,
        age: Date.now() - entry.timestamp,
        memorySizeKB: (entry.memorySize / 1024).toFixed(2)
      }))
    };
  }

  /**
   * Calculate cache hit rate
   * @private
   */
  _getHitRate() {
    if (this.stats.totalRequests === 0) {
      return 0;
    }
    return (this.stats.hits / this.stats.totalRequests) * 100;
  }

  /**
   * Set debounce delay dynamically
   * Requirement 4.3: Adaptive debounce delay
   * 
   * @param {number} ms - New debounce delay in milliseconds
   */
  setDebounceDelay(ms) {
    this.debounceDelay = Math.max(0, ms);
    console.log(`[FilterQueueManager] Debounce delay updated to ${this.debounceDelay}ms`);
  }

  /**
   * Get current debounce delay
   */
  getDebounceDelay() {
    return this.debounceDelay;
  }

  /**
   * Adjust debounce delay based on performance metrics
   * Requirement 4.3: Adaptive debounce delay based on device performance
   * 
   * @param {number} averageProcessingTime - Average processing time in ms
   */
  adaptDebounceDelay(averageProcessingTime) {
    // If processing is slow, increase debounce delay
    if (averageProcessingTime > 20) {
      const newDelay = Math.min(this.debounceDelay * 1.5, 500);
      this.setDebounceDelay(newDelay);
      console.log(`[FilterQueueManager] Increased debounce delay due to slow processing (${averageProcessingTime.toFixed(2)}ms avg)`);
    } 
    // If processing is fast, decrease debounce delay
    else if (averageProcessingTime < 10 && this.debounceDelay > 50) {
      const newDelay = Math.max(this.debounceDelay * 0.8, 50);
      this.setDebounceDelay(newDelay);
      console.log(`[FilterQueueManager] Decreased debounce delay due to fast processing (${averageProcessingTime.toFixed(2)}ms avg)`);
    }
  }

  /**
   * Clear the cache
   */
  clearCache() {
    const previousSize = this.cache.size;
    this.cache.clear();
    this.stats.totalMemoryUsed = 0;
    
    console.log(`[FilterQueueManager] Cache cleared (${previousSize} entries removed)`);
  }

  /**
   * Clear cache statistics
   */
  clearStats() {
    this.stats = {
      hits: 0,
      misses: 0,
      totalRequests: 0,
      evictions: 0,
      totalMemoryUsed: 0
    };
    
    console.log('[FilterQueueManager] Statistics cleared');
  }

  /**
   * Get device performance level
   */
  getDeviceLevel() {
    return this.deviceLevel;
  }

  /**
   * Check if cache should be cleared due to memory pressure
   * Requirement 3.2: Memory management
   * 
   * @param {number} memoryThresholdMB - Memory threshold in MB (default: 150)
   * @returns {boolean} True if cache should be cleared
   */
  shouldClearCacheForMemory(memoryThresholdMB = 150) {
    const currentMemoryMB = this.stats.totalMemoryUsed / (1024 * 1024);
    
    if (currentMemoryMB > memoryThresholdMB) {
      console.warn(`[FilterQueueManager] Memory threshold exceeded: ${currentMemoryMB.toFixed(2)}MB > ${memoryThresholdMB}MB`);
      return true;
    }
    
    return false;
  }

  /**
   * Print cache statistics to console
   */
  printStats() {
    const stats = this.getCacheStats();
    const workerStatus = this.getWorkerStatus();
    
    console.group('📦 FilterQueueManager Cache Statistics');
    console.log(`Cache Size: ${stats.size}/${stats.maxSize}`);
    console.log(`Hit Rate: ${stats.hitRate.toFixed(1)}%`);
    console.log(`Hits: ${stats.hits}`);
    console.log(`Misses: ${stats.misses}`);
    console.log(`Total Requests: ${stats.totalRequests}`);
    console.log(`Evictions: ${stats.evictions}`);
    console.log(`Memory Usage: ${stats.memoryUsageMB}MB`);
    console.log(`Debounce Delay: ${this.debounceDelay}ms`);
    console.log(`Device Level: ${this.deviceLevel}`);
    console.log(`Worker Status: ${workerStatus.ready ? '✓ Ready' : workerStatus.initializing ? '⏳ Initializing' : '✗ Not Ready'}`);
    console.log(`Worker Pending: ${workerStatus.pendingRequests}`);
    console.groupEnd();
  }

  /**
   * Get configuration summary
   */
  getConfig() {
    return {
      cacheSize: this.cacheSize,
      debounceDelay: this.debounceDelay,
      deviceLevel: this.deviceLevel,
      currentCacheSize: this.cache.size
    };
  }
}

// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
  module.exports = FilterQueueManager;
} else {
  window.FilterQueueManager = FilterQueueManager;
}
