// Aladin Mosaic Extension
// Provides mosaic panel calculation and visualization functionality

/**
 * Calculates mosaic panel positions for imaging planning using proper spherical geometry
 * @param {number} centerRA - Center RA in degrees
 * @param {number} centerDec - Center Dec in degrees
 * @param {number} fovWidthArcmin - FOV width in arcminutes
 * @param {number} fovHeightArcmin - FOV height in arcminutes
 * @param {number} panelsX - Number of panels in X direction
 * @param {number} panelsY - Number of panels in Y direction
 * @param {number} overlapPercent - Overlap percentage (0-100)
 * @param {number} rotationDegrees - Rotation angle in degrees (Position Angle)
 * @param {boolean} useRotator - Whether to use rotator compensation
 * @returns {Array} Array of panel objects with RA, Dec, and other properties
 */
function calculateMosaicPanels(centerRA, centerDec, fovWidthArcmin, fovHeightArcmin, 
                              panelsX, panelsY, overlapPercent, rotationDegrees = 0, useRotator = false) {
    const panels = [];
    
    // Convert to radians for calculations
    const centerRArad = centerRA * Math.PI / 180.0;
    const centerDecRad = centerDec * Math.PI / 180.0;
    const positionAngleRad = rotationDegrees * Math.PI / 180.0;
    
    // Convert FOV to degrees
    const fovWidthDeg = fovWidthArcmin / 60.0;
    const fovHeightDeg = fovHeightArcmin / 60.0;
    
    // Calculate effective panel spacing (accounting for overlap)
    const overlapFactor = (100 - overlapPercent) / 100.0;
    const panelSpacingXDeg = fovWidthDeg * overlapFactor;
    const panelSpacingYDeg = fovHeightDeg * overlapFactor;
    
    // Calculate grid offsets from center (in degrees)
    const totalWidthDeg = (panelsX - 1) * panelSpacingXDeg;
    const totalHeightDeg = (panelsY - 1) * panelSpacingYDeg;
    
    let panelIndex = 0;
    
    for (let y = 0; y < panelsY; y++) {
        for (let x = panelsX - 1; x >= 0; x--) {
            // Calculate offset from center in a rectangular grid
            const offsetXDeg = (x * panelSpacingXDeg) - (totalWidthDeg / 2.0);
            const offsetYDeg = (totalHeightDeg / 2.0) - (y * panelSpacingYDeg);
            
            // Convert offsets to angular distances
            const offsetXRad = offsetXDeg * Math.PI / 180.0;
            const offsetYRad = offsetYDeg * Math.PI / 180.0;
            
            let panelRA, panelDec, panelRotation;
            
            if (useRotator) {
                // WITH ROTATOR: Use spherical trigonometry for perfect rectangular grid
                // Apply rotation to grid offsets
                const rotatedXRad = offsetXRad * Math.cos(positionAngleRad) - offsetYRad * Math.sin(positionAngleRad);
                const rotatedYRad = offsetXRad * Math.sin(positionAngleRad) + offsetYRad * Math.cos(positionAngleRad);
                
                // Convert to angular distance and bearing
                const angularDistance = Math.sqrt(rotatedXRad * rotatedXRad + rotatedYRad * rotatedYRad);
                const bearing = Math.atan2(rotatedXRad, rotatedYRad);
                
                if (angularDistance > 0) {
                    // Use spherical trigonometry to calculate new position
                    const newDecRad = Math.asin(
                        Math.sin(centerDecRad) * Math.cos(angularDistance) +
                        Math.cos(centerDecRad) * Math.sin(angularDistance) * Math.cos(bearing)
                    );
                    
                    const deltaRArad = Math.atan2(
                        Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(centerDecRad),
                        Math.cos(angularDistance) - Math.sin(centerDecRad) * Math.sin(newDecRad)
                    );
                    
                    panelRA = centerRA + (deltaRArad * 180.0 / Math.PI);
                    panelDec = newDecRad * 180.0 / Math.PI;
                    
                    // Calculate field rotation for rotator compensation
                    // Detect center column: check if this panel is in the center X position
                    const centerX = Math.floor(panelsX / 2);
                    const isCenterColumn = (x === centerX);
                    
                    // Only panels NOT in center column need field rotation compensation
                    const fieldRotationRad = !isCenterColumn ? (deltaRArad * Math.sin(centerDecRad)) : 0;
                    
                    // WITH ROTATOR: Visual rotation stays same (user PA), rotator compensates for field rotation
                    panelRotation = rotationDegrees;
                } else {
                    // Center panel
                    panelRA = centerRA;
                    panelDec = centerDec;
                    panelRotation = rotationDegrees;
                }
            } else {
                // WITHOUT ROTATOR: Proper spherical positioning with PA=0 for all panels
                // Panels follow natural sky geometry, declinations vary per row
                
                // Apply rotation to grid offsets
                const rotatedXRad = offsetXRad * Math.cos(positionAngleRad) - offsetYRad * Math.sin(positionAngleRad);
                const rotatedYRad = offsetXRad * Math.sin(positionAngleRad) + offsetYRad * Math.cos(positionAngleRad);
                
                // Convert to angular distance and bearing
                const angularDistance = Math.sqrt(rotatedXRad * rotatedXRad + rotatedYRad * rotatedYRad);
                const bearing = Math.atan2(rotatedXRad, rotatedYRad);
                
                
                if (angularDistance > 1e-10) { // Use small epsilon instead of 0 to handle floating point precision
                    // Use spherical trigonometry to calculate panel position
                    // This ensures declinations vary properly per row at extreme declinations
                    const newDecRad = Math.asin(
                        Math.sin(centerDecRad) * Math.cos(angularDistance) +
                        Math.cos(centerDecRad) * Math.sin(angularDistance) * Math.cos(bearing)
                    );
                    
                    const deltaRArad = Math.atan2(
                        Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(centerDecRad),
                        Math.cos(angularDistance) - Math.sin(centerDecRad) * Math.sin(newDecRad)
                    );
                    
                    panelRA = centerRA + (deltaRArad * 180.0 / Math.PI);
                    panelDec = newDecRad * 180.0 / Math.PI;
                    
                    // Calculate field rotation for visual display (while keeping PA = 0 for imaging)
                    // Field rotation is proportional to RA offset and declination
                    // More rotation at higher declinations, less at equator
                    
                    // Detect center column: check if this panel is in the center X position
                    const centerX = Math.floor(panelsX / 2);
                    const isCenterColumn = (x === centerX);
                    
                    // Only panels NOT in center column should show field rotation
                    const fieldRotationRad = !isCenterColumn ? (deltaRArad * Math.sin(centerDecRad)) : 0;
                    
                    
                    // WITHOUT ROTATOR: PA stays same for imaging, add field rotation for visual display only
                    panelRotation = rotationDegrees + (fieldRotationRad * 180.0 / Math.PI);
                } else {
                    // Center panel
                    panelRA = centerRA;
                    panelDec = centerDec;
                    // Center panel has no field rotation
                    panelRotation = rotationDegrees;
                }
            }
            
            // Handle RA wraparound
            if (panelRA < 0) panelRA += 360;
            if (panelRA >= 360) panelRA -= 360;
            
            // Clamp declination
            if (panelDec > 90) panelDec = 90;
            if (panelDec < -90) panelDec = -90;
            
            // Calculate imagingPA based on rotator usage
            let imagingPA;
            if (useRotator) {
                // WITH ROTATOR: imagingPA = user PA + negated field rotation (actual PA needed for each panel)
                // Calculate field rotation for this panel
                const centerX = Math.floor(panelsX / 2);
                const isCenterColumn = (x === centerX);
                
                if (!isCenterColumn) {
                    // Calculate field rotation using same logic as without rotator
                    const rotatedXRad = offsetXRad * Math.cos(positionAngleRad) - offsetYRad * Math.sin(positionAngleRad);
                    const rotatedYRad = offsetXRad * Math.sin(positionAngleRad) + offsetYRad * Math.cos(positionAngleRad);
                    const angularDist = Math.sqrt(rotatedXRad * rotatedXRad + rotatedYRad * rotatedYRad);
                    
                    if (angularDist > 1e-10) {
                        const bearing = Math.atan2(rotatedXRad, rotatedYRad);
                        
                        const newDecRad = Math.asin(
                            Math.sin(centerDecRad) * Math.cos(angularDist) +
                            Math.cos(centerDecRad) * Math.sin(angularDist) * Math.cos(bearing)
                        );
                        
                        const deltaRArad = Math.atan2(
                            Math.sin(bearing) * Math.sin(angularDist) * Math.cos(centerDecRad),
                            Math.cos(angularDist) - Math.sin(centerDecRad) * Math.sin(newDecRad)
                        );
                        
                        const fieldRotationRad = deltaRArad * Math.sin(centerDecRad);
                        imagingPA = rotationDegrees - (fieldRotationRad * 180.0 / Math.PI);
                    } else {
                        imagingPA = rotationDegrees;
                    }
                } else {
                    imagingPA = rotationDegrees; // Center column
                }
            } else {
                // WITHOUT ROTATOR: imagingPA = user PA (same for all panels)
                imagingPA = rotationDegrees;
            }
            
            panels.push({
                index: panelIndex++,
                x: x,
                y: y,
                ra: panelRA,
                dec: panelDec,
                rotation: panelRotation, // Visual rotation for Aladin display
                imagingPA: imagingPA, // Actual PA for imaging
                fovWidthArcmin: fovWidthArcmin,
                fovHeightArcmin: fovHeightArcmin,
                label: `Panel ${x + 1},${y + 1}`,
                // Additional metadata
                offsetXDeg: offsetXDeg,
                offsetYDeg: offsetYDeg
            });
        }
    }
    
    return panels;
}

/**
 * Formats mosaic panel data for CSV export
 * @param {Array} panels - Array of panel objects
 * @param {string} equipmentName - Name of the equipment
 * @returns {string} CSV formatted string
 */
function formatMosaicPanelsCSV(panels, equipmentName = 'Equipment') {
    if (!panels || panels.length === 0) {
        return '';
    }
    
    const headers = [
        'Panel',
        'X_Index',
        'Y_Index', 
        'RA_Degrees',
        'Dec_Degrees',
        'RA_HMS',
        'Dec_DMS',
        'Rotation_Degrees',
        'FOV_Width_Arcmin',
        'FOV_Height_Arcmin',
        'Equipment'
    ];
    
    const rows = [headers.join(';')];
    
    panels.forEach(panel => {
        // Format RA as HMS
        const raHours = panel.ra / 15.0;
        const raH = Math.floor(raHours);
        const raM = Math.floor((raHours - raH) * 60);
        const raS = ((raHours - raH - raM / 60) * 3600).toFixed(2);
        const raHMS = `${raH.toString().padStart(2, '0')}:${raM.toString().padStart(2, '0')}:${raS.padStart(5, '0')}`;
        
        // Format Dec as DMS
        const decSign = panel.dec >= 0 ? '+' : '-';
        const decAbs = Math.abs(panel.dec);
        const decD = Math.floor(decAbs);
        const decM = Math.floor((decAbs - decD) * 60);
        const decS = ((decAbs - decD - decM / 60) * 3600).toFixed(1);
        const decDMS = `${decSign}${decD.toString().padStart(2, '0')}:${decM.toString().padStart(2, '0')}:${decS.padStart(4, '0')}`;
        
        const row = [
            panel.label,
            panel.x + 1,
            panel.y + 1,
            panel.ra.toFixed(6),
            panel.dec.toFixed(6),
            raHMS,
            decDMS,
            (panel.imagingPA !== undefined ? panel.imagingPA : panel.rotation).toFixed(2),
            panel.fovWidthArcmin.toFixed(2),
            panel.fovHeightArcmin.toFixed(2),
            equipmentName
        ];
        
        rows.push(row.join(';'));
    });
    
    return rows.join('\n');
}

/**
 * Downloads CSV data as a file
 * @param {string} csvData - CSV formatted string
 * @param {string} filename - Filename for download
 */
function downloadCSV(csvData, filename = 'mosaic_panels.csv') {
    const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' });
    const link = document.createElement('a');
    
    if (link.download !== undefined) {
        const url = URL.createObjectURL(blob);
        link.setAttribute('href', url);
        link.setAttribute('download', filename);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    }
}

/**
 * Extends the Aladin instance with mosaic functionality
 * @param {Object} aladinInstance - The Aladin instance to extend
 */
function extendAladinWithMosaic(aladinInstance) {
    if (!aladinInstance) {
        console.error('Cannot extend null Aladin instance with mosaic functionality');
        return;
    }
    
    // Add mosaic-specific properties - support multiple equipment mosaics
    aladinInstance.mosaicOverlays = [];
    aladinInstance.activeMosaicEquipments = new Map(); // Map of equipmentId -> equipment data
    
    // Event listeners are now handled centrally in aladin.js
    // The main positionChangedHandler will call updateMosaicPanelPositions() when needed
    // Also notify Blazor when position changes to update coordinate lists
    if (window.aladinInstance && window.aladinInstance.notifyPositionChanged) {
        window.aladinInstance.notifyPositionChanged = function() {
            if (window.dotNetHelper && window.dotNetHelper.invokeMethodAsync) {
                window.dotNetHelper.invokeMethodAsync('OnAladinPositionChanged');
            }
        };
    }
    
    /**
     * Creates and displays mosaic panels for equipment
     * @param {Object} equipment - Equipment object with mosaic settings
     * @param {number} centerRA - Center RA in degrees
     * @param {number} centerDec - Center Dec in degrees
     */
    aladinInstance.showMosaicPanels = function(equipment, centerRA, centerDec) {
        try {
            // Clear existing mosaic panels for this equipment only
            this.clearMosaicPanelsForEquipment(equipment.id);
            
            // Determine mosaic center position - use locked position if available
            let mosaicCenterRA, mosaicCenterDec;
                        if (equipment.isPositionLocked && equipment.lockedRA !== null && equipment.lockedDec !== null) {
                // Use locked position for mosaic center
                mosaicCenterRA = equipment.lockedRA;
                mosaicCenterDec = equipment.lockedDec;
                            } else {
                // Use provided center coordinates (current map position)
                mosaicCenterRA = centerRA;
                mosaicCenterDec = centerDec;
                            }

            // Store current equipment for updates
            this.activeMosaicEquipments.set(equipment.id, {
                ...equipment,
                centerRA: mosaicCenterRA,
                centerDec: mosaicCenterDec
            });
            
            // Calculate mosaic panels using the utility function
            const panels = calculateMosaicPanels(
                mosaicCenterRA, mosaicCenterDec, 
                equipment.fovWidthArcmin, equipment.fovHeightArcmin,
                equipment.mosaicPanelsX, equipment.mosaicPanelsY, 
                equipment.mosaicOverlapPercent, 
                equipment.rotationDegrees,
                equipment.mosaicUseRotator
            );

            if (!panels || panels.length === 0) {
                console.error('No panels calculated for mosaic');
                return;
            }
            
                                                
            // Create panels using calculated coordinates
            panels.forEach((panel, index) => {
                // Find progress for this panel if available
                let labelText = `P${panel.index + 1}`;
                let panelProgressData = null;
                if (equipment.panelProgress && Array.isArray(equipment.panelProgress)) {
                    const progress = equipment.panelProgress.find(p => p.panelNumber === (panel.index + 1));
                    if (progress) {
                        labelText += ` (${Math.round(progress.completionPercentage)}%)`;
                        panelProgressData = progress; // Store full progress data including imaging goals
                    }
                }
                
                const panelData = {
                    equipmentId: equipment.id,
                    widthArcmin: equipment.fovWidthArcmin,
                    heightArcmin: equipment.fovHeightArcmin,
                    rotationDegrees: panel.rotation,
                    color: equipment.overlayColor || equipment.color || '#00ff00',
                    labelText: labelText,
                    panelNumber: panel.index + 1,
                    panelProgress: panelProgressData, // Store progress data for click handling
                    isVisible: true,
                    isPositionLocked: equipment.isPositionLocked,
                    lockedRA: equipment.lockedRA,
                    lockedDec: equipment.lockedDec,
                    // Use calculated panel coordinates from utility function
                    panelRA: panel.ra,
                    panelDec: panel.dec,
                    centerRA: mosaicCenterRA,
                    centerDec: mosaicCenterDec,
                    // Store grid position for reference
                    gridX: panel.x,
                    gridY: panel.y
                };
                
                this.createMosaicPanel(panelData);
            });
            
            // Calculate total mosaic dimensions for label
            const overlapFactor = (100 - equipment.mosaicOverlapPercent) / 100;
            const spacingXArcmin = equipment.fovWidthArcmin * overlapFactor;
            const spacingYArcmin = equipment.fovHeightArcmin * overlapFactor;
            const totalWidthArcmin = (equipment.mosaicPanelsX - 1) * spacingXArcmin + equipment.fovWidthArcmin;
            const totalHeightArcmin = (equipment.mosaicPanelsY - 1) * spacingYArcmin + equipment.fovHeightArcmin;
            
            // Create overall mosaic label
            this.createMosaicLabel(equipment, totalWidthArcmin, totalHeightArcmin, equipment.rotationDegrees, mosaicCenterRA, mosaicCenterDec);
            
                        
            // Immediately update positions after creation
            this.updateMosaicPanelPositions();
            
            // Force a small delay to ensure DOM elements are ready, then update again
            setTimeout(() => {
                if (this.mosaicOverlays && this.mosaicOverlays.length > 0) {
                    this.updateMosaicPanelPositions();
                                    }
            }, 50);
            
        } catch (error) {
            console.error('Error creating mosaic panels:', error);
        }
    };

    /**
     * Creates a single mosaic panel element
     * @param {Object} panelData - Panel configuration data
     */
    aladinInstance.createMosaicPanel = function(panelData) {
        try {
            const container = this.aladin.aladinDiv;
            if (!container) {
                console.error('Aladin container not found');
                return;
            }

            // Create container div that holds both panel overlay and label (like FOV overlay)
            const panelContainer = document.createElement('div');
            panelContainer.className = 'mosaic-panel-container';
            panelContainer.style.position = 'absolute';
            panelContainer.style.pointerEvents = 'none';
            panelContainer.style.transformOrigin = 'center center';
            
            // Create panel element
            const panelElement = document.createElement('div');
            panelElement.id = `mosaic-panel-${panelData.id}`;
            panelElement.className = 'mosaic-panel';
            panelElement.style.cssText = `
                position: absolute;
                border: 2px solid ${panelData.color};
                background: transparent;
                pointer-events: none;
                z-index: 10;
                transform-origin: center center;
                display: flex;
                align-items: center;
                justify-content: center;
            `;
            
            // Create panel number label in center of panel
            const panelNumberElement = document.createElement('div');
            panelNumberElement.className = 'mosaic-panel-number';
            
            // Make clickable if we have imaging goals data
            const hasGoalsData = panelData.panelProgress && panelData.panelProgress.imagingGoals && panelData.panelProgress.imagingGoals.length > 0;
            
            panelNumberElement.style.cssText = `
                color: ${panelData.color};
                font-size: 16px;
                font-weight: bold;
                text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
                pointer-events: ${hasGoalsData ? 'auto' : 'none'};
                background: rgba(0,0,0,0.3);
                padding: 2px 6px;
                border-radius: 3px;
                ${hasGoalsData ? 'cursor: pointer;' : ''}
                ${hasGoalsData ? 'transition: background 0.2s;' : ''}
            `;
            panelNumberElement.textContent = panelData.labelText || '';
            
            // Add click handler if we have goals data
            if (hasGoalsData) {
                panelNumberElement.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this.showPanelGoalsTooltip(panelNumberElement, panelData.panelProgress, panelData.color);
                });
                
                // Add hover effect
                panelNumberElement.addEventListener('mouseenter', () => {
                    panelNumberElement.style.background = 'rgba(0,0,0,0.6)';
                });
                panelNumberElement.addEventListener('mouseleave', () => {
                    panelNumberElement.style.background = 'rgba(0,0,0,0.3)';
                });
            }
            
            // Add elements to container
            panelElement.appendChild(panelNumberElement);
            panelContainer.appendChild(panelElement);
            container.appendChild(panelContainer);
            
            // Store panel data
            this.mosaicOverlays.push({
                id: panelData.id,
                data: panelData,
                element: panelElement,
                container: panelContainer,
                panelNumberElement: panelNumberElement
            });
            
        } catch (error) {
            console.error('Error creating mosaic panel:', error);
        }
    };
    
    /**
     * Shows tooltip with panel imaging goals breakdown
     * @param {HTMLElement} anchorElement - Element to anchor tooltip to
     * @param {Object} panelProgress - Panel progress data with imaging goals
     * @param {string} color - Panel color
     */
    aladinInstance.showPanelGoalsTooltip = function(anchorElement, panelProgress, color) {
        // Remove any existing tooltip
        const existingTooltip = document.getElementById('mosaic-panel-tooltip');
        if (existingTooltip) {
            existingTooltip.remove();
        }
        
        // Create tooltip element
        const tooltip = document.createElement('div');
        tooltip.id = 'mosaic-panel-tooltip';
        tooltip.style.cssText = `
            position: fixed;
            background: rgba(0, 0, 0, 0.95);
            color: white;
            padding: 12px;
            border-radius: 6px;
            border: 2px solid ${color};
            font-size: 12px;
            z-index: 10000;
            pointer-events: auto;
            box-shadow: 0 4px 6px rgba(0,0,0,0.3);
            max-width: 300px;
        `;
        
        // Build tooltip content
        let content = `<div style="font-weight: bold; margin-bottom: 8px; font-size: 14px;">Panel ${panelProgress.panelNumber} - Imaging Goals</div>`;
        
        if (panelProgress.imagingGoals && panelProgress.imagingGoals.length > 0) {
            panelProgress.imagingGoals.forEach(goal => {
                const completedHours = (goal.completedTimeMinutes / 60).toFixed(1);
                const goalHours = (goal.goalTimeMinutes / 60).toFixed(1);
                const progressColor = goal.completionPercentage >= 100 ? '#22c55e' : '#9ca3af';
                const goalTypeLabel = goal.isCustomGoal ? 'Panel-specific' : 'Base';
                const goalTypeColor = goal.isCustomGoal ? '#60a5fa' : '#9ca3af';
                
                content += `
                    <div style="margin-bottom: 6px; display: flex; align-items: center; gap: 8px;">
                        <span style="width: 0.55rem; height: 0.55rem; border-radius: 999px; flex-shrink: 0; background: ${goalTypeColor};" title="${goalTypeLabel}"></span>
                        <span style="font-weight: 600; min-width: 80px;">${goal.filter} (${goal.exposureTimeSeconds}s)</span>
                        <span style="color: ${progressColor};">${completedHours}/${goalHours}h</span>
                        <span style="color: ${progressColor}; font-weight: 600;">(${Math.round(goal.completionPercentage)}%)</span>
                    </div>
                `;
            });
            content += `<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid ${color}; font-size: 11px; color: #9ca3af;">
                <span style="display:inline-block;width:0.55rem;height:0.55rem;border-radius:999px;background:#60a5fa;margin-right:4px;"></span> Panel-Specific &nbsp;
                <span style="display:inline-block;width:0.55rem;height:0.55rem;border-radius:999px;background:#9ca3af;margin-right:4px;"></span> Base Goal
            </div>`;
        } else {
            content += `<div style="color: #9ca3af;">No imaging goals</div>`;
        }
        
        tooltip.innerHTML = content;
        
        // Position tooltip near the anchor element
        const rect = anchorElement.getBoundingClientRect();
        tooltip.style.left = `${rect.right + 10}px`;
        tooltip.style.top = `${rect.top}px`;
        
        document.body.appendChild(tooltip);
        
        // Close on click outside
        const closeTooltip = (e) => {
            if (!tooltip.contains(e.target) && e.target !== anchorElement) {
                tooltip.remove();
                document.removeEventListener('click', closeTooltip);
            }
        };
        
        // Delay adding the close listener to avoid immediate close
        setTimeout(() => {
            document.addEventListener('click', closeTooltip);
        }, 100);
    };
    
    /**
     * Creates overall mosaic label below the mosaic
     * @param {Object} equipment - Equipment object
     * @param {number} totalWidthArcmin - Total mosaic width in arcminutes
     * @param {number} totalHeightArcmin - Total mosaic height in arcminutes
     * @param {number} rotationDegrees - Rotation in degrees
     */
    aladinInstance.createMosaicLabel = function(equipment, totalWidthArcmin, totalHeightArcmin, rotationDegrees, centerRA, centerDec) {
        try {
            const container = this.aladin.aladinDiv;
            if (!container) return;

            // Create mosaic label container
            const labelContainer = document.createElement('div');
            labelContainer.className = 'mosaic-label-container';
            labelContainer.style.position = 'absolute';
            labelContainer.style.pointerEvents = 'none';
            labelContainer.style.transformOrigin = 'center center';
            
            // Create label element
            const labelElement = document.createElement('div');
            labelElement.className = 'mosaic-overall-label';
            labelElement.style.cssText = `
                position: absolute;
                color: ${equipment.overlayColor || equipment.color || '#00ff00'};
                font-size: 12px;
                font-weight: bold;
                text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
                white-space: nowrap;
                pointer-events: none;
            `;
            
            // Format total mosaic FOV dimensions for display (same format as regular FOV)
            const formatMosaicFOV = (widthArcmin, heightArcmin) => {
                if (widthArcmin >= 60 && heightArcmin >= 60) {
                    return `${(widthArcmin/60).toFixed(1)}° × ${(heightArcmin/60).toFixed(1)}°`;
                } else {
                    return `${widthArcmin.toFixed(0)}' × ${heightArcmin.toFixed(0)}'`;
                }
            };
            
            // Create label text in same format as regular FOV overlay: "FOV: {FOV} • {PixelScaleString} • PA: {EffectivePAString}"
            // For mosaic, show total mosaic FOV instead of individual panel FOV
            const mosaicFOV = formatMosaicFOV(totalWidthArcmin, totalHeightArcmin);
            const pixelScaleString = equipment.pixelScaleString || `${((equipment.camera && equipment.camera.pixelSize) || 0).toFixed(1)}"/px`;
            
            // Calculate effective PA and equivalent angle (360 - PA)
            let effectivePA = (((equipment.camera && equipment.camera.skyPA) || 0) + (equipment.overlayRotationDegrees || 0));
            effectivePA = ((effectivePA % 360) + 360) % 360; // Normalize to 0-360
            const equivalentPA = effectivePA === 0 ? 0 : 360 - effectivePA;
            const paString = `${effectivePA.toFixed(1)}° (${equivalentPA.toFixed(1)}°)`;
            
            labelElement.textContent = `${equipment.nameOfEquipment || 'Equipment'} • FOV: ${mosaicFOV} • ${pixelScaleString} • PA: ${paString}`;
            
            labelContainer.appendChild(labelElement);
            container.appendChild(labelContainer);

            // Store label data
            this.mosaicOverlays.push({
                id: `mosaic-label-${equipment.id}`,
                data: {
                    equipmentId: equipment.id,
                    isLabel: true,
                    widthArcmin: totalWidthArcmin,
                    heightArcmin: totalHeightArcmin,
                    rotationDegrees: rotationDegrees,
                    userPA: equipment.rotationDegrees, // Store user-set PA for label rotation
                    color: equipment.overlayColor || equipment.color || '#00ff00',
                    isVisible: true,
                    isPositionLocked: equipment.isPositionLocked,
                    lockedRA: equipment.lockedRA,
                    lockedDec: equipment.lockedDec,
                    centerRA: equipment.isPositionLocked && equipment.lockedRA !== null && equipment.lockedDec !== null
                        ? equipment.lockedRA
                        : centerRA,
                    centerDec: equipment.isPositionLocked && equipment.lockedRA !== null && equipment.lockedDec !== null
                        ? equipment.lockedDec
                        : centerDec
                },
                element: labelElement,
                container: labelContainer
            });
            
        } catch (error) {
            console.error('Error creating mosaic label:', error);
        }
    };

    /**
     * Clears all mosaic panel overlays
     */
    aladinInstance.clearMosaicPanels = function() {
        try {
            this.mosaicOverlays.forEach(mosaicData => {
                if (mosaicData.container && mosaicData.container.parentNode) {
                    mosaicData.container.parentNode.removeChild(mosaicData.container);
                }
            });
            this.mosaicOverlays = [];
            this.activeMosaicEquipments.clear();
        } catch (error) {
            console.error('Error clearing mosaic panels:', error);
        }
    };

    /**
     * Clears mosaic panels for a specific equipment
     */
    aladinInstance.clearMosaicPanelsForEquipment = function(equipmentId) {
        try {
            // Remove overlays for this equipment
            this.mosaicOverlays = this.mosaicOverlays.filter(mosaicData => {
                if (mosaicData.data && mosaicData.data.equipmentId === equipmentId) {
                    // Remove from DOM
                    if (mosaicData.container && mosaicData.container.parentNode) {
                        mosaicData.container.parentNode.removeChild(mosaicData.container);
                    }
                    return false; // Remove from array
                }
                return true; // Keep in array
            });
            
            // Remove from active equipment map
            this.activeMosaicEquipments.delete(equipmentId);
        } catch (error) {
            console.error('Error clearing mosaic panels for equipment:', error);
        }
    };
    
    /**
     * Updates positions of all mosaic panels - treats entire mosaic as single unit
     */
    aladinInstance.updateMosaicPanelPositions = function() {
        try {
            // Early check if projection is ready
            try {
                const testCoords = this.aladin.pix2world(100, 100);
                if (!testCoords || !Array.isArray(testCoords)) return;
            } catch (e) {
                return; // Projection not ready, skip silently
            }
            
            const fov = this.aladin.getFov();
            const containerWidth = this.aladin.aladinDiv.offsetWidth;
            const containerHeight = this.aladin.aladinDiv.offsetHeight;
            
            const clampDec = (value) => Math.max(-89.9, Math.min(89.9, value));
            const projectOverlaySizePx = (centerRA, centerDec, widthArcmin, heightArcmin) => {
                const widthDegrees = (widthArcmin || 0) / 60;
                const heightDegrees = (heightArcmin || 0) / 60;
                const halfWidthDegrees = widthDegrees / 2;
                const halfHeightDegrees = heightDegrees / 2;
                const cosDec = Math.max(Math.cos((centerDec * Math.PI) / 180), 0.05);
                const halfWidthRaDegrees = halfWidthDegrees / cosDec;

                const projectDistancePx = (ra1, dec1, ra2, dec2) => {
                    try {
                        const start = this.aladin.world2pix(ra1, dec1);
                        const end = this.aladin.world2pix(ra2, dec2);
                        if (!start || !end || start.length < 2 || end.length < 2) {
                            return null;
                        }

                        return Math.hypot(end[0] - start[0], end[1] - start[1]);
                    } catch (error) {
                        return null;
                    }
                };

                const projectedWidthPx = projectDistancePx(
                    centerRA - halfWidthRaDegrees,
                    centerDec,
                    centerRA + halfWidthRaDegrees,
                    centerDec
                );
                const projectedHeightPx = projectDistancePx(
                    centerRA,
                    clampDec(centerDec - halfHeightDegrees),
                    centerRA,
                    clampDec(centerDec + halfHeightDegrees)
                );

                return {
                    widthPx: projectedWidthPx || (widthDegrees * (containerWidth / Math.max(fov[0], 0.0001))),
                    heightPx: projectedHeightPx || (heightDegrees * (containerHeight / Math.max(fov[1] || fov[0], 0.0001)))
                };
            };

            // Group overlays by equipment ID to handle each mosaic independently
            const overlaysByEquipment = new Map();
            
            this.mosaicOverlays.forEach(overlay => {
                const equipmentId = overlay.data && overlay.data.equipmentId;
                if (equipmentId) {
                    if (!overlaysByEquipment.has(equipmentId)) {
                        overlaysByEquipment.set(equipmentId, []);
                    }
                    overlaysByEquipment.get(equipmentId).push(overlay);
                }
            });

            // Process each equipment's overlays independently
            overlaysByEquipment.forEach((overlays, equipmentId) => {
                if (overlays.length === 0) return;
                
                // Get position lock info for this specific equipment
                const firstOverlay = overlays[0].data;
                const rotationDegrees = firstOverlay.rotationDegrees || 0;
                const isPositionLocked = firstOverlay.isPositionLocked || false;
                const lockedRA = firstOverlay.lockedRA || 0;
                const lockedDec = firstOverlay.lockedDec || 0;

                let mosaicCenterRA, mosaicCenterDec;
                const activeEquipment = this.activeMosaicEquipments.get(equipmentId);
                if (isPositionLocked && lockedRA !== 0 && lockedDec !== 0) {
                    mosaicCenterRA = lockedRA;
                    mosaicCenterDec = lockedDec;
                } else {
                    const center = this.aladin.getRaDec();
                    if (!center || center.length < 2) return;
                    mosaicCenterRA = center[0];
                    mosaicCenterDec = center[1];

                    // Recalculate unlocked mosaics directly from the live viewer center
                    // during every visual refresh so browser/WASM and MAUI use the same
                    // center-driven placement logic.
                    if (activeEquipment) {
                        const recalculatedPanels = calculateMosaicPanels(
                            mosaicCenterRA,
                            mosaicCenterDec,
                            activeEquipment.fovWidthArcmin,
                            activeEquipment.fovHeightArcmin,
                            activeEquipment.mosaicPanelsX,
                            activeEquipment.mosaicPanelsY,
                            activeEquipment.mosaicOverlapPercent,
                            activeEquipment.rotationDegrees,
                            activeEquipment.mosaicUseRotator
                        );

                        const panelLookup = new Map(
                            recalculatedPanels.map(panel => [panel.index + 1, panel])
                        );

                        overlays.forEach(overlay => {
                            if (!overlay?.data) {
                                return;
                            }

                            if (overlay.data.isLabel) {
                                overlay.data.centerRA = mosaicCenterRA;
                                overlay.data.centerDec = mosaicCenterDec;
                                overlay.data.rotationDegrees = activeEquipment.rotationDegrees;
                                overlay.data.userPA = activeEquipment.rotationDegrees;
                                return;
                            }

                            const panelNumber = overlay.data.panelNumber;
                            const panel = panelLookup.get(panelNumber);
                            if (!panel) {
                                return;
                            }

                            overlay.data.panelRA = panel.ra;
                            overlay.data.panelDec = panel.dec;
                            overlay.data.rotationDegrees = panel.rotation;
                            overlay.data.centerRA = mosaicCenterRA;
                            overlay.data.centerDec = mosaicCenterDec;
                        });

                        this.activeMosaicEquipments.set(equipmentId, {
                            ...activeEquipment,
                            centerRA: mosaicCenterRA,
                            centerDec: mosaicCenterDec
                        });
                    }
                }

                // Update all overlays for this equipment
                overlays.forEach(overlay => {
                    const element = overlay.element;
                    const container = overlay.container;
                    const data = overlay.data;
                    const labelElement = overlay.labelElement;
                    const panelNumberElement = overlay.panelNumberElement;

                    if (!element || !data || !container) return;

                // Handle mosaic label - use mosaic center position
                if (data.isLabel) {
                    let mosaicCenterX, mosaicCenterY;
                    const labelCenterRA = data.centerRA !== undefined ? data.centerRA : mosaicCenterRA;
                    const labelCenterDec = data.centerDec !== undefined ? data.centerDec : mosaicCenterDec;
                    
                    if (isPositionLocked) {
                        // Convert locked coordinates to screen position
                        try {
                            const screenPos = this.aladin.world2pix(labelCenterRA, labelCenterDec);
                            if (screenPos && screenPos.length >= 2) {
                                mosaicCenterX = screenPos[0];
                                mosaicCenterY = screenPos[1];
                            } else {
                                // Hide label when mosaic is on back side of globe
                                container.style.display = 'none';
                                return; // Skip further processing for this overlay
                            }
                        } catch (e) {
                            // Hide label when conversion fails
                            container.style.display = 'none';
                            return; // Skip further processing for this overlay
                        }
                    } else {
                        // Use screen center when not locked
                        mosaicCenterX = containerWidth / 2;
                        mosaicCenterY = containerHeight / 2;
                    }
                    
                    // Position the container at the mosaic center
                    container.style.left = `${mosaicCenterX}px`;
                    container.style.top = `${mosaicCenterY}px`;
                    container.style.transform = `rotate(${data.userPA !== undefined ? data.userPA : rotationDegrees}deg)`;
                    container.style.transformOrigin = 'center center';
                    container.style.display = data.isVisible ? 'block' : 'none';
                    
                    // Position label within container based on total mosaic size
                    if (element) {
                        const totalMosaicSizePx = projectOverlaySizePx(labelCenterRA,
                                                                       labelCenterDec,
                                                                       data.widthArcmin,
                                                                       data.heightArcmin);
                        const totalHeightPx = totalMosaicSizePx.heightPx;
                        element.style.left = '50%';
                        element.style.color = data.color;
                        
                        // Position label based on rotation to keep text readable
                        const rotation = rotationDegrees % 360;
                        const normalizedRotation = rotation < 0 ? rotation + 360 : rotation;
                        
                        // If rotation would make text upside down (90° to 270°), flip to opposite side
                        if (normalizedRotation > 90 && normalizedRotation < 270) {
                            element.style.top = `${-totalHeightPx / 2 - 25}px`; // 25px above mosaic top edge
                            element.style.transform = `translateX(-50%) rotate(180deg)`;
                        } else {
                            element.style.top = `${totalHeightPx / 2 + 10}px`; // 10px below mosaic bottom edge
                            element.style.transform = `translateX(-50%)`;
                        }
                    }
                    return;
                }

                // Calculate panel position based on current mosaic center
                let panelCenterX, panelCenterY, actualPanelRA, actualPanelDec;
                
                if (data.panelRA !== undefined && data.panelDec !== undefined) {
                    try {
                        const screenPos = this.aladin.world2pix(data.panelRA, data.panelDec);
                        if (screenPos && screenPos.length >= 2) {
                            panelCenterX = screenPos[0];
                            panelCenterY = screenPos[1];
                            actualPanelRA = data.panelRA;
                            actualPanelDec = data.panelDec;
                        } else {
                            // Hide panel when it's on back side of globe
                            container.style.display = 'none';
                            return; // Skip further processing for this overlay
                        }
                    } catch (e) {
                        // Hide panel when conversion fails
                        container.style.display = 'none';
                        return; // Skip further processing for this overlay
                    }
                } else {
                    container.style.display = 'none';
                    return;
                }

                const panelSizePx = projectOverlaySizePx(actualPanelRA ?? mosaicCenterRA,
                                                         actualPanelDec ?? mosaicCenterDec,
                                                         data.widthArcmin,
                                                         data.heightArcmin);
                const overlayWidthPx = panelSizePx.widthPx;
                const overlayHeightPx = panelSizePx.heightPx;
                
                // Position the container at the calculated panel center
                container.style.left = `${panelCenterX}px`;
                container.style.top = `${panelCenterY}px`;
                // Apply rotation - use individual panel rotation if available, otherwise mosaic rotation
                const panelRotation = data.rotationDegrees !== undefined ? data.rotationDegrees : rotationDegrees;
                container.style.transform = `rotate(${panelRotation}deg)`;
                container.style.transformOrigin = 'center center'; // Rotate around panel center
                container.style.display = data.isVisible ? 'block' : 'none';
                
                // Position panel overlay within container (centered, no individual rotation)
                element.style.width = `${overlayWidthPx}px`;
                element.style.height = `${overlayHeightPx}px`;
                element.style.left = `${-overlayWidthPx / 2}px`;
                element.style.top = `${-overlayHeightPx / 2}px`;
                element.style.transform = 'none'; // No individual panel rotation
                element.style.borderColor = data.color;
                
                // Position label within container
                if (labelElement) {
                    labelElement.textContent = data.labelText || '';
                    labelElement.style.left = '50%';
                    labelElement.style.color = data.color;
                    
                    // Position label based on rotation to keep text readable
                    const rotation = rotationDegrees % 360;
                    const normalizedRotation = rotation < 0 ? rotation + 360 : rotation;
                    
                    // If rotation would make text upside down (90° to 270°), flip to opposite side
                    if (normalizedRotation > 90 && normalizedRotation < 270) {
                        labelElement.style.top = `${-overlayHeightPx / 2 - 25}px`; // 25px above panel top edge
                        labelElement.style.transform = `translateX(-50%) rotate(180deg)`;
                    } else {
                        labelElement.style.top = `${overlayHeightPx / 2 + 10}px`; // 10px below panel bottom edge
                        labelElement.style.transform = `translateX(-50%)`;
                    }
                }
                });
            });
        } catch (error) {
            console.error('Error updating mosaic panel positions:', error);
        }
    };
    
    /**
     * Exports mosaic panel coordinates to CSV
     * @param {string} equipmentId - ID of the equipment to export
     * @returns {string} CSV data or null if not found
     */
    aladinInstance.exportMosaicPanelsCSV = function(equipmentId = null) {
        try {
            if (!equipmentId || !this.activeMosaicEquipments.has(equipmentId)) {
                console.warn('No active mosaic data found for equipment ID:', equipmentId);
                return null;
            }
            
            const center = this.aladin.getRaDec();
            if (!center || center.length < 2) {
                console.warn('Could not get current position for export');
                return null;
            }
            
            // Get equipment data for the specified equipment ID
            const equipment = this.activeMosaicEquipments.get(equipmentId);
            const panels = calculateMosaicPanels(
                center[0], center[1],
                equipment.fovWidthArcmin, equipment.fovHeightArcmin,
                equipment.mosaicPanelsX, equipment.mosaicPanelsY,
                equipment.mosaicOverlapPercent,
                equipment.rotationDegrees,
                equipment.mosaicUseRotator
            );
            
            if (!panels || panels.length === 0) {
                console.warn('No panel data available for export');
                return null;
            }
            
            const csvData = formatMosaicPanelsCSV(panels, equipment.nameOfEquipment || 'Equipment');
            
            const equipmentName = (equipment.nameOfEquipment || 'Equipment').replace(/[^a-zA-Z0-9]/g, '_');
            const filename = `mosaic_${equipmentName}_${new Date().toISOString().slice(0, 10)}.csv`;
            downloadCSV(csvData, filename);
            
            return csvData;
            
        } catch (error) {
            console.error('Error exporting mosaic panels CSV:', error);
            return null;
        }
    };
    
    /**
     * Gets mosaic panel data for a specific equipment
     * @param {string} equipmentId - ID of the equipment
     * @returns {Array|null} Panel data or null if not found
     */
    aladinInstance.getMosaicPanels = function(equipmentId) {
        const mosaicData = this.mosaicOverlays.find(m => m.equipmentId === equipmentId);
        return mosaicData ? mosaicData.panels : null;
    };
    
    
    /**
     * Gets current mosaic panels data for coordinate display
     * @returns {Array|null} Array of panel data or null if no mosaic active
     */
    aladinInstance.getMosaicPanelsData = function(equipmentId = null) {
        try {
            if (!equipmentId || !this.activeMosaicEquipments.has(equipmentId)) {
                return null;
            }
            
            const center = this.aladin.getRaDec();
            if (!center || center.length < 2) {
                return null;
            }
            
            // Get equipment data for the specified equipment ID
            const equipment = this.activeMosaicEquipments.get(equipmentId);
            const panels = calculateMosaicPanels(
                center[0], center[1],
                equipment.fovWidthArcmin, equipment.fovHeightArcmin,
                equipment.mosaicPanelsX, equipment.mosaicPanelsY,
                equipment.mosaicOverlapPercent,
                equipment.rotationDegrees,
                equipment.mosaicUseRotator
            );
            
            // Convert to the format expected by the UI
            return panels.map(panel => ({
                PanelNumber: panel.index + 1,
                RA: panel.ra,
                Dec: panel.dec,
                PA: panel.imagingPA !== undefined ? panel.imagingPA : panel.rotation // Use imagingPA for coordinate list, fallback to rotation for compatibility
            }));
            
        } catch (error) {
            console.error('Error getting mosaic panels data:', error);
            return null;
        }
    };
}

// Global functions for Blazor integration
window.showMosaicPanels = function(equipment, centerRA, centerDec) {
        if (window.aladinInstance && window.aladinInstance.showMosaicPanels) {
        window.aladinInstance.showMosaicPanels(equipment, centerRA, centerDec);
    } else {
        console.error('Aladin instance or showMosaicPanels method not available');
    }
};

window.clearMosaicPanels = function() {
        if (window.aladinInstance && window.aladinInstance.clearMosaicPanels) {
        window.aladinInstance.clearMosaicPanels();
    } else {
        console.error('Aladin instance or clearMosaicPanels method not available');
    }
};

window.updateEquipmentOverlayState = function(state) {
    if (!window.aladinInstance) {
        console.error('Aladin instance not available');
        return;
    }

    const overlayState = state || {};
    const fovOverlays = Array.isArray(overlayState.fovOverlays) ? overlayState.fovOverlays : [];
    const mosaicEquipments = Array.isArray(overlayState.mosaicEquipments) ? overlayState.mosaicEquipments : [];
    const centerRA = Number.isFinite(overlayState.centerRA) ? overlayState.centerRA : 0;
    const centerDec = Number.isFinite(overlayState.centerDec) ? overlayState.centerDec : 0;

    if (window.aladinInstance.updateFovOverlays) {
        window.aladinInstance.updateFovOverlays(fovOverlays);
    }

    if (window.aladinInstance.clearMosaicPanels) {
        window.aladinInstance.clearMosaicPanels();
    }

    if (window.aladinInstance.showMosaicPanels) {
        let liveCenterRA = centerRA;
        let liveCenterDec = centerDec;

        try {
            const currentCenter = window.aladinInstance?.aladin?.getRaDec?.();
            if (currentCenter && currentCenter.length >= 2 &&
                Number.isFinite(currentCenter[0]) &&
                Number.isFinite(currentCenter[1])) {
                liveCenterRA = currentCenter[0];
                liveCenterDec = currentCenter[1];
            }
        } catch (error) {
            console.warn('Falling back to Blazor-synced center for mosaic refresh:', error);
        }

        mosaicEquipments.forEach(equipment => {
            window.aladinInstance.showMosaicPanels(equipment, liveCenterRA, liveCenterDec);
        });
    }
};

window.exportMosaicPanelsCSV = function(equipmentId) {
        if (window.aladinInstance && window.aladinInstance.exportMosaicPanelsCSV) {
        return window.aladinInstance.exportMosaicPanelsCSV(equipmentId);
    } else {
        console.error('Aladin instance or exportMosaicPanelsCSV method not available');
    }
    return null;
};

window.getMosaicPanelsData = function(equipmentId = null) {
        if (window.aladinInstance && window.aladinInstance.getMosaicPanelsData) {
        return window.aladinInstance.getMosaicPanelsData(equipmentId);
    } else {
        console.error('Aladin instance or getMosaicPanelsData method not available');
        return null;
    }
};

// Make extendAladinWithMosaic globally available
window.extendAladinWithMosaic = extendAladinWithMosaic;

// Export functions for module use
if (typeof module !== 'undefined' && module.exports) {
    module.exports = {
        calculateMosaicPanels,
        formatMosaicPanelsCSV,
        downloadCSV,
        extendAladinWithMosaic
    };
}

// Also make calculateMosaicPanels globally available for use outside SkyAtlas
if (typeof window !== 'undefined') {
    window.calculateMosaicPanels = calculateMosaicPanels;
    window.formatMosaicPanelsCSV = formatMosaicPanelsCSV;
    window.downloadCSV = downloadCSV;
}
