import '../database/db_helper.dart'; import '../models/song.dart'; /// Ein Song mit seiner Abspiel-Anzahl im Recap class RecapSongStat { final Song song; final int anzahl; RecapSongStat(this.song, this.anzahl); } /// Ein Künstler mit seiner Abspiel-Anzahl im Recap class RecapKuenstlerStat { final String name; final int anzahl; RecapKuenstlerStat(this.name, this.anzahl); } /// Aggregierte Recap-Daten (wie Spotify Wrapped) class RecapDaten { final int jahr; final int gesamtAnzahl; // Gesamtzahl aller Abspielungen final int gesamtSekunden; // Geschätzte Hörzeit (Summe Songdauer je Abspielung) final String haeufigsterTag; // z.B. "Freitag" final double nachtProzent; // % der Abspielungen nach 22 Uhr (0–100) final List topSongs; // Top 5 final List topKuenstler; // Top 3 RecapDaten({ required this.jahr, required this.gesamtAnzahl, required this.gesamtSekunden, required this.haeufigsterTag, required this.nachtProzent, required this.topSongs, required this.topKuenstler, }); String get hoerzeitFormatiert { final stunden = gesamtSekunden ~/ 3600; final minuten = (gesamtSekunden % 3600) ~/ 60; if (stunden > 0) return '$stunden Std $minuten Min'; return '$minuten Min'; } } /// Aggregiert die lokale Abspiel-Historie zu Recap-Statistiken. /// /// Datenquelle ist die lokale `abspiel_historie`-Tabelle (nicht der Server), /// da die History lokal aufgezeichnet wird. class RecapService { final DbHelper _db = DbHelper(); static const List _wochentage = [ 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag', ]; /// Berechnet den Recap. Filtert auf [jahr] (Standard: aktuelles Jahr). /// Falls im gewählten Jahr keine Daten existieren, wird auf die /// Gesamt-Historie zurückgegriffen. Liefert `null` bei leerer History. Future berechne({int? jahr}) async { final zielJahr = jahr ?? DateTime.now().year; var rows = await _db.abspielHistorie(); if (rows.isEmpty) return null; final gefiltert = rows.where((r) { final t = DateTime.tryParse(r['played_at'] as String? ?? ''); return t != null && t.year == zielJahr; }).toList(); // Fallback: wenn im Zieljahr nichts gehört wurde → gesamte History final daten = gefiltert.isNotEmpty ? gefiltert : rows; // ── Aggregation ── final songCounts = {}; final songMap = {}; final kuenstlerCounts = {}; final wochentagCounts = List.filled(7, 0); var nachtCount = 0; var gesamtSekunden = 0; for (final r in daten) { final songId = r['song_id'] as int; songCounts[songId] = (songCounts[songId] ?? 0) + 1; songMap[songId] ??= Song.fromMap({ 'id': songId, 'titel': r['titel'] ?? '', 'kuenstler': r['kuenstler'] ?? 'Unbekannt', 'dauer_sekunden': r['dauer_sekunden'] ?? 0, 'datei_pfad': '', }); final kuenstler = (r['kuenstler'] as String?) ?? 'Unbekannt'; kuenstlerCounts[kuenstler] = (kuenstlerCounts[kuenstler] ?? 0) + 1; final t = DateTime.tryParse(r['played_at'] as String? ?? ''); if (t != null) { wochentagCounts[t.weekday - 1]++; if (t.hour >= 22) nachtCount++; } gesamtSekunden += (r['dauer_sekunden'] as int?) ?? 0; } final gesamtAnzahl = daten.length; final nachtProzent = gesamtAnzahl == 0 ? 0.0 : (nachtCount * 100 / gesamtAnzahl).roundToDouble(); // Häufigster Wochentag (bei Gleichstand der erste) var maxTag = 0; for (var i = 1; i < 7; i++) { if (wochentagCounts[i] > wochentagCounts[maxTag]) maxTag = i; } // Top 5 Songs final sortierteSongs = songCounts.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); final topSongs = sortierteSongs .take(5) .map((e) => RecapSongStat(songMap[e.key]!, e.value)) .toList(); // Top 3 Künstler final sortierteKuenstler = kuenstlerCounts.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); final topKuenstler = sortierteKuenstler .take(3) .map((e) => RecapKuenstlerStat(e.key, e.value)) .toList(); return RecapDaten( jahr: zielJahr, gesamtAnzahl: gesamtAnzahl, gesamtSekunden: gesamtSekunden, haeufigsterTag: _wochentage[maxTag], nachtProzent: nachtProzent, topSongs: topSongs, topKuenstler: topKuenstler, ); } }