class EventLogItem { final String timestamp; final String action; final String targetId; final Object? params; final String actor; const EventLogItem({ required this.timestamp, required this.action, required this.targetId, this.params, this.actor = '', }); String get title => '$action - $targetId'; String get paramsText { final value = params; if (value == null) return ''; return value.toString(); } String get formattedTime { if (timestamp.isEmpty) return ''; try { final date = DateTime.parse(timestamp); String pad(int n) => n.toString().padLeft(2, '0'); return '${pad(date.day)}.${pad(date.month)} ' '${pad(date.hour)}:${pad(date.minute)}:${pad(date.second)}'; } catch (_) { return timestamp; } } static EventLogItem fromApi(Object? data, {String? fallbackId}) { if (data is! Map) { final action = data?.toString() ?? fallbackId; if (action == null || action.isEmpty) { throw const FormatException('event log item должен быть объектом'); } return EventLogItem(timestamp: '', action: action, targetId: ''); } final map = Map.from(data); return EventLogItem( timestamp: _stringValue(map, const ['timestamp', 'time', 'created_at']) ?? '', action: _stringValue(map, const ['action', 'command', 'type']) ?? '', targetId: _stringValue(map, const ['target_id', 'target', 'group_id']) ?? '', params: map['params'] ?? map['details'], actor: _stringValue(map, const ['actor', 'user', 'key_name']) ?? '', ); } static List listFromApi(Object? data) { final values = _collectionValues(data, const ['data', 'events', 'log']); return values.map((value) { if (value.entryKey == null) return EventLogItem.fromApi(value.value); return EventLogItem.fromApi(value.value, fallbackId: value.entryKey); }).toList(); } } 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; } 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(); } } if (_looksLikeEventItem(map)) { return <_CollectionValue>[_CollectionValue(map)]; } return map.entries .map((entry) => _CollectionValue(entry.value, entryKey: entry.key)) .toList(); } throw const FormatException('stats log должен быть списком событий'); } bool _looksLikeEventItem(Map map) { return map.containsKey('timestamp') || map.containsKey('time') || map.containsKey('created_at') || map.containsKey('action') || map.containsKey('command') || map.containsKey('type'); } class _CollectionValue { final Object? value; final String? entryKey; const _CollectionValue(this.value, {this.entryKey}); }