/**
 * Improved Canvas Handler focusing on proper scaling, grid, and controls
 * With enhanced aspect ratio maintenance
 */
class CanvasHandler {
    constructor(canvasId, config = {}) {
        this.canvas = document.getElementById(canvasId);
        if (!this.canvas) {
            console.error('Canvas element not found:', canvasId);
            return;
        }
        
        // Apply configuration with defaults
        this.config = {
            enableGrid: config.enableGrid !== undefined ? config.enableGrid : true,
            enableTooltips: config.enableTooltips !== undefined ? config.enableTooltips : true,
            enableControls: config.enableControls !== undefined ? config.enableControls : true,
            initialZoom: config.initialZoom || 1,
            gridColor: config.gridColor || 'rgba(255, 255, 255, 0.1)'
        };
        
        this.ctx = this.canvas.getContext('2d');
        this.width = this.canvas.width;
        this.height = this.canvas.height;
        this.aspectRatio = this.width / this.height;
        
        // Initialize zoom and pan variables
        this.zoomLevel = this.config.initialZoom;
        // These will be calculated dynamically in initializeCanvas
        this.minScaleFactor = 0.25;  // Default, will be overwritten
        this.maxScaleFactor = 1.5;   // Default, will be overwritten
        this.panX = 0;
        this.panY = 0;
        this.isPanning = false;
        this.startPanX = 0;
        this.startPanY = 0;
        
        // Important: This keeps the canvas crisp with no anti-aliasing
        this.canvas.style.imageRendering = 'pixelated';
        this.canvas.style.imageRendering = '-moz-crisp-edges';
        this.canvas.style.imageRendering = 'crisp-edges';
        
        // Initialize pixel data array
        this.pixels = Array(this.width * this.height).fill('#000000');
        
        // Calculate initial pixel size based on container size
        this.initializeCanvas();
        
        // Create grid overlay that only covers the canvas
        if (this.config.enableGrid) {
            this.createGridOverlay();
        }
        
        // Add grid state
        this.gridVisible = this.config.enableGrid;
        
        // Draw initial state
        this.clear();
        
        // Add hover state to show coordinates
        if (this.config.enableTooltips) {
            this.setupHoverEffect();
        }
        
        // Add controls for zoom and pan
        if (this.config.enableControls) {
            this.addZoomPanControls();
        }
        
        // Add event listeners for zoom and pan
        this.setupZoomPanEvents();
        
        // Add window resize handler
        window.addEventListener('resize', () => this.handleResize());
    }
    
    initializeCanvas() {
        const wrapper = this.canvas.parentElement;
        if (!wrapper) return;
        
        // Set wrapper to relative positioning
        wrapper.style.position = 'relative';
        wrapper.style.overflow = 'hidden';
        
        // Get available space, accounting for padding
        const wrapperStyle = window.getComputedStyle(wrapper);
        const paddingX = parseFloat(wrapperStyle.paddingLeft) + parseFloat(wrapperStyle.paddingRight);
        const paddingY = parseFloat(wrapperStyle.paddingTop) + parseFloat(wrapperStyle.paddingBottom);
        
        const availableWidth = wrapper.clientWidth - paddingX;
        const availableHeight = wrapper.clientHeight - paddingY;
        
        // Calculate the scale to fit while maintaining aspect ratio
        const scaleX = availableWidth / this.width;
        const scaleY = availableHeight / this.height;
        const scale = Math.min(scaleX, scaleY);
        
        // Set base pixel size to fit the available space
        this.basePixelSize = scale;
        
        // Apply zoom to get current pixel size
        this.pixelSize = this.basePixelSize * this.zoomLevel;
        
        // Calculate dimensions based on pixel size
        const visibleWidth = this.width * this.pixelSize;
        const visibleHeight = this.height * this.pixelSize;
        
        // Center the canvas in the available space
        this.panX = Math.floor((availableWidth - visibleWidth) / 2) + parseFloat(wrapperStyle.paddingLeft);
        this.panY = Math.floor((availableHeight - visibleHeight) / 2) + parseFloat(wrapperStyle.paddingTop);
        
        // Define reasonable zoom limits based on container size
        this.minScaleFactor = 0.1; // Min 10% of base size
        this.maxScaleFactor = 4;   // Max 400% of base size
        
        // Update canvas display size
        this.updateCanvasSize();
    }
    
    updateCanvasSize() {
        // Calculate the visible width and height based on zoom
        const visibleWidth = Math.round(this.width * this.pixelSize);
        const visibleHeight = Math.round(this.height * this.pixelSize);
        
        // Set canvas display size with exact pixel measurements
        this.canvas.style.position = 'absolute';
        this.canvas.style.width = `${visibleWidth}px`;
        this.canvas.style.height = `${visibleHeight}px`;
        this.canvas.style.left = `${this.panX}px`;
        this.canvas.style.top = `${this.panY}px`;
        
        // Remove any transform to prevent blurring
        this.canvas.style.transform = 'none';
        this.canvas.style.transformOrigin = 'top left';
        
        // Ensure pixel-perfect rendering
        this.canvas.style.imageRendering = 'pixelated';
        this.canvas.style.imageRendering = '-moz-crisp-edges';
        this.canvas.style.imageRendering = 'crisp-edges';
        
        // Update grid overlay to match canvas exactly
        if (this.gridOverlay) {
            this.updateGridOverlay();
        }
    }
    // Add or update a method to update canvas dimensions
    updateCanvasDimensions(width, height) {
        this.width = width;
        this.height = height;
        this.canvas.width = width;
        this.canvas.height = height;
        
        // Reinitialize the pixel array with the new dimensions
        this.pixels = Array(this.width * this.height).fill('#000000');
        
        // Recalculate scaling to fit the available space
        this.initializeCanvas();
        
        // Clear the canvas with the new dimensions
        this.clear();
    }
    
    createGridOverlay() {
        // Create a grid overlay div that's positioned exactly over the canvas
        const wrapper = this.canvas.parentElement;
        if (!wrapper) return;
        
        // Check if grid already exists
        let gridOverlay = wrapper.querySelector('.grid-overlay');
        if (gridOverlay) {
            wrapper.removeChild(gridOverlay);
        }
        
        gridOverlay = document.createElement('div');
        gridOverlay.className = 'grid-overlay';
        
        // Position overlay to follow canvas with exact positioning
        gridOverlay.style.position = 'absolute';
        gridOverlay.style.pointerEvents = 'none';
        gridOverlay.style.zIndex = '5';
        
        wrapper.appendChild(gridOverlay);
        this.gridOverlay = gridOverlay;
        
        // Update grid style and position
        this.updateGridOverlay();
    }
    
    updateGridOverlay() {
        if (!this.gridOverlay) return;
        
        // Set grid size to match canvas size precisely
        this.gridOverlay.style.width = this.canvas.style.width;
        this.gridOverlay.style.height = this.canvas.style.height;

        // Position grid exactly over the canvas
        this.gridOverlay.style.left = this.canvas.style.left;
        this.gridOverlay.style.top = this.canvas.style.top;
        
        // Set grid pattern size to match pixels
        const cellSize = this.pixelSize;
        this.gridOverlay.style.backgroundSize = `${cellSize}px ${cellSize}px`;
        this.gridOverlay.style.backgroundPosition = '0 0'; // Align grid with canvas pixels
        
        // Set grid lines
        this.gridOverlay.style.backgroundImage = `
            linear-gradient(${this.config.gridColor} 1px, transparent 1px),
            linear-gradient(90deg, ${this.config.gridColor} 1px, transparent 1px)
        `;
        
        // Update visibility
        this.gridOverlay.style.display = this.gridVisible ? 'block' : 'none';
    }
    
    /**
     * Sets up enhanced hover tooltip effect with accurate pixel color
     */
    setupHoverEffect() {
        // Create tooltip element
        const tooltip = document.createElement('div');
        tooltip.style.position = 'absolute';
        tooltip.style.padding = '4px 8px';
        tooltip.style.background = 'rgba(0, 0, 0, 0.8)';
        tooltip.style.color = '#fff';
        tooltip.style.borderRadius = '4px';
        tooltip.style.fontSize = '12px';
        tooltip.style.pointerEvents = 'none';
        tooltip.style.zIndex = '999';
        tooltip.style.display = 'none';
        document.body.appendChild(tooltip);
        
        // Add color swatch to tooltip
        const colorSwatch = document.createElement('div');
        colorSwatch.style.display = 'inline-block';
        colorSwatch.style.width = '12px';
        colorSwatch.style.height = '12px';
        colorSwatch.style.marginRight = '6px';
        colorSwatch.style.border = '1px solid rgba(255, 255, 255, 0.3)';
        colorSwatch.style.verticalAlign = 'middle';
        
        // Add to tooltip
        tooltip.appendChild(colorSwatch);
        
        // Create text container
        const textContainer = document.createElement('span');
        tooltip.appendChild(textContainer);
        
        // Mouse move event to update tooltip
        this.canvas.addEventListener('mousemove', (e) => {
            const rect = this.canvas.getBoundingClientRect();
            
            // Calculate pixel coordinates accounting for zoom and pan
            const x = Math.floor((e.clientX - rect.left) / this.pixelSize);
            const y = Math.floor((e.clientY - rect.top) / this.pixelSize);
            
            if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
                // Get actual pixel data from our internal array
                const index = y * this.width + x;
                const color = this.pixels[index];
                
                // Update color swatch
                colorSwatch.style.backgroundColor = color;
                
                // Update tooltip text
                textContainer.textContent = `(${x},${y}) - ${color}`;
                
                // Position tooltip near the cursor
                tooltip.style.left = (e.clientX + 15) + 'px';
                tooltip.style.top = (e.clientY + 15) + 'px';
                tooltip.style.display = 'flex';
                tooltip.style.alignItems = 'center';
            } else {
                tooltip.style.display = 'none';
            }
            
            // Handle panning if active
            if (this.isPanning) {
                const deltaX = e.clientX - this.startPanX;
                const deltaY = e.clientY - this.startPanY;
                
                this.panX += deltaX;
                this.panY += deltaY;
                
                this.startPanX = e.clientX;
                this.startPanY = e.clientY;
                
                this.updateCanvasSize();
            }
        });
        
        // Hide tooltip when mouse leaves canvas
        this.canvas.addEventListener('mouseout', () => {
            tooltip.style.display = 'none';
        });
    }
    
    toggleGrid() {
        if (!this.gridOverlay) return;
        
        this.gridVisible = !this.gridVisible;
        this.gridOverlay.style.display = this.gridVisible ? 'block' : 'none';
    }
    
    /**
     * Clear the canvas and reset pixel data
     */
    clear() {
        // Fill canvas with black
        this.ctx.fillStyle = '#000000';
        this.ctx.fillRect(0, 0, this.width, this.height);
        
        // Reset pixel data array - make sure all pixels are initialized to black
        for (let i = 0; i < this.pixels.length; i++) {
            this.pixels[i] = '#000000';
        }
    }
    
    /**
     * Set a single pixel both in the canvas and in our tracking array
     */
    setPixel(x, y, color) {
        if (x < 0 || x >= this.width || y < 0 || y >= this.height) {
            return; // Out of bounds
        }
        
        // Update our pixel data array
        const index = y * this.width + x;
        this.pixels[index] = color;
        
        // Draw the pixel on the canvas (1:1 scale)
        this.ctx.fillStyle = color;
        this.ctx.fillRect(x, y, 1, 1);
    }
    
    /**
     * Updates the canvas with new pixel data and ensures internal tracking array is updated
     * This ensures tooltip shows accurate colors for each pixel
     */
    updateFromData(pixelData) {
        // Clear canvas first
        this.clear();
        
        // Set each pixel according to the data
        for (let y = 0; y < this.height; y++) {
            for (let x = 0; x < this.width; x++) {
                const index = y * this.width + x;
                if (index < pixelData.length) {
                    // Update our internal pixel data array AND draw the pixel
                    this.setPixel(x, y, pixelData[index]);
                }
            }
        }
    }
    
    drawText(x, y, text, color, size = 1) {
        // This is a helper method, but actual text drawing would be done
        // by setting individual pixels to form text characters
        const tempCanvas = document.createElement('canvas');
        const tempCtx = tempCanvas.getContext('2d');
        
        // Set font size relative to the LED scale
        const fontSize = size * 7;
        tempCanvas.width = text.length * fontSize * 0.6;
        tempCanvas.height = fontSize * 1.2;
        
        // Render text to the temporary canvas
        tempCtx.fillStyle = 'rgba(0,0,0,0)';
        tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
        tempCtx.fillStyle = color;
        tempCtx.font = `${fontSize}px monospace`;
        tempCtx.textBaseline = 'top';
        tempCtx.fillText(text, 0, 0);
        
        // Get pixel data from the temporary canvas
        const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
        const data = imageData.data;
        
        // Map the rendered text to LED pixels
        for (let ty = 0; ty < tempCanvas.height; ty++) {
            for (let tx = 0; tx < tempCanvas.width; tx++) {
                const dataIndex = (ty * tempCanvas.width + tx) * 4;
                const alpha = data[dataIndex + 3];
                
                // If the pixel is not fully transparent
                if (alpha > 128) {
                    const pixelX = x + Math.floor(tx / (fontSize * 0.6) * size);
                    const pixelY = y + Math.floor(ty / (fontSize * 1.2) * size);
                    
                    if (pixelX < this.width && pixelY < this.height) {
                        this.setPixel(pixelX, pixelY, color);
                    }
                }
            }
        }
    }
    
    /**
     * Add time-delayed tooltips to canvas control buttons
     * This replaces the original addZoomPanControls method
     */
    addZoomPanControls() {
        const wrapper = this.canvas.parentElement;
        if (!wrapper) return;
        
        // Create controls container
        const controls = document.createElement('div');
        controls.className = 'canvas-zoom-controls';
        
        // Create tooltip container (shared by all buttons)
        const tooltip = document.createElement('div');
        tooltip.className = 'control-tooltip';
        tooltip.style.position = 'absolute';
        tooltip.style.padding = '6px 10px';
        tooltip.style.background = 'rgba(0, 0, 0, 0.8)';
        tooltip.style.color = '#fff';
        tooltip.style.borderRadius = '4px';
        tooltip.style.fontSize = '12px';
        tooltip.style.pointerEvents = 'none';
        tooltip.style.display = 'none';
        tooltip.style.zIndex = '1000';
        tooltip.style.whiteSpace = 'nowrap';
        tooltip.style.transition = 'opacity 0.2s ease';
        tooltip.style.opacity = '0';
        wrapper.appendChild(tooltip);
        
        // Function to create a button with delayed tooltip
        const createButton = (symbol, tooltipText, clickHandler) => {
            const button = document.createElement('button');
            button.innerHTML = symbol;
            button.title = tooltipText; // For browser's native tooltip as fallback
            
            let tooltipTimer = null;
            
            // Add hover handlers
            button.addEventListener('mouseenter', (e) => {
                // Clear any existing timer
                if (tooltipTimer) clearTimeout(tooltipTimer);
                
                // Set 2-second delay before showing tooltip
                tooltipTimer = setTimeout(() => {
                    // Set tooltip text
                    tooltip.textContent = tooltipText;
                    
                    // Position tooltip above button
                    const buttonRect = button.getBoundingClientRect();
                    const wrapperRect = wrapper.getBoundingClientRect();
                    
                    tooltip.style.left = `${buttonRect.left - wrapperRect.left + (buttonRect.width / 2) - (tooltip.offsetWidth / 2)}px`;
                    tooltip.style.bottom = `${wrapperRect.bottom - buttonRect.top + 10}px`;
                    
                    // Show tooltip
                    tooltip.style.display = 'block';
                    
                    // Trigger fade in
                    setTimeout(() => {
                        tooltip.style.opacity = '1';
                    }, 10);
                }, 2000); // 2 seconds delay
            });
            
            button.addEventListener('mouseleave', () => {
                // Clear timer if mouse leaves before delay
                if (tooltipTimer) {
                    clearTimeout(tooltipTimer);
                    tooltipTimer = null;
                }
                
                // Hide tooltip
                tooltip.style.opacity = '0';
                setTimeout(() => {
                    if (tooltip.style.opacity === '0') {
                        tooltip.style.display = 'none';
                    }
                }, 200);
            });
            
            // Add click handler
            button.addEventListener('click', clickHandler);
            
            return button;
        };
        
        // Add zoom in button
        const zoomInBtn = createButton(
            '+', 
            'Zoom In',
            () => this.zoomIn()
        );
        
        // Add zoom out button
        const zoomOutBtn = createButton(
            '−', 
            'Zoom Out',
            () => this.zoomOut()
        );
        
        // Add reset button
        const resetBtn = createButton(
            '↔', 
            'Fit Screen',
            () => this.resetView()
        );
        
        // Add pan button
        const panBtn = createButton(
            '✥', 
            'Pan',
            () => {
                this.canvas.style.cursor = this.canvas.style.cursor === 'move' ? '' : 'move';
            }
        );
        
        // Add buttons to controls
        controls.appendChild(zoomInBtn);
        controls.appendChild(zoomOutBtn);
        controls.appendChild(resetBtn);
        controls.appendChild(panBtn);
        
        // Add controls to wrapper
        wrapper.appendChild(controls);
    }
    
    setupZoomPanEvents() {
        // Zoom with mouse wheel
        this.canvas.addEventListener('wheel', (e) => {
            e.preventDefault();
            
            if (e.deltaY < 0) {
                this.zoomIn(e.clientX, e.clientY);
            } else {
                this.zoomOut(e.clientX, e.clientY);
            }
        });
        
        // Pan with mouse down
        this.canvas.addEventListener('mousedown', (e) => {
            // Only start panning if cursor is set to move (pan mode active)
            if (this.canvas.style.cursor === 'move') {
                this.isPanning = true;
                this.startPanX = e.clientX;
                this.startPanY = e.clientY;
                this.canvas.style.cursor = 'grabbing';
            }
        });
        
        // Stop panning on mouse up
        document.addEventListener('mouseup', () => {
            if (this.isPanning) {
                this.isPanning = false;
                this.canvas.style.cursor = 'move';
            }
        });
    }
    
    zoomIn(clientX, clientY) {
        // Store previous zoom level
        const oldZoom = this.zoomLevel;
        
        // Define discrete zoom levels for better control
        const zoomLevels = [0.1, 0.2, 0.3, 0.5, 0.75, 1, 1.5, 2, 3, 4];
        
        // Find the next zoom level higher than our current level
        this.zoomLevel = zoomLevels.find(level => level > this.zoomLevel) || this.zoomLevel;
        
        // Ensure we don't exceed max scale
        this.zoomLevel = Math.min(this.zoomLevel, this.maxScaleFactor);
        
        // Update pixel size based on new zoom
        this.pixelSize = this.basePixelSize * this.zoomLevel;
        
        // If we have client coordinates, adjust pan to zoom toward cursor
        if (clientX && clientY) {
            const rect = this.canvas.getBoundingClientRect();
            
            // Calculate the point to zoom toward in canvas coordinates
            const canvasX = clientX - rect.left;
            const canvasY = clientY - rect.top;
            
            // Calculate zoom factor
            const zoomFactor = this.zoomLevel / oldZoom;
            
            // Adjust pan to keep the point under cursor
            this.panX = clientX - canvasX * zoomFactor;
            this.panY = clientY - canvasY * zoomFactor;
        }
        
        // Update canvas size and position
        this.updateCanvasSize();
    }
    
    zoomOut(clientX, clientY) {
        // Store previous zoom level
        const oldZoom = this.zoomLevel;
        
        // Define discrete zoom levels for better control
        const zoomLevels = [0.1, 0.2, 0.3, 0.5, 0.75, 1, 1.5, 2, 3, 4];
        
        // Find the next zoom level lower than our current level
        for (let i = zoomLevels.length - 1; i >= 0; i--) {
            if (zoomLevels[i] < this.zoomLevel) {
                this.zoomLevel = zoomLevels[i];
                break;
            }
        }
        
        // Ensure we don't go below minimum scale
        this.zoomLevel = Math.max(this.zoomLevel, this.minScaleFactor);
        
        // Update pixel size based on new zoom
        this.pixelSize = this.basePixelSize * this.zoomLevel;
        
        // If we have client coordinates, adjust pan to zoom from cursor
        if (clientX && clientY) {
            const rect = this.canvas.getBoundingClientRect();
            
            // Calculate the point to zoom from in canvas coordinates
            const canvasX = clientX - rect.left;
            const canvasY = clientY - rect.top;
            
            // Calculate zoom factor
            const zoomFactor = this.zoomLevel / oldZoom;
            
            // Adjust pan to keep the point under cursor
            this.panX = clientX - canvasX * zoomFactor;
            this.panY = clientY - canvasY * zoomFactor;
        }
        
        // Update canvas size and position
        this.updateCanvasSize();
    }
    
    resetView() {
        // Auto-fit the canvas to the available space while maintaining aspect ratio
        this.zoomLevel = 1;
        this.initializeCanvas();
    }
    
    handleResize() {
        // On window resize, recalculate optimal scaling and position
        // while maintaining the aspect ratio
        this.initializeCanvas();
    }

    handleZoom(scale) {
        this.scale = scale;
        const canvas = this.canvas;
        
        // Calculate new dimensions while maintaining aspect ratio
        const baseWidth = this.width;
        const baseHeight = this.height;
        
        // Scale both dimensions equally
        const scaledWidth = baseWidth * scale;
        const scaledHeight = baseHeight * scale;
        
        // Apply transform instead of changing canvas dimensions
        canvas.style.transform = `scale(${scale})`;
        
        // Update wrapper scroll area if needed
        const wrapper = canvas.parentElement;
        if (scale > 1) {
            wrapper.classList.add('zoomed');
        } else {
            wrapper.classList.remove('zoomed');
        }
        
        // Force redraw to ensure crisp pixels
        this.redraw();
    }
}