// View Models for SPORE UI Components

// Cluster View Model
class ClusterViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            members: [],
            primaryNode: null,
            totalNodes: 0,
            clientInitialized: false,
            isLoading: false,
            error: null,
            expandedCards: new Map(),
            activeTabs: new Map(), // Store active tab for each node
            lastUpdateTime: null,
            onlineNodes: 0
        });
        
        // Initialize cluster status after a short delay to allow components to subscribe
        setTimeout(() => {
            this.updatePrimaryNodeDisplay();
        }, 100);

        // Set up WebSocket listeners for real-time updates
        this.setupWebSocketListeners();
    }

    // Set up WebSocket event listeners
    setupWebSocketListeners() {
        if (!window.wsClient) {
            logger.warn('WebSocket client not available');
            return;
        }

        // Listen for cluster updates
        window.wsClient.on('clusterUpdate', (data) => {
            logger.debug('ClusterViewModel: Received WebSocket cluster update:', data);

            // Update members from WebSocket data
            if (data.members && Array.isArray(data.members)) {
                const onlineNodes = data.members.filter(m => m && m.status && m.status.toUpperCase() === 'ACTIVE').length;

                logger.debug(`ClusterViewModel: Updating members from ${this.get('members')?.length || 0} to ${data.members.length} members`);

                this.batchUpdate({
                    members: data.members,
                    lastUpdateTime: data.timestamp || new Date().toISOString(),
                    onlineNodes: onlineNodes
                });

                // Update primary node display if it changed
                if (data.primaryNode !== this.get('primaryNode')) {
                    logger.debug(`ClusterViewModel: Primary node changed from ${this.get('primaryNode')} to ${data.primaryNode}`);
                    this.set('primaryNode', data.primaryNode);
                    this.set('totalNodes', data.totalNodes || 0);
                }
            } else {
                logger.warn('ClusterViewModel: Received cluster update but no valid members array:', data);
            }
        });

        // Listen for node discovery events
        window.wsClient.on('nodeDiscovery', (data) => {
            logger.debug('ClusterViewModel: Received WebSocket node discovery event:', data);

            if (data.action === 'discovered') {
                // A new node was discovered - trigger a cluster update
                setTimeout(() => {
                    this.updateClusterMembers();
                }, 500);
            } else if (data.action === 'stale') {
                // A node became stale - trigger a cluster update
                setTimeout(() => {
                    this.updateClusterMembers();
                }, 500);
            }
        });

        // Listen for connection status changes
        window.wsClient.on('connected', () => {
            logger.debug('ClusterViewModel: WebSocket connected');
            // Optionally trigger an immediate update when connection is restored
            setTimeout(() => {
                this.updateClusterMembers();
            }, 1000);
        });

        window.wsClient.on('disconnected', () => {
            logger.debug('ClusterViewModel: WebSocket disconnected');
        });
    }

    // Update cluster members
    async updateClusterMembers() {
        try {
            logger.debug('ClusterViewModel: updateClusterMembers called');

            // Check if we have recent WebSocket data (within last 30 seconds)
            const lastUpdateTime = this.get('lastUpdateTime');
            const now = new Date();
            const websocketDataAge = lastUpdateTime ? (now - new Date(lastUpdateTime)) : Infinity;

            // If WebSocket data is recent, skip REST API call to avoid conflicts
            if (websocketDataAge < 30000 && this.get('members').length > 0) {
                logger.debug('ClusterViewModel: Using recent WebSocket data, skipping REST API call');
                return;
            }

            // Store current UI state before update
            const currentUIState = this.getAllUIState();
            const currentExpandedCards = this.get('expandedCards');
            const currentActiveTabs = this.get('activeTabs');

            this.set('isLoading', true);
            this.set('error', null);

            logger.debug('ClusterViewModel: Fetching cluster members...');
            const response = await window.apiClient.getClusterMembers();
            logger.debug('ClusterViewModel: Got response:', response);

            const members = response.members || [];
            const onlineNodes = Array.isArray(members)
                ? members.filter(m => m && m.status && m.status.toUpperCase() === 'ACTIVE').length
                : 0;

            // Use batch update
            this.batchUpdate({
                members: members,
                lastUpdateTime: new Date().toISOString(),
                onlineNodes: onlineNodes
            });

            // Restore expanded cards and active tabs
            this.set('expandedCards', currentExpandedCards);
            this.set('activeTabs', currentActiveTabs);

            // Update primary node display
            logger.debug('ClusterViewModel: Updating primary node display...');
            await this.updatePrimaryNodeDisplay();

        } catch (error) {
            console.error('ClusterViewModel: Failed to fetch cluster members:', error);
            this.set('error', error.message);
        } finally {
            this.set('isLoading', false);
            logger.debug('ClusterViewModel: updateClusterMembers completed');
        }
    }

    // Update primary node display
    async updatePrimaryNodeDisplay() {
        try {
            const discoveryInfo = await window.apiClient.getDiscoveryInfo();
            
            // Use batch update
            const updates = {};
            
            if (discoveryInfo.primaryNode) {
                updates.primaryNode = discoveryInfo.primaryNode;
                updates.clientInitialized = discoveryInfo.clientInitialized;
                updates.totalNodes = discoveryInfo.totalNodes;
            } else if (discoveryInfo.totalNodes > 0) {
                updates.primaryNode = discoveryInfo.nodes[0]?.ip;
                updates.clientInitialized = false;
                updates.totalNodes = discoveryInfo.totalNodes;
            } else {
                updates.primaryNode = null;
                updates.clientInitialized = false;
                updates.totalNodes = 0;
            }
            
            this.batchUpdate(updates);
            
        } catch (error) {
            console.error('Failed to fetch discovery info:', error);
            this.set('error', error.message);
        }
    }

    // Select random primary node
    async selectRandomPrimaryNode() {
        try {
            const result = await window.apiClient.selectRandomPrimaryNode();
            
            if (result.success) {
                // Update the display after a short delay
                setTimeout(() => {
                    this.updatePrimaryNodeDisplay();
                }, 1500);
                
                return result;
            } else {
                throw new Error(result.message || 'Random selection failed');
            }
        } catch (error) {
            console.error('Failed to select random primary node:', error);
            throw error;
        }
    }

    // Store expanded card state
    storeExpandedCard(memberIp, content) {
        const expandedCards = this.get('expandedCards');
        expandedCards.set(memberIp, content);
        this.set('expandedCards', expandedCards);
        
        // Also store in UI state for persistence
        this.setUIState(`expanded_${memberIp}`, content);
    }

    // Get expanded card state
    getExpandedCard(memberIp) {
        const expandedCards = this.get('expandedCards');
        return expandedCards.get(memberIp);
    }

    // Clear expanded card state
    clearExpandedCard(memberIp) {
        const expandedCards = this.get('expandedCards');
        expandedCards.delete(memberIp);
        this.set('expandedCards', expandedCards);
        
        // Also clear from UI state
        this.clearUIState(`expanded_${memberIp}`);
    }

    // Store active tab for a specific node
    storeActiveTab(memberIp, tabName) {
        const activeTabs = this.get('activeTabs');
        activeTabs.set(memberIp, tabName);
        this.set('activeTabs', activeTabs);
        
        // Also store in UI state for persistence
        this.setUIState(`activeTab_${memberIp}`, tabName);
    }

    // Get active tab for a specific node
    getActiveTab(memberIp) {
        const activeTabs = this.get('activeTabs');
        return activeTabs.get(memberIp) || 'status'; // Default to 'status' tab
    }

    // Check if data has actually changed to avoid unnecessary updates
    hasDataChanged(newData, dataType) {
        const currentData = this.get(dataType);
        
        if (Array.isArray(newData) && Array.isArray(currentData)) {
            if (newData.length !== currentData.length) return true;
            
            // Compare each member's key properties
            return newData.some((newMember, index) => {
                const currentMember = currentData[index];
                return !currentMember || 
                       newMember.ip !== currentMember.ip ||
                       newMember.status !== currentMember.status ||
                       newMember.latency !== currentMember.latency;
            });
        }
        
        return newData !== currentData;
    }

    // Smart update that only updates changed data
    async smartUpdate() {
        try {
            logger.debug('ClusterViewModel: Performing smart update...');
            
            // Fetch new data
            const response = await window.apiClient.getClusterMembers();
            const newMembers = response.members || [];
            
            // Check if members data has actually changed
            if (this.hasDataChanged(newMembers, 'members')) {
                logger.debug('ClusterViewModel: Members data changed, updating...');
                await this.updateClusterMembers();
            } else {
                logger.debug('ClusterViewModel: Members data unchanged, skipping update');
                // Still update primary node display as it might have changed
                await this.updatePrimaryNodeDisplay();
            }
            
        } catch (error) {
            console.error('ClusterViewModel: Smart update failed:', error);
            this.set('error', error.message);
        }
    }
}

// Node Details View Model
class NodeDetailsViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            nodeStatus: null,
            tasks: [],
            isLoading: false,
            error: null,
            activeTab: 'status',
            nodeIp: null,
            endpoints: null,
            tasksSummary: null,
            monitoringResources: null
        });
    }

    // Load node details
    async loadNodeDetails(ip) {
        try {
            // Store current UI state
            const currentActiveTab = this.get('activeTab');
            
            this.set('isLoading', true);
            this.set('error', null);
            this.set('nodeIp', ip);
            
            const nodeStatus = await window.apiClient.getNodeStatus(ip);
            
            // Use batch update
            this.batchUpdate({
                nodeStatus: nodeStatus
            });
            
            // Restore active tab
            this.set('activeTab', currentActiveTab);
            
            // Load tasks data
            await this.loadTasksData();
            
            // Load endpoints data
            await this.loadEndpointsData();
            
            // Load monitoring resources data
            await this.loadMonitoringResources();
            
        } catch (error) {
            console.error('Failed to load node details:', error);
            this.set('error', error.message);
        } finally {
            this.set('isLoading', false);
        }
    }

    // Load tasks data
    async loadTasksData() {
        try {
            const ip = this.get('nodeIp');
            const response = await window.apiClient.getTasksStatus(ip);
            this.set('tasks', (response && Array.isArray(response.tasks)) ? response.tasks : []);
            this.set('tasksSummary', response && response.summary ? response.summary : null);
        } catch (error) {
            console.error('Failed to load tasks:', error);
            this.set('tasks', []);
            this.set('tasksSummary', null);
        }
    }

    // Load endpoints data
    async loadEndpointsData() {
        try {
            const ip = this.get('nodeIp');
            const response = await window.apiClient.getEndpoints(ip);
            // Handle both real API (wrapped in endpoints) and mock API (direct array)
            const endpointsData = (response && response.endpoints) ? response : { endpoints: response };
            this.set('endpoints', endpointsData || null);
        } catch (error) {
            console.error('Failed to load endpoints:', error);
            this.set('endpoints', null);
        }
    }

    // Load monitoring resources data
    async loadMonitoringResources() {
        try {
            const ip = this.get('nodeIp');
            const response = await window.apiClient.getMonitoringResources(ip);
            // Handle both real API (wrapped in data) and mock API (direct response)
            const monitoringData = (response && response.data) ? response.data : response;
            this.set('monitoringResources', monitoringData);
        } catch (error) {
            console.error('Failed to load monitoring resources:', error);
            this.set('monitoringResources', null);
        }
    }

    // Invoke an endpoint against this node
    async callEndpoint(method, uri, params) {
        const ip = this.get('nodeIp');
        return window.apiClient.callEndpoint({ ip, method, uri, params });
    }

    // Set active tab with state persistence
    setActiveTab(tabName) {
        logger.debug('NodeDetailsViewModel: Setting activeTab to:', tabName);
        this.set('activeTab', tabName);
        
        // Store in UI state for persistence
        this.setUIState('activeTab', tabName);
    }

    // Upload firmware
    async uploadFirmware(file, nodeIp) {
        try {
            const result = await window.apiClient.uploadFirmware(file, nodeIp);
            return result;
        } catch (error) {
            console.error('Firmware upload failed:', error);
            throw error;
        }
    }
}

// Firmware View Model
class FirmwareViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            selectedFile: null,
            targetType: 'all',
            specificNode: null,
            availableNodes: [],
            uploadProgress: null,
            uploadResults: [],
            isUploading: false,
            selectedLabels: [],
            availableLabels: []
        });
    }

    // Set selected file
    setSelectedFile(file) {
        this.set('selectedFile', file);
    }

    // Set target type
    setTargetType(type) {
        this.set('targetType', type);
        // Clear any previously selected labels when switching options
        const currentLabels = this.get('selectedLabels') || [];
        if (currentLabels.length > 0) {
            this.set('selectedLabels', []);
        }
    }

    // Set specific node
    setSpecificNode(nodeIp) {
        this.set('specificNode', nodeIp);
    }

    // Update available nodes
    updateAvailableNodes(nodes) {
        this.set('availableNodes', nodes);
        // Compute availableLabels as unique key=value pairs from nodes' labels
        try {
            const labelSet = new Set();
            (nodes || []).forEach(n => {
                const labels = n && n.labels ? n.labels : {};
                Object.entries(labels).forEach(([k, v]) => {
                    labelSet.add(`${k}=${v}`);
                });
            });
            const availableLabels = Array.from(labelSet).sort((a, b) => a.localeCompare(b));
            this.set('availableLabels', availableLabels);
            // Prune selected labels that are no longer available
            const selected = this.get('selectedLabels') || [];
            const pruned = selected.filter(x => availableLabels.includes(x));
            if (pruned.length !== selected.length) {
                this.set('selectedLabels', pruned);
            }
        } catch (_) {
            this.set('availableLabels', []);
            this.set('selectedLabels', []);
        }
    }

    // Start upload
    startUpload() {
        this.set('isUploading', true);
        this.set('uploadProgress', {
            current: 0,
            total: 0,
            status: 'Preparing...'
        });
        this.set('uploadResults', []);
    }

    // Update upload progress
    updateUploadProgress(current, total, status) {
        this.set('uploadProgress', {
            current,
            total,
            status
        });
    }

    // Add upload result
    addUploadResult(result) {
        const results = this.get('uploadResults');
        results.push(result);
        this.set('uploadResults', results);
    }

    // Complete upload
    completeUpload() {
        this.set('isUploading', false);
    }

    // Reset upload state
    resetUploadState() {
        this.set('selectedFile', null);
        this.set('uploadProgress', null);
        this.set('uploadResults', []);
        this.set('isUploading', false);
    }

    // Set selected labels
    setSelectedLabels(labels) {
        this.set('selectedLabels', Array.isArray(labels) ? labels : []);
    }

    // Return nodes matching ALL selected label pairs
    getAffectedNodesByLabels() {
        const selected = this.get('selectedLabels') || [];
        if (!selected.length) return [];
        const selectedPairs = selected.map(s => {
            const idx = String(s).indexOf('=');
            return idx > -1 ? { key: s.slice(0, idx), value: s.slice(idx + 1) } : null;
        }).filter(Boolean);
        const nodes = this.get('availableNodes') || [];
        return nodes.filter(n => {
            const labels = n && n.labels ? n.labels : {};
            return selectedPairs.every(p => String(labels[p.key]) === String(p.value));
        });
    }

    // Check if deploy button should be enabled
    isDeployEnabled() {
        const hasFile = this.get('selectedFile') !== null;
        const availableNodes = this.get('availableNodes');
        const hasAvailableNodes = availableNodes && availableNodes.length > 0;
        
        let isValidTarget = false;
        if (this.get('targetType') === 'all') {
            isValidTarget = hasAvailableNodes;
        } else if (this.get('targetType') === 'specific') {
            isValidTarget = hasAvailableNodes && this.get('specificNode');
        } else if (this.get('targetType') === 'labels') {
            const affected = this.getAffectedNodesByLabels();
            isValidTarget = hasAvailableNodes && (this.get('selectedLabels') || []).length > 0 && affected.length > 0;
        }
        
        return hasFile && isValidTarget && !this.get('isUploading');
    }
}

// Navigation View Model
class NavigationViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            activeView: 'cluster',
            views: ['cluster', 'topology', 'firmware', 'monitoring']
        });
    }

    // Set active view
    setActiveView(viewName) {
        this.set('activeView', viewName);
    }

    // Get active view
    getActiveView() {
        return this.get('activeView');
    }
} 

// Topology View Model for network topology visualization
class TopologyViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            nodes: [],
            links: [],
            isLoading: false,
            error: null,
            lastUpdateTime: null,
            selectedNode: null
        });
    }

    // Update network topology data
    async updateNetworkTopology() {
        try {
            logger.debug('TopologyViewModel: updateNetworkTopology called');
            
            this.set('isLoading', true);
            this.set('error', null);
            
            // Get cluster members from the primary node
            const response = await window.apiClient.getClusterMembers();
            logger.debug('TopologyViewModel: Got cluster members response:', response);
            
            const members = response.members || [];
            
            // Build enhanced graph data with actual node connections
            const { nodes, links } = await this.buildEnhancedGraphData(members);
            
            this.batchUpdate({
                nodes: nodes,
                links: links,
                lastUpdateTime: new Date().toISOString()
            });
            
        } catch (error) {
            console.error('TopologyViewModel: Failed to fetch network topology:', error);
            this.set('error', error.message);
        } finally {
            this.set('isLoading', false);
            logger.debug('TopologyViewModel: updateNetworkTopology completed');
        }
    }

    // Build enhanced graph data with actual node connections
    async buildEnhancedGraphData(members) {
        const nodes = [];
        const links = [];
        const nodeConnections = new Map();
        
        // Create nodes from members
        members.forEach((member, index) => {
            if (member && member.ip) {
                nodes.push({
                    id: member.ip,
                    hostname: member.hostname || member.ip,
                    ip: member.ip,
                    status: member.status || 'UNKNOWN',
                    latency: member.latency || 0,
                    // Preserve both legacy 'resources' and preferred 'labels'
                    labels: (member.labels && typeof member.labels === 'object') ? member.labels : (member.resources || {}),
                    resources: member.resources || {},
                    x: Math.random() * 1200 + 100,   // Better spacing for 1400px width
                    y: Math.random() * 800 + 100     // Better spacing for 1000px height
                });
            }
        });
        
        // Try to get cluster members from each node to build actual connections
        for (const node of nodes) {
            try {
                const nodeResponse = await window.apiClient.getClusterMembersFromNode(node.ip);
                if (nodeResponse && nodeResponse.members) {
                    nodeConnections.set(node.ip, nodeResponse.members);
                }
            } catch (error) {
                console.warn(`Failed to get cluster members from node ${node.ip}:`, error);
                // Continue with other nodes
            }
        }
        
        // Build links based on actual connections
        for (const [sourceIp, sourceMembers] of nodeConnections) {
            for (const targetMember of sourceMembers) {
                if (targetMember.ip && targetMember.ip !== sourceIp) {
                    // Check if we already have this link
                    const existingLink = links.find(link => 
                        (link.source === sourceIp && link.target === targetMember.ip) ||
                        (link.source === targetMember.ip && link.target === sourceIp)
                    );
                    
                    if (!existingLink) {
                        const sourceNode = nodes.find(n => n.id === sourceIp);
                        const targetNode = nodes.find(n => n.id === targetMember.ip);
                        
                        if (sourceNode && targetNode) {
                            const latency = targetMember.latency || this.estimateLatency(sourceNode, targetNode);
                            
                            links.push({
                                source: sourceIp,
                                target: targetMember.ip,
                                latency: latency,
                                sourceNode: sourceNode,
                                targetNode: targetNode,
                                bidirectional: true
                            });
                        }
                    }
                }
            }
        }
        
        // If no actual connections found, create a basic mesh
        if (links.length === 0) {
            logger.debug('TopologyViewModel: No actual connections found, creating basic mesh');
            for (let i = 0; i < nodes.length; i++) {
                for (let j = i + 1; j < nodes.length; j++) {
                    const sourceNode = nodes[i];
                    const targetNode = nodes[j];
                    
                    const estimatedLatency = this.estimateLatency(sourceNode, targetNode);
                    
                    links.push({
                        source: sourceNode.id,
                        target: targetNode.id,
                        latency: estimatedLatency,
                        sourceNode: sourceNode,
                        targetNode: targetNode,
                        bidirectional: true
                    });
                }
            }
        }
        
        return { nodes, links };
    }

    // Estimate latency between two nodes
    estimateLatency(sourceNode, targetNode) {
        // Simple estimation - in a real implementation, you'd get actual measurements
        const baseLatency = 5; // Base latency in ms
        const randomVariation = Math.random() * 10; // Random variation
        return Math.round(baseLatency + randomVariation);
    }

    // Select a node in the graph
    selectNode(nodeId) {
        this.set('selectedNode', nodeId);
    }

    // Clear node selection
    clearSelection() {
        this.set('selectedNode', null);
    }
}

// Monitoring View Model for cluster resource monitoring
class MonitoringViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            clusterMembers: [],
            nodeResources: new Map(), // Map of node IP -> resource data
            clusterSummary: {
                totalCpu: 0,
                totalMemory: 0,
                totalStorage: 0,
                totalNodes: 0,
                availableCpu: 0,
                availableMemory: 0,
                availableStorage: 0
            },
            isLoading: false,
            lastUpdated: null,
            error: null
        });
    }

    // Load cluster members and their resource data
    async loadClusterData() {
        this.set('isLoading', true);
        this.set('error', null);
        
        try {
            // Get cluster members
            const response = await window.apiClient.getClusterMembers();
            // Extract members array from response object
            const members = response.members || [];
            this.set('clusterMembers', members);
            
            // Load resource data for each node
            await this.loadNodeResources(members);
            
            // Calculate cluster summary
            this.calculateClusterSummary();
            
            this.set('lastUpdated', new Date());
        } catch (error) {
            console.error('Failed to load cluster monitoring data:', error);
            this.set('error', error.message || 'Failed to load monitoring data');
        } finally {
            this.set('isLoading', false);
        }
    }

    // Load resource data for all nodes
    async loadNodeResources(members) {
        const nodeResources = new Map();
        
        // Process nodes in parallel
        const resourcePromises = members.map(async (member) => {
            try {
                const resources = await window.apiClient.getMonitoringResources(member.ip);
                // Handle both real API (wrapped in data) and mock API (direct response)
                const resourceData = (resources && resources.data) ? resources.data : resources;
                nodeResources.set(member.ip, {
                    ...member,
                    resources: resourceData,
                    hasResources: true,
                    resourceSource: 'monitoring'
                });
            } catch (error) {
                console.warn(`Failed to load monitoring resources for node ${member.ip}:`, error);
                // Fall back to basic resource data from cluster members API
                const basicResources = member.resources ? this.convertBasicResources(member.resources) : null;
                nodeResources.set(member.ip, {
                    ...member,
                    resources: basicResources,
                    basic: member.resources, // Preserve original basic data
                    hasResources: !!basicResources,
                    resourceSource: basicResources ? 'basic' : 'none',
                    error: basicResources ? null : error.message
                });
            }
        });
        
        await Promise.all(resourcePromises);
        this.set('nodeResources', nodeResources);
    }

    // Convert basic resource data from cluster members API to monitoring format
    convertBasicResources(basicResources) {
        // Convert ESP32 basic resources to monitoring format
        const freeHeap = basicResources.freeHeap || 0;
        const flashSize = basicResources.flashChipSize || 0;
        const cpuFreq = basicResources.cpuFreqMHz || 80;
        
        // Estimate total heap (ESP32 typically has ~300KB heap)
        const estimatedTotalHeap = 300 * 1024; // 300KB in bytes
        const usedHeap = estimatedTotalHeap - freeHeap;
        
        return {
            cpu: {
                total: cpuFreq, // Total CPU frequency in MHz
                available: cpuFreq * 0.8, // Estimate 80% available
                used: cpuFreq * 0.2
            },
            memory: {
                total: estimatedTotalHeap,
                available: freeHeap,
                used: usedHeap
            },
            storage: {
                total: flashSize,
                available: flashSize * 0.5, // Estimate 50% available
                used: flashSize * 0.5
            },
            // Include original basic resources for reference
            basic: basicResources
        };
    }

    // Calculate cluster resource summary
    calculateClusterSummary() {
        const nodeResources = this.get('nodeResources');
        const members = this.get('clusterMembers');
        
        let totalCpu = 0;
        let totalMemory = 0;
        let totalStorage = 0;
        let availableCpu = 0;
        let availableMemory = 0;
        let availableStorage = 0;
        let totalNodes = 0;
        
        for (const [ip, nodeData] of nodeResources) {
            if (nodeData.hasResources && nodeData.resources) {
                totalNodes++;
                
                // Extract resource data (handle both monitoring API and basic data formats)
                const resources = nodeData.resources;
                
                // CPU resources
                if (resources.cpu) {
                    const cpuFreqMHz = nodeData.basic?.cpuFreqMHz || 80; // Get CPU frequency from basic resources
                    if (nodeData.resourceSource === 'monitoring') {
                        // Real monitoring API format
                        totalCpu += cpuFreqMHz; // Total CPU frequency in MHz
                        availableCpu += cpuFreqMHz * (100 - (resources.cpu.average_usage || 0)) / 100; // Available frequency
                    } else {
                        // Basic data format
                        totalCpu += cpuFreqMHz; // Total CPU frequency in MHz
                        availableCpu += cpuFreqMHz * (resources.cpu.available || 0.8); // Available frequency
                    }
                }
                
                // Memory resources
                if (resources.memory) {
                    if (nodeData.resourceSource === 'monitoring') {
                        // Real monitoring API format
                        totalMemory += resources.memory.total_heap || 0;
                        availableMemory += resources.memory.free_heap || 0;
                    } else {
                        // Basic data format
                        totalMemory += resources.memory.total || 0;
                        availableMemory += resources.memory.available || 0;
                    }
                }
                
                // Storage resources
                if (resources.filesystem || resources.storage) {
                    const storage = resources.filesystem || resources.storage;
                    if (nodeData.resourceSource === 'monitoring') {
                        // Real monitoring API format
                        totalStorage += storage.total_bytes || 0;
                        availableStorage += storage.free_bytes || 0;
                    } else {
                        // Basic data format
                        totalStorage += storage.total || 0;
                        availableStorage += storage.available || 0;
                    }
                }
            }
        }
        
        this.set('clusterSummary', {
            totalCpu,
            totalMemory,
            totalStorage,
            totalNodes,
            availableCpu,
            availableMemory,
            availableStorage
        });
    }

    // Get resource utilization percentage
    getResourceUtilization(resourceType) {
        const summary = this.get('clusterSummary');
        const total = summary[`total${resourceType}`];
        const available = summary[`available${resourceType}`];
        
        if (total === 0) return 0;
        return Math.round(((total - available) / total) * 100);
    }

    // Get formatted resource value
    formatResourceValue(value, type) {
        if (type === 'memory' || type === 'storage') {
            // Convert bytes to human readable format
            const units = ['B', 'KB', 'MB', 'GB', 'TB'];
            let size = value;
            let unitIndex = 0;
            
            while (size >= 1024 && unitIndex < units.length - 1) {
                size /= 1024;
                unitIndex++;
            }
            
            return `${size.toFixed(1)} ${units[unitIndex]}`;
        } else if (type === 'cpu') {
            // CPU is typically in cores or percentage
            return `${value} cores`;
        }
        
        return value.toString();
    }

    // Refresh monitoring data
    async refresh() {
        await this.loadClusterData();
    }
}

// Cluster Firmware Upload View Model
class ClusterFirmwareViewModel extends ViewModel {
    constructor() {
        super();
        this.setMultiple({
            selectedFile: null,
            targetNodes: [],
            uploadProgress: null,
            uploadResults: [],
            isUploading: false
        });
    }

    // Set selected file
    setSelectedFile(file) {
        this.set('selectedFile', file);
    }

    // Set target nodes (filtered from cluster view)
    setTargetNodes(nodes) {
        this.set('targetNodes', nodes);
    }

    // Start upload
    startUpload() {
        this.set('isUploading', true);
        this.set('uploadProgress', {
            current: 0,
            total: 0,
            status: 'Preparing...'
        });
        this.set('uploadResults', []);
    }

    // Update upload progress
    updateUploadProgress(current, total, status) {
        this.set('uploadProgress', {
            current,
            total,
            status
        });
    }

    // Add upload result
    addUploadResult(result) {
        const results = this.get('uploadResults');
        results.push(result);
        this.set('uploadResults', results);
    }

    // Complete upload
    completeUpload() {
        this.set('isUploading', false);
    }

    // Reset upload state
    resetUploadState() {
        this.set('selectedFile', null);
        this.set('uploadProgress', null);
        this.set('uploadResults', []);
        this.set('isUploading', false);
    }

    // Check if deploy is enabled
    isDeployEnabled() {
        const file = this.get('selectedFile');
        const targetNodes = this.get('targetNodes');
        const isUploading = this.get('isUploading');
        
        return file && targetNodes && targetNodes.length > 0 && !isUploading;
    }
}

// WiFi Configuration View Model
class WiFiConfigViewModel extends ViewModel {
    constructor() {
        super();
        this.set('targetNodes', []);
        this.set('ssid', '');
        this.set('password', '');
        this.set('isConfiguring', false);
        this.set('configProgress', null);
        this.set('configResults', []);
    }

    // Set target nodes (filtered from cluster view)
    setTargetNodes(nodes) {
        this.set('targetNodes', nodes);
    }

    // Set WiFi credentials
    setCredentials(ssid, password) {
        this.set('ssid', ssid);
        this.set('password', password);
    }

    // Start configuration
    startConfiguration() {
        this.set('isConfiguring', true);
        this.set('configProgress', {
            current: 0,
            total: 0,
            status: 'Preparing...'
        });
        this.set('configResults', []);
    }

    // Update configuration progress
    updateConfigProgress(current, total, status) {
        this.set('configProgress', {
            current,
            total,
            status
        });
    }

    // Add configuration result
    addConfigResult(result) {
        const results = this.get('configResults');
        results.push(result);
        this.set('configResults', results);
    }

    // Complete configuration
    completeConfiguration() {
        this.set('isConfiguring', false);
        this.set('configProgress', null);
    }

    // Reset configuration state
    resetConfiguration() {
        this.set('ssid', '');
        this.set('password', '');
        this.set('configProgress', null);
        this.set('configResults', []);
        this.set('isConfiguring', false);
    }

    // Check if apply is enabled
    isApplyEnabled() {
        const ssid = this.get('ssid');
        const password = this.get('password');
        const targetNodes = this.get('targetNodes');
        const isConfiguring = this.get('isConfiguring');
        
        return ssid && password && targetNodes && targetNodes.length > 0 && !isConfiguring;
    }
}

window.WiFiConfigViewModel = WiFiConfigViewModel; 