// Surfaces playback errors (#58). _handlePlaybackError in the audio
// handler silently skips a dead track (404 / decoder failure / premature
// EOS / network drop) with only a debugPrint — which hides exactly the
// signal that distinguishes "this track is broken" from "the app is
// flaky" (server file moved, auth expired, cache miss, transcode fail).
//
// This listens to the handler's playbackErrorStream and shows a
// transient SnackBar via a global ScaffoldMessenger key. Bursts are
// coalesced: a debounce window collects errors and emits one message
// ("Skipped N unplayable tracks") instead of stacking N toasts.

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'player_provider.dart';

/// Set on MaterialApp.router so SnackBars can be shown from outside any
/// widget's BuildContext (the handler's error stream is isolate-side).
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

class PlaybackErrorReporter {
  PlaybackErrorReporter(this._ref);
  final Ref _ref;

  StreamSubscription<String>? _sub;
  Timer? _debounce;
  final _buffer = <String>[];
  bool _disposed = false;

  void start() {
    try {
      // audioHandlerProvider throws until main() overrides it (the real
      // app always does). In tests / no-audio environments there's
      // nothing to report — stay inert.
      final h = _ref.read(audioHandlerProvider);
      _sub = h.playbackErrorStream.listen(_onError);
    } catch (_) {
      return;
    }
  }

  void _onError(String title) {
    if (_disposed) return;
    _buffer.add(title);
    _debounce?.cancel();
    _debounce = Timer(const Duration(seconds: 2), _flush);
  }

  void _flush() {
    if (_disposed || _buffer.isEmpty) return;
    final n = _buffer.length;
    final first = _buffer.first;
    _buffer.clear();
    final msg = n == 1
        ? 'Couldn’t play “$first” — skipping'
        : 'Skipped $n unplayable tracks';
    scaffoldMessengerKey.currentState?.showSnackBar(
      SnackBar(content: Text(msg)),
    );
  }

  void dispose() {
    _disposed = true;
    _debounce?.cancel();
    _sub?.cancel();
  }
}

/// Read once at app start (app.dart postFrame). Disposed via
/// ref.onDispose when the scope tears down.
final playbackErrorReporterProvider = Provider<PlaybackErrorReporter>((ref) {
  final r = PlaybackErrorReporter(ref);
  ref.onDispose(r.dispose);
  r.start();
  return r;
});
