import 'dart:async'; import 'package:logger/logger.dart'; import 'package:rxdart/rxdart.dart'; import '../../domain/entities/monitor_state.dart'; import '../../domain/entities/alarm_state.dart'; import '../models/bridge_event.dart'; import 'bridge_service.dart'; import 'alarm_service.dart'; /// Service for tracking overall system state (monitors + alarms) class StateService { final BridgeService _bridgeService; final AlarmService _alarmService; final Logger _logger = Logger(); StreamSubscription? _eventSubscription; // Monitor state final _monitorStatesController = BehaviorSubject>.seeded({}); // Combined state stream (monitors with alarm flags) final _combinedMonitorStatesController = BehaviorSubject>.seeded({}); StateService({ required BridgeService bridgeService, required AlarmService alarmService, }) : _bridgeService = bridgeService, _alarmService = alarmService; /// Stream of monitor states (without alarm info) Stream> get monitorStates => _monitorStatesController.stream; /// Stream of monitor states with alarm flags Stream> get combinedMonitorStates => _combinedMonitorStatesController.stream; /// Stream of active alarms (delegated to AlarmService) Stream> get activeAlarms => _alarmService.alarms; /// Current monitor states Map get currentMonitorStates => _monitorStatesController.value; /// Get state for a specific monitor MonitorState? getMonitorState(int viewerId) { return _combinedMonitorStatesController.value[viewerId]; } /// Initialize state tracking Future initialize() async { // Subscribe to bridge events _eventSubscription = _bridgeService.eventStream.listen(_handleBridgeEvent); // Subscribe to alarm changes to update monitor flags _alarmService.alarms.listen((_) => _updateCombinedStates()); _logger.i('StateService initialized'); } /// Sync initial state from all bridges Future syncFromBridges() async { _logger.i('Syncing state from bridges...'); final connectionStatus = _bridgeService.currentConnectionStatus; final monitors = {}; for (final entry in connectionStatus.entries) { if (!entry.value) continue; // Skip disconnected servers try { final serverMonitors = await _bridgeService.getMonitorStates(entry.key); for (final monitorJson in serverMonitors) { final state = MonitorState.fromJson(monitorJson); monitors[state.viewerId] = state; } _logger.d( 'Synced ${serverMonitors.length} monitors from ${entry.key}'); } catch (e) { _logger.e('Failed to sync monitors from ${entry.key}: $e'); } } _monitorStatesController.add(monitors); _updateCombinedStates(); // Also sync alarms await _alarmService.queryAllAlarms(); _logger.i('State sync complete: ${monitors.length} monitors'); } /// Handle incoming bridge event void _handleBridgeEvent(BridgeEvent event) { if (event.isViewerConnected || event.isViewerSelectionChanged) { _handleViewerConnected(event); } else if (event.isViewerCleared) { _handleViewerCleared(event); } else if (event.isEventStarted || event.isEventStopped || event.isAlarmQueueNotification) { // Delegate alarm events to AlarmService _alarmService.handleAlarmEvent(event); } } /// Handle viewer connected event void _handleViewerConnected(BridgeEvent event) { final viewer = event.viewer; final channel = event.channel; final playMode = event.playMode; if (viewer == null) return; final monitors = Map.from(_monitorStatesController.value); final existing = monitors[viewer]; monitors[viewer] = MonitorState( viewerId: viewer, currentChannel: channel ?? existing?.currentChannel ?? 0, playMode: PlayMode.fromValue(playMode ?? existing?.playMode.value ?? 0), serverId: event.serverId, lastUpdated: event.timestamp, ); _monitorStatesController.add(monitors); _updateCombinedStates(); _logger.d('Monitor $viewer connected to channel $channel'); } /// Handle viewer cleared event void _handleViewerCleared(BridgeEvent event) { final viewer = event.viewer; if (viewer == null) return; final monitors = Map.from(_monitorStatesController.value); final existing = monitors[viewer]; if (existing != null) { monitors[viewer] = existing.cleared(); } else { monitors[viewer] = MonitorState( viewerId: viewer, currentChannel: 0, playMode: PlayMode.unknown, serverId: event.serverId, lastUpdated: event.timestamp, ); } _monitorStatesController.add(monitors); _updateCombinedStates(); _logger.d('Monitor $viewer cleared'); } /// Update combined states with alarm flags void _updateCombinedStates() { final monitors = _monitorStatesController.value; final combined = {}; for (final entry in monitors.entries) { final hasAlarm = _alarmService.isMonitorBlocked(entry.key); combined[entry.key] = entry.value.withAlarm(hasAlarm); } _combinedMonitorStatesController.add(combined); } /// Check if a monitor is blocked by an alarm bool isMonitorBlocked(int viewerId) { return _alarmService.isMonitorBlocked(viewerId); } /// Dispose resources void dispose() { _eventSubscription?.cancel(); _monitorStatesController.close(); _combinedMonitorStatesController.close(); } }