/**
 * render-system.ts — Advanced rendering system with performance optimization
 * 
 * Manages the complete rendering pipeline with:
 * - Double buffering for flicker-free rendering
 * - Adaptive quality based on performance
 * - Intelligent page caching
 * - Smooth zoom and scroll handling
 */

import { PerformanceManager } from './performance-manager.js';
import { PerformanceStats } from './types.js';
import { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';

interface RenderTask {
    pageNum: number;
    scale: number;
    quality: number;
    priority: number;
}

interface CacheEntry {
    bitmap: ImageBitmap;
    quality: number;
    timestamp: number;
    accessTime: number;
}

class RenderSystem {
    private pdfDoc: PDFDocumentProxy;
    private container: HTMLElement;
    private pageManager: any; // Replace with proper type when available
    private performanceManager: PerformanceManager;
    
    // Render state
    private currentScale: number;
    private targetScale: number;
    private renderQuality: number;
    private visiblePages: Set<number>;
    private bufferPages: Set<number>;
    private renderQueue: RenderTask[];
    private activeRenders: Map<number, boolean>;
    private pageCache: Map<string, CacheEntry>;
    private maxCacheSize: number;
    private cacheSize: number;
    
    // Performance metrics
    private lastRenderTime: number;
    private renderCount: number;

    constructor(pdfDoc: PDFDocumentProxy, container: HTMLElement, pageManager: any) {
        this.pdfDoc = pdfDoc;
        this.container = container;
        this.pageManager = pageManager;
        
        try {
            this.performanceManager = new PerformanceManager();
        } catch (error) {
            console.error("Failed to create PerformanceManager:", error);
            // Fallback: create a minimal performance manager
            this.performanceManager = {
                getPerformanceStats: () => ({ fps: 60, renderQuality: 1.0, adaptiveMode: false }),
                isPerformanceCritical: () => false,
                cleanup: () => {}
            } as PerformanceManager;
        }
        
        // Render state
        this.currentScale = 1.0;
        this.targetScale = 1.0;
        this.renderQuality = 1.0;
        this.visiblePages = new Set();
        this.bufferPages = new Set();
        this.renderQueue = [];
        this.activeRenders = new Map();
        this.pageCache = new Map(); // { "page_scale": {bitmap, timestamp, quality} }
        this.maxCacheSize = 20; // Max pages in cache
        this.cacheSize = 0;
        
        // Performance metrics
        this.lastRenderTime = 0;
        this.renderCount = 0;
        
        // Setup event listeners
        this.setupEventListeners();
    }

    private setupEventListeners(): void {
        // Performance-based quality adjustments
        setInterval(() => {
            this.adjustQualityBasedOnPerformance();
        }, 1000);
    }

    public setVisibility(visiblePages: Set<number>, bufferPages: Set<number>): void {
        this.visiblePages = new Set(visiblePages);
        this.bufferPages = new Set(bufferPages);
        this.scheduleRenders();
    }

    public setScale(scale: number): void {
        this.targetScale = scale;
        
        // Smooth transition for zoom
        if (Math.abs(this.targetScale - this.currentScale) > 0.01) {
            this.startZoomTransition();
        } else {
            this.currentScale = this.targetScale;
            this.scheduleRenders();
        }
    }

    private startZoomTransition(): void {
        const startScale = this.currentScale;
        const endScale = this.targetScale;
        const startTime = performance.now();
        const duration = 150; // 150ms transition
        
        const animate = (currentTime: number) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);
            
            // Ease-in-out animation
            const easedProgress = this.easeInOutCubic(progress);
            this.currentScale = startScale + (endScale - startScale) * easedProgress;
            
            // Use lower quality during transition
            const transitionQuality = this.renderQuality * 0.8;
            this.scheduleRenders(transitionQuality);
            
            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                this.currentScale = endScale;
                // Final high-quality render after transition
                this.scheduleRenders(this.renderQuality);
            }
        };
        
        requestAnimationFrame(animate);
    }

    private easeInOutCubic(t: number): number {
        return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
    }

    private scheduleRenders(overrideQuality: number | null = null): void {
        const quality = overrideQuality !== null ? overrideQuality : this.renderQuality;
        const scale = this.currentScale;
        
        // Cancel renders for pages that are no longer needed
        this.cancelStaleRenders();
        
        // Prioritize visible pages, then buffer pages
        const allPages = [...this.visiblePages, ...this.bufferPages];
        
        allPages.forEach(pageNum => {
            const cacheKey = this.getCacheKey(pageNum, scale);
            
            // Check if we need to render this page
            if (this.shouldRenderPage(pageNum, scale, quality)) {
                this.queueRender(pageNum, scale, quality);
            }
        });
        
        // Process the render queue
        this.processRenderQueue();
    }

    private shouldRenderPage(pageNum: number, scale: number, quality: number): boolean {
        const cacheKey = this.getCacheKey(pageNum, scale);
        
        // Check if already rendering
        if (this.activeRenders.has(pageNum)) {
            return false;
        }
        
        // Check cache with acceptable quality
        const cached = this.pageCache.get(cacheKey);
        if (cached) {
            // If cached quality is acceptable, no need to re-render
            if (cached.quality >= quality * 0.9) {
                this.useCachedPage(pageNum, scale, cached.bitmap);
                return false;
            }
        }
        
        return true;
    }

    private queueRender(pageNum: number, scale: number, quality: number): void {
        // Avoid duplicate queue entries
        if (this.renderQueue.some(item => item.pageNum === pageNum && Math.abs(item.scale - scale) < 0.01)) {
            return;
        }
        
        // Prioritize visible pages
        const priority = this.visiblePages.has(pageNum) ? 1 : 2;
        
        this.renderQueue.push({ pageNum, scale, quality, priority });
        
        // Sort by priority (visible pages first)
        this.renderQueue.sort((a, b) => a.priority - b.priority);
    }

    private processRenderQueue(): void {
        if (this.renderQueue.length === 0) return;
        
        // Limit concurrent renders based on performance
        const maxConcurrent = this.performanceManager.isPerformanceCritical() ? 2 : 4;
        
        while (this.activeRenders.size < maxConcurrent && this.renderQueue.length > 0) {
            const renderTask = this.renderQueue.shift();
            if (renderTask) {
                this.startRender(renderTask.pageNum, renderTask.scale, renderTask.quality);
            }
        }
    }

    private async startRender(pageNum: number, scale: number, quality: number): Promise<void> {
        this.activeRenders.set(pageNum, true);
        let page: PDFPageProxy | null = null;
        
        try {
            page = await this.pdfDoc.getPage(pageNum);
            
            // Apply quality-based scaling
            const effectiveScale = scale * quality;
            const viewport = page.getViewport({ scale: effectiveScale });
            
            // Validate canvas dimensions
            const width = Math.round(viewport.width);
            const height = Math.round(viewport.height);
            
            if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
                throw new Error(`Invalid canvas dimensions: ${width}x${height}`);
            }
            
            // Create offscreen canvas
            const offscreen = new OffscreenCanvas(width, height);
            const context = offscreen.getContext('2d') as unknown as CanvasRenderingContext2D;
            
            if (!context) {
                throw new Error('Failed to get 2D context for offscreen canvas');
            }
            
            // Render with quality settings
            await page.render({
                canvasContext: context,
                viewport: viewport,
                // Use lower quality rendering when performance is critical
                renderQuality: this.performanceManager.isPerformanceCritical() ? 'low' : 'high'
            }).promise;
            
            // Transfer bitmap
            const bitmap = offscreen.transferToImageBitmap();
            
            // Cache the result
            const cacheKey = this.getCacheKey(pageNum, scale);
            this.cachePage(cacheKey, bitmap, quality);
            
            // Update display
            this.updatePageDisplay(pageNum, bitmap);
            
        } catch (error) {
            if (error && typeof error === 'object' && 'name' in error && error.name !== "RenderingCancelledException") {
                console.warn(`[RenderSystem] Render failed for page ${pageNum}:`, error);
            }
        } finally {
            this.activeRenders.delete(pageNum);
            page?.cleanup();
            
            // Process next in queue
            this.processRenderQueue();
        }
    }

    private cachePage(cacheKey: string, bitmap: ImageBitmap, quality: number): void {
        // Evict if cache is full
        if (this.cacheSize >= this.maxCacheSize) {
            this.evictOldestCacheEntry();
        }
        
        this.pageCache.set(cacheKey, {
            bitmap,
            quality,
            timestamp: Date.now(),
            accessTime: Date.now()
        });
        
        this.cacheSize++;
        
        // Update access time for LRU
        this.updateCacheAccess(cacheKey);
    }

    private evictOldestCacheEntry(): void {
        if (this.pageCache.size === 0) return;
        
        // Find least recently used
        let oldestKey: string | null = null;
        let oldestTime = Infinity;
        
        this.pageCache.forEach((entry, key) => {
            if (entry.accessTime < oldestTime) {
                oldestTime = entry.accessTime;
                oldestKey = key;
            }
        });
        
        if (oldestKey) {
            const entry = this.pageCache.get(oldestKey);
            if (entry) {
                entry.bitmap.close(); // Free memory
            }
            this.pageCache.delete(oldestKey);
            this.cacheSize--;
        }
    }

    private updateCacheAccess(cacheKey: string): void {
        const entry = this.pageCache.get(cacheKey);
        if (entry) {
            entry.accessTime = Date.now();
        }
    }

    private useCachedPage(pageNum: number, scale: number, bitmap: ImageBitmap): void {
        // Create a copy of the bitmap for display
        const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
        const context = canvas.getContext('2d');
        if (context) {
            context.drawImage(bitmap, 0, 0);
            const displayBitmap = canvas.transferToImageBitmap();
            this.updatePageDisplay(pageNum, displayBitmap);
        }
    }

    private updatePageDisplay(pageNum: number, bitmap: ImageBitmap): void {
        // Use the EnhancedPageManager's onRendered method to handle display
        // This ensures proper integration with the page manager's state and canvas management
        const gen = this.pageManager.renderGen;
        const quality = this.renderQuality; // Use current render quality
        
        this.pageManager.onRendered(pageNum, gen, bitmap, quality);
    }

    private cancelStaleRenders(): void {
        // Cancel renders for pages that are no longer visible or buffered
        const neededPages = new Set([...this.visiblePages, ...this.bufferPages]);
        
        this.activeRenders.forEach((_, pageNum) => {
            if (!neededPages.has(pageNum)) {
                // This render is no longer needed
                this.activeRenders.delete(pageNum);
            }
        });
        
        // Also clean up render queue
        this.renderQueue = this.renderQueue.filter(task => 
            neededPages.has(task.pageNum)
        );
    }

    private adjustQualityBasedOnPerformance(): void {
        const stats = this.performanceManager.getPerformanceStats();
        
        // If performance is critical, we may have already adjusted quality
        // Here we can make additional adjustments based on render load
        if (this.activeRenders.size > 4 && stats.fps < 50) {
            // Too many active renders and low FPS - reduce quality more
            const newQuality = Math.max(0.5, this.renderQuality - 0.1);
            if (newQuality < this.renderQuality) {
                this.setRenderQuality(newQuality);
            }
        }
    }

    private setRenderQuality(quality: number): void {
        this.renderQuality = Math.max(0.3, Math.min(1.0, quality));
        
        // Re-render visible pages with new quality
        if (this.visiblePages.size > 0) {
            this.scheduleRenders(this.renderQuality);
        }
    }

    public cleanupMemory(): void {
        // Aggressive cleanup - keep only visible pages
        const visibleCacheKeys = new Set<string>();
        
        this.visiblePages.forEach(pageNum => {
            const cacheKey = this.getCacheKey(pageNum, this.currentScale);
            visibleCacheKeys.add(cacheKey);
        });
        
        // Remove all non-visible pages from cache
        this.pageCache.forEach((entry, cacheKey) => {
            if (!visibleCacheKeys.has(cacheKey)) {
                entry.bitmap.close();
                this.pageCache.delete(cacheKey);
                this.cacheSize--;
            }
        });
        
        // Force garbage collection if available
        if (window.gc) {
            window.gc();
        }
    }

    private getCacheKey(pageNum: number, scale: number): string {
        // Round scale to 2 decimal places for caching
        const roundedScale = Math.round(scale * 100) / 100;
        return `${pageNum}_${roundedScale}`;
    }

    public async handleRenderRequest(pageNum: number, scale: number, vpWidth: number, vpHeight: number, quality: number): Promise<void> {
        // Handle render request from PageManager
        // This integrates with the PageManager's rendering pipeline
        await this.startRender(pageNum, scale, quality);
    }

    public cleanup(): void {
        // Clean up all resources
        this.pageCache.forEach(entry => {
            entry.bitmap.close();
        });
        
        this.activeRenders.clear();
        this.renderQueue = [];
        
        this.performanceManager.cleanup();
    }

    public getPerformanceStats(): PerformanceStats & {
        activeRenders: number;
        cacheSize: number;
        renderQueueLength: number;
    } {
        return {
            ...this.performanceManager.getPerformanceStats(),
            activeRenders: this.activeRenders.size,
            cacheSize: this.cacheSize,
            renderQueueLength: this.renderQueue.length
        };
    }
}

// Export for ES6 modules
export { RenderSystem };