import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../../../../api/webuntis/queries/get_rooms/get_rooms_response.dart'; import '../../../../api/webuntis/queries/get_subjects/get_subjects_response.dart'; import '../../../../api/webuntis/queries/get_timetable/get_timetable_response.dart'; import '../../../../routing/app_routes.dart'; import '../../../../state/app/modules/timetable/bloc/timetable_bloc.dart'; import '../../../../state/app/modules/timetable/bloc/timetable_state.dart'; import '../../../../widget/debug/debug_tile.dart'; import '../../../../widget/unimplemented_dialog.dart'; import 'bottom_sheet.dart'; class WebuntisLessonSheet { static void show(BuildContext context, TimetableBloc bloc, Appointment appointment, GetTimetableResponseObject lesson) { final state = bloc.state.data; if (state == null) return; final headerSubject = _resolveSubject(state, lesson.su.firstOrNull?.id); final headerTitle = _firstNonEmpty([headerSubject.alternateName, headerSubject.name, headerSubject.longName, '?']); final headerLongName = headerSubject.longName.isNotEmpty && headerSubject.longName != headerTitle ? headerSubject.longName : ''; final timeRange = '${Jiffy.parseFromDateTime(appointment.startTime).format(pattern: 'HH:mm')} - ' '${Jiffy.parseFromDateTime(appointment.endTime).format(pattern: 'HH:mm')}'; showAppointmentBottomSheet( context, header: ListTile( leading: Icon(_iconForCode(lesson.code), size: 32), title: Text( '${_codePrefix(lesson.code)}$headerTitle', style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text(headerLongName.isNotEmpty ? '$timeRange\n$headerLongName' : timeRange), isThreeLine: headerLongName.isNotEmpty, ), children: (_) => [ ListTile( leading: const Icon(Icons.notifications_active), title: Text('Status: ${_statusLabel(lesson.code)}'), ), if (lesson.su.length > 1) _listTile( icon: Icons.book_outlined, label: 'Fächer', entries: lesson.su.map((s) { final resolved = _resolveSubject(state, s.id); return _formatLine( _firstNonEmpty([resolved.name, s.name, '?']), longname: _firstNonEmpty([resolved.longName, s.longname, '']), ); }).toList(), ), _roomTile(context, state, lesson), _teacherTile(context, lesson), if ((lesson.activityType ?? '').trim().isNotEmpty) ListTile( leading: const Icon(Icons.abc), title: Text('Typ: ${lesson.activityType}'), ), if (lesson.kl.isNotEmpty) _listTile( icon: Icons.people, label: lesson.kl.length == 1 ? 'Klasse' : 'Klassen', entries: lesson.kl .map((k) => _formatLine( k.name.isNotEmpty ? k.name : '?', longname: k.longname, )) .toList(), ), ..._optionalTextTiles(lesson), DebugTile(context).jsonData(lesson.toJson()), ], ); } static IconData _iconForCode(String? code) { switch (code) { case 'cancelled': return Icons.event_busy_outlined; case 'irregular': return Icons.swap_horiz; default: return Icons.school_outlined; } } static Widget _roomTile(BuildContext context, TimetableState state, GetTimetableResponseObject lesson) { final trailing = IconButton( icon: const Icon(Icons.house_outlined), onPressed: () => AppRoutes.openRoomplan(context), ); if (lesson.ro.isEmpty) { return ListTile( leading: const Icon(Icons.room), title: const Text('Raum: ?'), trailing: trailing, ); } final entries = lesson.ro.map((r) { final resolved = _resolveRoom(state, r.id); final name = _firstNonEmpty([resolved.name, r.name, '?']); final longname = _firstNonEmpty([resolved.longName, r.longname, '']); final building = resolved.building.trim(); return _formatLine( name, longname: longname, extra: (building.isNotEmpty && building != '?') ? building : null, ); }).toList(); return _listTile( icon: Icons.room, label: lesson.ro.length == 1 ? 'Raum' : 'Räume', entries: entries, trailing: trailing, ); } static Widget _teacherTile(BuildContext context, GetTimetableResponseObject lesson) { final trailing = Visibility( visible: !kReleaseMode, child: IconButton( icon: const Icon(Icons.textsms_outlined), onPressed: () => UnimplementedDialog.show(context), ), ); if (lesson.te.isEmpty) { return ListTile( leading: const Icon(Icons.person), title: const Text('Lehrkraft: ?'), trailing: trailing, ); } final entries = lesson.te.map((t) { final base = _formatLine( t.name.isNotEmpty ? t.name : '?', longname: t.longname, ); final orgname = (t.orgname ?? '').trim(); return orgname.isEmpty ? base : '$base · ehemals $orgname'; }).toList(); return _listTile( icon: Icons.person, label: lesson.te.length == 1 ? 'Lehrkraft' : 'Lehrkräfte', entries: entries, trailing: trailing, ); } static Widget _listTile({ required IconData icon, required String label, required List entries, Widget? trailing, }) { if (entries.length == 1) { return ListTile( leading: Icon(icon), title: Text('$label: ${entries.first}'), trailing: trailing, ); } return ListTile( leading: Icon(icon), title: Text(label), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: entries.map(Text.new).toList(), ), trailing: trailing, ); } static List _optionalTextTiles(GetTimetableResponseObject lesson) { return [ _textTile(Icons.info_outline, 'Info', lesson.info), _textTile(Icons.swap_horiz, 'Vertretungstext', lesson.substText), _textTile(Icons.subject, 'Stundentext', lesson.lstext), _textTile(Icons.category_outlined, 'Stundentyp', lesson.lstype), _textTile(Icons.flag_outlined, 'Statusmerkmale', lesson.statflags), _textTile(Icons.school_outlined, 'Lerngruppe', lesson.sg), _textTile(Icons.bookmark_outline, 'Buchungshinweis', lesson.bkRemark), _textTile(Icons.notes, 'Buchungstext', lesson.bkText), ].whereType().toList(); } static Widget? _textTile(IconData icon, String label, String? value) { final text = (value ?? '').trim(); if (text.isEmpty || text == '-') return null; return ListTile( leading: Icon(icon), title: Text(label), subtitle: Text(text), ); } static String _formatLine(String name, {String? longname, String? extra}) { final parts = [ if (name.isNotEmpty) name else '?', ]; final ln = (longname ?? '').trim(); if (ln.isNotEmpty && ln != name) parts.add('($ln)'); final ex = (extra ?? '').trim(); if (ex.isNotEmpty) parts.add('· $ex'); return parts.join(' '); } static String _firstNonEmpty(List values) { for (final v in values) { if (v.trim().isNotEmpty) return v; } return ''; } static String _statusLabel(String? code) { switch (code) { case null: case '': return 'Regulär'; case 'cancelled': return 'Entfällt'; case 'irregular': return 'Geändert'; default: return code; } } static String _codePrefix(String? code) { if (code == 'cancelled') return 'Entfällt: '; if (code == 'irregular') return 'Änderung: '; return code ?? ''; } static GetSubjectsResponseObject _resolveSubject(TimetableState state, int? id) { final fallback = GetSubjectsResponseObject(0, '?', 'Unbekannt', '?', true); if (id == null) return fallback; return state.subjects?.result.firstWhereOrNull((s) => s.id == id) ?? fallback; } static GetRoomsResponseObject _resolveRoom(TimetableState state, int? id) { final fallback = GetRoomsResponseObject(0, '?', 'Unbekannt', true, ''); if (id == null) return fallback; return state.rooms?.result.firstWhereOrNull((r) => r.id == id) ?? fallback; } }