class ScheduleTask { final String jobId; final String targetId; final bool? targetState; final String type; final String? runAt; final String? cron; final String? hour; final String? minute; final String? dayOfWeek; const ScheduleTask({ required this.jobId, required this.targetId, required this.type, this.targetState, this.runAt, this.cron, this.hour, this.minute, this.dayOfWeek, }); bool get isCron => type == 'cron' || cron != null; String get actionText { return targetState == true ? 'Включить' : targetState == false ? 'Выключить' : '?'; } String get title => '$actionText - $targetId'; String get subtitle { final lines = ['Цель: $targetId']; if (runAt != null && runAt!.isNotEmpty) lines.add('Запуск: $runAt'); if (cron != null && cron!.isNotEmpty) lines.add('Cron: $cron'); if (hour != null && minute != null) lines.add('Время: $hour:$minute'); if (dayOfWeek != null && dayOfWeek != '*') { lines.add('Дни: $dayOfWeek'); } return lines.join('\n'); } static ScheduleTask fromApi(Object? data, {String? fallbackId}) { if (data is! Map) { final id = data?.toString() ?? fallbackId; if (id == null || id.isEmpty) { throw const FormatException('schedule task должен быть объектом'); } return ScheduleTask(jobId: id, targetId: '', type: 'once'); } final map = Map.from(data); final jobId = _stringValue(map, const ['id', 'job_id']) ?? fallbackId; if (jobId == null || jobId.isEmpty) { throw const FormatException('schedule task не содержит id/job_id'); } final cron = map['cron']?.toString(); final type = _stringValue(map, const ['type']) ?? (cron != null ? 'cron' : 'once'); return ScheduleTask( jobId: jobId, targetId: _stringValue(map, const ['target_id', 'target']) ?? '', targetState: _boolValue(map['state']), type: type, runAt: _stringValue(map, const ['run_at', 'next_run', 'next_run_time']), cron: cron, hour: map['hour']?.toString(), minute: map['minute']?.toString(), dayOfWeek: map['day_of_week']?.toString(), ); } static List listFromApi(Object? data) { final values = _collectionValues(data, const ['tasks', 'data']); return values.map((value) { if (value.entryKey == null) return ScheduleTask.fromApi(value.value); return ScheduleTask.fromApi(value.value, fallbackId: value.entryKey); }).toList(); } } bool? _boolValue(Object? value) { if (value is bool) return value; if (value is num) return value != 0; if (value is String) { final normalized = value.trim().toLowerCase(); if (normalized == 'true' || normalized == '1') return true; if (normalized == 'false' || normalized == '0') return false; } return null; } 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(); } } 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}); }