import 'package:equatable/equatable.dart'; import '../../../domain/entities/wall_config.dart'; /// State for a single viewer class ViewerState extends Equatable { final int viewerId; final int currentCameraId; final bool isLive; final bool hasAlarm; final bool isLocked; final String? lockedBy; const ViewerState({ required this.viewerId, this.currentCameraId = 0, this.isLive = true, this.hasAlarm = false, this.isLocked = false, this.lockedBy, }); bool get hasCamera => currentCameraId > 0; bool get isLockedByOther => isLocked && lockedBy != null; ViewerState copyWith({ int? currentCameraId, bool? isLive, bool? hasAlarm, bool? isLocked, String? lockedBy, }) { return ViewerState( viewerId: viewerId, currentCameraId: currentCameraId ?? this.currentCameraId, isLive: isLive ?? this.isLive, hasAlarm: hasAlarm ?? this.hasAlarm, isLocked: isLocked ?? this.isLocked, lockedBy: lockedBy ?? this.lockedBy, ); } @override List get props => [viewerId, currentCameraId, isLive, hasAlarm, isLocked, lockedBy]; } /// Main wall bloc state class WallState extends Equatable { final WallConfig? config; final bool isLoading; final String? error; // Selection state final int? selectedViewerId; final int? selectedPhysicalMonitorId; // Camera input state final int cameraPrefix; // 500, 501, 502 final String cameraNumberInput; // Up to 6 digits typed by user final bool isEditing; // Whether camera input is active // Viewer states (keyed by viewer ID) final Map viewerStates; // Expanded sections final Set expandedSections; const WallState({ this.config, this.isLoading = false, this.error, this.selectedViewerId, this.selectedPhysicalMonitorId, this.cameraPrefix = 500, this.cameraNumberInput = '', this.isEditing = false, this.viewerStates = const {}, this.expandedSections = const {}, }); static const int _maxLength = 6; static const List _prefixes = [500, 501, 502]; /// Compose camera number with prefix (legacy CameraNumber.GetCameraNumberWithPrefix). /// If digits > prefix length: use digits as-is, right-pad with zeros. /// If digits <= prefix length: prefix + left-padded digits. int? get fullCameraNumber { if (cameraNumberInput.isEmpty) return null; final prefix = cameraPrefix.toString(); final String composed; if (cameraNumberInput.length > prefix.length) { composed = cameraNumberInput.padRight(_maxLength, '0'); } else { composed = prefix + cameraNumberInput.padLeft(_maxLength - prefix.length, '0'); } return int.tryParse(composed); } /// Display string: typed digits only (no prefix shown in field). String get cameraInputDisplay { if (!isEditing || cameraNumberInput.isEmpty) return ''; return cameraNumberInput; } /// Check if a viewer is selected bool isViewerSelected(int viewerId) => selectedViewerId == viewerId; /// Check if a physical monitor is selected (any of its viewers) bool isPhysicalMonitorSelected(PhysicalMonitor monitor) => selectedPhysicalMonitorId == monitor.id; /// Get viewer state ViewerState getViewerState(int viewerId) { return viewerStates[viewerId] ?? ViewerState(viewerId: viewerId); } /// Check if section is expanded bool isSectionExpanded(String sectionId) => expandedSections.contains(sectionId); /// Check if CrossSwitch can be executed bool get canExecuteCrossSwitch { if (selectedViewerId == null) return false; if (!isEditing || cameraNumberInput.isEmpty) return false; if (fullCameraNumber == null) return false; final viewerState = getViewerState(selectedViewerId!); if (viewerState.hasAlarm) return false; return true; } WallState copyWith({ WallConfig? config, bool? isLoading, String? error, int? selectedViewerId, int? selectedPhysicalMonitorId, int? cameraPrefix, String? cameraNumberInput, bool? isEditing, Map? viewerStates, Set? expandedSections, bool clearSelection = false, bool clearError = false, }) { return WallState( config: config ?? this.config, isLoading: isLoading ?? this.isLoading, error: clearError ? null : (error ?? this.error), selectedViewerId: clearSelection ? null : (selectedViewerId ?? this.selectedViewerId), selectedPhysicalMonitorId: clearSelection ? null : (selectedPhysicalMonitorId ?? this.selectedPhysicalMonitorId), cameraPrefix: cameraPrefix ?? this.cameraPrefix, cameraNumberInput: cameraNumberInput ?? this.cameraNumberInput, isEditing: isEditing ?? this.isEditing, viewerStates: viewerStates ?? this.viewerStates, expandedSections: expandedSections ?? this.expandedSections, ); } @override List get props => [ config, isLoading, error, selectedViewerId, selectedPhysicalMonitorId, cameraPrefix, cameraNumberInput, isEditing, viewerStates, expandedSections, ]; }