class IgnisGroup { final String id; final String name; final List macs; final IgnisGroupState state; const IgnisGroup({ required this.id, required this.name, this.macs = const [], this.state = const IgnisGroupState(), }); IgnisGroup copyWith({ String? id, String? name, List? macs, IgnisGroupState? state, }) { return IgnisGroup( id: id ?? this.id, name: name ?? this.name, macs: macs ?? this.macs, state: state ?? this.state, ); } static IgnisGroup fromApi(Object? value, {String? fallbackId}) { if (value is! Map) { final id = value?.toString() ?? fallbackId; if (id == null || id.isEmpty) { throw const FormatException('group должен быть объектом или id'); } return IgnisGroup(id: id, name: id); } final map = Map.from(value); final id = _stringValue(map, const ['id', 'group_id']) ?? fallbackId; if (id == null || id.isEmpty) { throw const FormatException('group не содержит id'); } final name = _stringValue(map, const ['name', 'label']) ?? id; final macs = _stringList( map['macs'] ?? map['devices'] ?? map['device_ids'], ); final state = IgnisGroupState.fromApi( map['last_state'] ?? map['state'] ?? map['status'], ); return IgnisGroup(id: id, name: name, macs: macs, state: state); } static List listFromApi(Object? data) { final values = _collectionValues(data, const ['data', 'groups']); return values.map((value) { if (value.entryKey == null) return IgnisGroup.fromApi(value.value); return IgnisGroup.fromApi(value.value, fallbackId: value.entryKey); }).toList(); } } class IgnisGroupState { final bool isOn; final int brightness; final int temp; final int r; final int g; final int b; final String? sceneId; const IgnisGroupState({ this.isOn = false, this.brightness = 100, this.temp = 4000, this.r = 255, this.g = 200, this.b = 100, this.sceneId, }); IgnisGroupState copyWith({ bool? isOn, int? brightness, int? temp, int? r, int? g, int? b, String? sceneId, }) { return IgnisGroupState( isOn: isOn ?? this.isOn, brightness: brightness ?? this.brightness, temp: temp ?? this.temp, r: r ?? this.r, g: g ?? this.g, b: b ?? this.b, sceneId: sceneId ?? this.sceneId, ); } IgnisGroupState applyPatch(Map patch) { return copyWith( isOn: patch.containsKey('state') ? patch['state'] == true : null, brightness: _intValue(patch['brightness'] ?? patch['dimming']), temp: _intValue(patch['temp']), r: _intValue(patch['r']), g: _intValue(patch['g']), b: _intValue(patch['b']), sceneId: patch['scene']?.toString(), ); } static IgnisGroupState fromApi(Object? value, {IgnisGroupState? fallback}) { final base = fallback ?? const IgnisGroupState(); if (value is! Map) return base; final map = Map.from(value); return base.applyPatch({ 'state': map['state'], 'brightness': map['brightness'] ?? map['dimming'], 'temp': map['temp'], 'r': map['r'], 'g': map['g'], 'b': map['b'], 'scene': map['scene'], }); } static IgnisGroupState? firstFromStatusResponse( Object? data, { IgnisGroupState? fallback, }) { if (data is! Map) return fallback; final map = Map.from(data); final results = map['results']; if (results is! List || results.isEmpty) return fallback; Object? validResult; for (final result in results) { if (result is Map && result['status'] != null && result['error'] == null) { validResult = result; break; } } validResult ??= results.first; if (validResult is! Map) return fallback; return IgnisGroupState.fromApi(validResult['status'], fallback: fallback); } } String? _stringValue(Map map, List keys) { for (final key in keys) { final value = map[key]; if (value != null && value.toString().isNotEmpty) { return value.toString(); } } return null; } int? _intValue(Object? value) { if (value is int) return value; if (value is num) return value.toInt(); return int.tryParse(value?.toString() ?? ''); } List _stringList(Object? value) { if (value is! List) return const []; return value.map((item) => item.toString()).toList(); } List<_CollectionValue> _collectionValues(Object? data, List wrappers) { if (data is List) { return data.map((value) => _CollectionValue(value)).toList(); } if (data is Map) { final map = Map.from(data); for (final wrapper in wrappers) { final value = map[wrapper]; if (value is List) { return value.map((item) => _CollectionValue(item)).toList(); } } return map.entries .map((entry) => _CollectionValue(entry.value, entryKey: entry.key)) .toList(); } throw const FormatException('ожидался список или объект'); } class _CollectionValue { final Object? value; final String? entryKey; const _CollectionValue(this.value, {this.entryKey}); }