import 'dart:io';

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

import 'cache/cache_filler.dart';
import 'cache/metadata_prefetcher.dart';
import 'cache/offline_provider.dart';
import 'cache/mutation_queue.dart';
import 'cache/prefetcher.dart';
import 'cache/resume_controller.dart';
import 'cache/sync_controller.dart';
import 'player/play_events_reporter.dart';
import 'player/playback_error_reporter.dart';
import 'shared/live_events_dispatcher.dart';
import 'shared/routing.dart';
import 'theme/theme_data.dart';
import 'theme/theme_mode_provider.dart';

class MinstrelApp extends ConsumerStatefulWidget {
  const MinstrelApp({super.key});

  @override
  ConsumerState<MinstrelApp> createState() => _MinstrelAppState();
}

class _MinstrelAppState extends ConsumerState<MinstrelApp> {
  @override
  void initState() {
    super.initState();
    // Activate offline-mode infrastructure once the first frame ships.
    // SyncController.sync() is connectivity-aware (no-ops on no auth /
    // no server URL), so calling it unconditionally is safe.
    // Reading prefetcherProvider runs its constructor, which wires the
    // queue + settings listeners.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // ignore: unawaited_futures
      ref.read(syncControllerProvider.notifier).sync();
      ref.read(prefetcherProvider);
      // Metadata prefetcher: when /api/home returns, fire background
      // albumProvider/artistProvider reads for the top-N items in
      // each section so subsequent taps are drift hits, not network
      // round trips.
      ref.read(metadataPrefetcherProvider);
      // Live events (#392): subscribes to /api/events/stream and
      // invalidates publicly-scoped providers when relevant events
      // arrive. Also installs an AppLifecycleState observer for
      // resume-time defensive invalidation.
      ref.read(liveEventsDispatcherProvider);
      // Cache filler: background sweeper that walks cached_artists
      // / cached_albums for missing relations and fetches the
      // per-entity detail so tapping an artist surfaces albums
      // immediately (drift hit, no /api/artists/:id round-trip at
      // tap time). First sweep 10s after launch; every 5 minutes
      // thereafter. Throttled 200ms between requests so it never
      // competes with user activity.
      ref.read(cacheFillerProvider);
      // Mutation replayer: drains the cached_mutations queue when
      // connectivity comes back. Controllers (LikesController,
      // MyQuarantineController, the add-to-playlist + request flows)
      // enqueue on REST failure so user intent persists across
      // network loss instead of getting rolled back.
      ref.read(mutationReplayerProvider);
      // Play-events reporter (#415): the Flutter client otherwise
      // reports no plays at all — this feeds history, recommendation
      // scoring, scrobbles, and system-playlist rotation, and is the
      // path that carries the `source` tag for #415.
      ref.read(playEventsReporterProvider);
      // Resume-on-launch (#54): restores the last persisted session
      // (paused) and persists queue/index/position on track change,
      // pause, and app teardown. Pairs with the #52 idle teardown so a
      // torn-down session is recoverable instead of lost.
      ref.read(resumeControllerProvider);
      // Playback-error reporter (#58): turns the handler's silent
      // dead-track skips into a debounced/coalesced SnackBar so a
      // vanished track isn't mysterious (and aids server/cache debug).
      ref.read(playbackErrorReporterProvider);
      // Offline marker (#427 S1): periodic /healthz reachability
      // probe → offlineProvider. Read here to start the poller; S4
      // gates system-playlist play + Shuffle-all on it.
      ref.read(offlineProvider);
      // POST_NOTIFICATIONS (Android 13+) is denied-by-default until
      // requested; without it the media notification is silently
      // suppressed on physical devices. One-shot, post-first-frame so
      // it never blocks launch; no-op on <13 / once already decided.
      if (Platform.isAndroid) {
        // ignore: unawaited_futures
        Permission.notification.request();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    final router = ref.watch(routerProvider);
    final mode = ref.watch(themeModeProvider).value ?? AppThemeMode.system;
    return MaterialApp.router(
      title: 'Minstrel',
      scaffoldMessengerKey: scaffoldMessengerKey,
      theme: buildLightTheme(),
      darkTheme: buildDarkTheme(),
      themeMode: mode.materialMode,
      routerConfig: router,
    );
  }
}
