/// One user's report under an aggregated quarantine row. class AdminQuarantineReport { const AdminQuarantineReport({ required this.userId, required this.username, required this.reason, this.notes, required this.createdAt, }); final String userId; final String username; final String reason; final String? notes; final String createdAt; factory AdminQuarantineReport.fromJson(Map j) => AdminQuarantineReport( userId: j['user_id'] as String? ?? '', username: j['username'] as String? ?? '', reason: j['reason'] as String? ?? '', notes: j['notes'] as String?, createdAt: j['created_at'] as String? ?? '', ); } /// Mirrors `adminQueueRowView` from internal/api/admin_quarantine.go. /// One row aggregates all reports against a single track, with /// per-reason counts and the underlying per-user reports nested. class AdminQuarantineItem { const AdminQuarantineItem({ required this.trackId, required this.trackTitle, required this.artistName, required this.albumTitle, required this.albumId, this.lidarrAlbumMbid, required this.reportCount, required this.latestAt, required this.reasonCounts, required this.reports, }); final String trackId; final String trackTitle; final String artistName; final String albumTitle; final String albumId; final String? lidarrAlbumMbid; final int reportCount; final String latestAt; /// Map of reason → number of reports citing that reason. final Map reasonCounts; final List reports; /// One-line summary of the most-cited reason. Returns just the top /// reason when only one reason exists, or `top (+N more)` when there /// are additional distinct reasons. String get topReasonSummary { if (reasonCounts.isEmpty) return ''; final entries = reasonCounts.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); final top = entries.first.key; return entries.length > 1 ? '$top (+${entries.length - 1} more)' : top; } factory AdminQuarantineItem.fromJson(Map j) { final reasonCountsRaw = (j['reason_counts'] as Map?)?.cast() ?? const {}; final reportsRaw = (j['reports'] as List?) ?? const []; return AdminQuarantineItem( trackId: j['track_id'] as String? ?? '', trackTitle: j['track_title'] as String? ?? '', artistName: j['artist_name'] as String? ?? '', albumTitle: j['album_title'] as String? ?? '', albumId: j['album_id'] as String? ?? '', lidarrAlbumMbid: j['lidarr_album_mbid'] as String?, reportCount: (j['report_count'] as int?) ?? 0, latestAt: j['latest_at'] as String? ?? '', reasonCounts: reasonCountsRaw.map((k, v) => MapEntry(k, (v as int?) ?? 0)), reports: reportsRaw .map((e) => AdminQuarantineReport.fromJson((e as Map).cast())) .toList(growable: false), ); } }