import 'dart:async';
import 'dart:io' show Platform;

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

import '../../core/contracts/library.dart';
import '../../providers/di_providers.dart';
import '../../providers/emby_headers_provider.dart';
import '../../providers/last_played_version_store.dart';
import '../../providers/playback_info_prefetch_provider.dart';
import '../../providers/playback_active_provider.dart';
import '../../providers/session_provider.dart';
import '../../providers/version_priority_provider.dart';
import '../../shared/theme/app_theme.dart';
import '../../shared/utils/cross_server_search_key.dart';
import '../../shared/utils/app_logger.dart';
import '../../shared/widgets/app_snack_bar.dart';
import '../player/player_launcher.dart';
import 'emby_detail_body_android.dart';
import 'emby_detail_body_builder.dart';
import 'emby_detail_helpers.dart';
import 'android/android_detail_geometry.dart';
import 'model/selected_episode_merge.dart';
import 'model/stream_selection.dart';
import 'widgets/detail_scroll_progress.dart';


class EmbyDetailView extends ConsumerStatefulWidget {
  final String itemId;
  final String? initialSeriesId;


  final String? initialSeasonId;


  final EmbyRawItem? seedItem;

  const EmbyDetailView({
    super.key,
    required this.itemId,
    this.initialSeriesId,
    this.initialSeasonId,
    this.seedItem,
  });

  @override
  ConsumerState<EmbyDetailView> createState() => _EmbyDetailViewState();
}

class _EmbyDetailViewState extends ConsumerState<EmbyDetailView> {
  static const _tag = 'EmbyDetailView';

  Future<MediaDetailRes>? _detailFuture;
  MediaDetailRes? _data;
  EmbyRawItem? _selectedItem;
  EmbyRawItem? _tmdbSeriesItem;
  bool _isFavorite = false;
  bool _isPlayed = false;
  int _selectedSourceIdx = 0;
  int? _selectedSubtitleIdx;
  int _selectedAudioIdx = 0;
  bool _isLaunchingPlayer = false;

  String? _detailServerId;
  String? _originServerId;
  bool _serverReloadScheduled = false;
  String? _overrideItemId;
  String? _pendingMediaSourceId;
  bool _isRestoringOriginServer = false;


  CrossServerSourceCard? _selectedCrossServerCard;


  bool _crossServerPlaybackInFlight = false;
  bool _backInFlight = false;
  final _scrollController = ScrollController();
  final _scrollProgress = ValueNotifier<double>(0.0);
  late final DetailPageActivity _detailActivity;


  bool _didEnterDetailActivity = false;

  String get _detailItemId {
    if (_overrideItemId != null && _overrideItemId!.isNotEmpty) {
      return _overrideItemId!;
    }
    return widget.itemId;
  }

  @override
  void initState() {
    super.initState();
    _detailActivity = ref.read(detailPageActivityProvider.notifier);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      _didEnterDetailActivity = true;
      _detailActivity.enter();
    });
    _originServerId = ref.read(activeSessionProvider)?.serverId;
    _scrollController.addListener(_onScroll);
    _reload();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_didEnterDetailActivity) _detailActivity.leave();
    });
    _scrollController.removeListener(_onScroll);
    _scrollController.dispose();
    _scrollProgress.dispose();
    super.dispose();
  }

  void _onScroll() {
    _scrollProgress.value = computeDetailScrollProgress(
      context,
      _scrollController.offset,
      collapseExtent: Platform.isAndroid
          ? computeAndroidDetailCollapseExtent(context)
          : null,
    );
  }

  @override
  void didUpdateWidget(covariant EmbyDetailView oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.itemId != widget.itemId ||
        oldWidget.initialSeriesId != widget.initialSeriesId ||
        oldWidget.initialSeasonId != widget.initialSeasonId) {
      _overrideItemId = null;
      _reload();
    }
  }


  void _reload({bool invalidateCache = false}) {
    _detailServerId = ref.read(activeSessionProvider)?.serverId;
    _serverReloadScheduled = false;

    _resetScrollState();
    if (invalidateCache) {
      final serverId = _detailServerId ?? '';
      ref.invalidate(
        mediaDetailDataProvider((serverId: serverId, itemId: _detailItemId)),
      );
    }
    setState(() {
      _data = null;
      _selectedItem = null;
      _tmdbSeriesItem = null;
      _selectedSourceIdx = 0;
      _selectedSubtitleIdx = null;
      _selectedAudioIdx = 0;
      _selectedCrossServerCard = null;
      _isLaunchingPlayer = false;
      _detailFuture = _loadDetail();
    });
  }

  void _resetScrollState() {
    _scrollProgress.value = 0.0;
    if (_scrollController.hasClients) {
      _scrollController.jumpTo(0);
    }
  }

  Future<MediaDetailRes> _loadDetail() async {
    final capturedServerId = _detailServerId ?? '';
    final initialSeriesId = widget.initialSeriesId;
    if (initialSeriesId != null &&
        initialSeriesId.isNotEmpty &&
        capturedServerId.isNotEmpty) {
      unawaited(
        ref.read(
          mediaDetailDataProvider((
            serverId: capturedServerId,
            itemId: initialSeriesId,
          )).future,
        ),
      );
    }
    final res = await ref.read(
      mediaDetailDataProvider((
        serverId: capturedServerId,
        itemId: _detailItemId,
      )).future,
    );
    if (!mounted) return res;
    AppLogger.debug(
      _tag,
      '_loadDetail entry itemId=$_detailItemId '
      'detailServerId=$capturedServerId '
      'activeServerId=${ref.read(activeSessionProvider)?.serverId} '
      'base.Id=${res.base.Id} type=${res.base.Type} '
      'seriesId=${res.base.SeriesId} seasonId=${res.base.SeasonId} '
      'index=${res.base.IndexNumber} '
      'sources=${mediaSourcesOfItem(res.base).length}',
    );
    setState(() {
      _data = res;
      _selectedItem = res.base;
      _isFavorite = res.base.UserData?.IsFavorite ?? false;
      _isPlayed = res.base.UserData?.Played ?? false;
    });
    unawaited(_loadRememberedVersion(res.base));
    final seriesId = seriesIdForEpisode(res.base, widget.itemId);
    if (seriesId != null) {
      unawaited(_loadSeriesInfo(seriesId, capturedServerId, res.base));
    } else {
      _firePrewarm(res.base);
    }
    _prefetchPlaybackInfoFor(res.base);
    return res;
  }

  Future<void> _loadSeriesInfo(
    String seriesId,
    String serverId,
    EmbyRawItem episode,
  ) async {
    try {
      final seriesRes = await ref.read(
        mediaDetailDataProvider((serverId: serverId, itemId: seriesId)).future,
      );
      if (!mounted) return;
      setState(() {
        _tmdbSeriesItem = seriesRes.base;
      });
      _firePrewarm(seriesRes.base);
    } catch (_) {
      _firePrewarm(episode);
    }
  }

  void _firePrewarm(EmbyRawItem refItem) {
    if (refItem.Type != 'Series' && refItem.Type != 'Episode') return;
    final pids = providerIdsOfItem(refItem);
    if (pids.isEmpty) return;
    final activeServerId = ref.read(activeSessionProvider)?.serverId ?? '';
    if (activeServerId.isEmpty) return;
    unawaited(
      ref.read(
        seriesPrewarmProvider((
          activeServerId: activeServerId,
          seriesProviderIdQuery: encodeProviderIdsForKey(pids),
        )).future,
      ),
    );
  }

  void _syncDetailWithActiveServer(String serverId) {
    final loadedServerId = _detailServerId;
    if (loadedServerId == null) {
      _detailServerId = serverId;
      return;
    }
    if (_crossServerPlaybackInFlight) return;
    if (loadedServerId == serverId || _serverReloadScheduled) return;

    _serverReloadScheduled = true;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      _serverReloadScheduled = false;

      final currentServerId = ref.read(activeSessionProvider)?.serverId;
      if (currentServerId == null || _detailServerId == currentServerId) {
        return;
      }
      _reload(invalidateCache: true);
    });
  }


  void _selectCrossServerSource(CrossServerSourceCard card) {
    setState(() {
      _selectedCrossServerCard = card;
      _selectedSubtitleIdx = null;
      _selectedAudioIdx = 0;
    });
    _prefetchPlaybackInfoForCrossServer(card);
  }

  void _onEpisodeTap(EmbyRawItem episode) {
    AppLogger.debug(
      _tag,
      '_onEpisodeTap id=${episode.Id} name=${episode.Name} '
      'type=${episode.Type} seriesId=${episode.SeriesId} '
      'seasonId=${episode.SeasonId} index=${episode.IndexNumber} '
      'positionTicks=${episode.UserData?.PlaybackPositionTicks} '
      'sources=${mediaSourcesOfItem(episode).length}',
    );
    setState(() {
      _selectedItem = episode;
      _isFavorite = episode.UserData?.IsFavorite ?? false;
      _isPlayed = episode.UserData?.Played ?? false;
      _selectedSourceIdx = 0;
      _selectedSubtitleIdx = null;
      _selectedAudioIdx = 0;
      _selectedCrossServerCard = null;
    });
    unawaited(_loadSelectedEpisodeDetail(episode.Id));
  }


  Future<void> _loadSelectedEpisodeDetail(String episodeId) async {
    final serverId = _detailServerId ?? '';
    if (serverId.isEmpty || episodeId.isEmpty) return;
    try {
      final res = await ref.read(
        mediaDetailDataProvider((serverId: serverId, itemId: episodeId)).future,
      );
      final listItem = _selectedItem;
      if (!mounted || listItem?.Id != episodeId) return;
      final merged = listItem != null
          ? mergeSelectedEpisodeDetail(detail: res.base, listItem: listItem)
          : res.base;
      final mergedSources = mediaSourcesOfItem(merged);
      AppLogger.debug(
        _tag,
        '_loadSelectedEpisodeDetail episodeId=$episodeId '
        'listItem.Id=${listItem?.Id} detail.base.Id=${res.base.Id} '
        'merged.Id=${merged.Id} mergedSources=${mergedSources.length} '
        'firstSourceId=${mergedSources.isNotEmpty ? mergedSources.first.Id : null}',
      );
      setState(() {
        _selectedItem = merged;
        _isFavorite = merged.UserData?.IsFavorite ?? false;
        _isPlayed = merged.UserData?.Played ?? false;
      });
      unawaited(_loadRememberedVersion(merged));
      _prefetchPlaybackInfoFor(merged);
    } catch (_) {}
  }

  void _selectSourceIndex(int index) {
    setState(() {
      _selectedSourceIdx = index;
      _selectedSubtitleIdx = null;
      _selectedAudioIdx = 0;
      _selectedCrossServerCard = null;
      _pendingMediaSourceId = null;
    });
    final item = _selectedItem;
    if (item != null) _prefetchPlaybackInfoFor(item);
  }

  void _selectSubtitleIndex(int? index) {
    setState(() => _selectedSubtitleIdx = index);
  }

  void _selectAudioIndex(int index) {
    setState(() => _selectedAudioIdx = index);
  }

  void _applyPendingMediaSource(int index) {
    if (!mounted) return;
    setState(() {
      _selectedSourceIdx = index;
      _pendingMediaSourceId = null;
    });
    final item = _selectedItem;
    if (item != null) _prefetchPlaybackInfoFor(item);
  }

  void _clearPendingMediaSource() {
    _pendingMediaSourceId = null;
  }

  int _prefetchStartTimeTicksForCrossServer(CrossServerSourceCard card) {
    final selectedTicks = _selectedItem?.UserData?.PlaybackPositionTicks;
    if (selectedTicks != null && selectedTicks > 0) return selectedTicks;
    return 0;
  }

  void _prefetchPlaybackInfoFor(EmbyRawItem item) {
    final serverId = _detailServerId ?? '';
    if (serverId.isEmpty || item.Id.isEmpty || item.Type == 'Series') return;
    final key = _playbackInfoPrefetchKeyFor(
      item,
      mediaSourceId: _currentLocalMediaSourceIdFor(item),
      fromStart: false,
    );
    AppLogger.debug(
      _tag,
      'playbackInfo prefetch request local '
      '${describePlaybackInfoPrefetchKey(key)}',
    );
    unawaited(ref.read(playbackInfoPrefetchProvider(key).future));
  }

  void _prefetchPlaybackInfoForCrossServer(CrossServerSourceCard card) {
    if (card.serverId.isEmpty ||
        card.landingItemId.isEmpty ||
        card.item.Type == 'Series') {
      return;
    }
    final key = (
      serverId: card.serverId,
      itemId: card.landingItemId,
      mediaSourceId: card.mediaSourceId.isEmpty ? null : card.mediaSourceId,
      startTimeTicks: _prefetchStartTimeTicksForCrossServer(card),
    );
    AppLogger.debug(
      _tag,
      'playbackInfo prefetch request crossServer '
      '${describePlaybackInfoPrefetchKey(key)}',
    );
    unawaited(ref.read(playbackInfoPrefetchProvider(key).future));
  }

  PlaybackInfoPrefetchKey _playbackInfoPrefetchKeyFor(
    EmbyRawItem item, {
    String? mediaSourceId,
    required bool fromStart,
  }) {
    return (
      serverId: _detailServerId ?? '',
      itemId: item.Id,
      mediaSourceId: mediaSourceId,
      startTimeTicks: fromStart
          ? 0
          : (item.UserData?.PlaybackPositionTicks ?? 0),
    );
  }

  String? _currentLocalMediaSourceIdFor(EmbyRawItem item) {
    if (_selectedCrossServerCard != null) return null;
    final priorities =
        ref.read(versionPriorityProvider).value?.activePriorities ?? [];
    final sources = sortMediaSources(mediaSourcesOfItem(item), priorities);
    if (_selectedSourceIdx < 0 || _selectedSourceIdx >= sources.length) {
      return null;
    }
    return sources[_selectedSourceIdx].Id;
  }


  Future<void> _loadRememberedVersion(EmbyRawItem item) async {
    final serverId = _detailServerId ?? '';
    if (serverId.isEmpty || item.Id.isEmpty || item.Type == 'Series') return;
    try {
      final remembered = await ref
          .read(lastPlayedVersionStoreProvider)
          .get(serverId: serverId, itemId: item.Id);
      if (!mounted || remembered == null) return;
      if (_selectedItem?.Id != item.Id) return;
      if (_selectedSourceIdx != 0) return;
      setState(() => _pendingMediaSourceId = remembered);
    } catch (_) {}
  }

  void _refreshSelectedItemState(EmbyRawItem item) {
    setState(() {
      _selectedItem = item;
      _isFavorite = item.UserData?.IsFavorite ?? false;
      _isPlayed = item.UserData?.Played ?? false;
    });
  }

  Future<void> _toggleFavorite() async {
    final item = _selectedItem;
    if (item == null) return;
    final next = !_isFavorite;
    final uc = await ref.read(setFavoriteUseCaseProvider.future);
    final res = await uc.execute(
      FavoriteSetReq(itemId: item.Id, isFavorite: next),
    );
    if (!mounted) return;
    setState(() => _isFavorite = res.isFavorite);
    final serverId = _detailServerId;
    if (serverId != null && serverId.isNotEmpty) {
      ref.invalidate(
        mediaDetailDataProvider((serverId: serverId, itemId: item.Id)),
      );
    }
  }

  Future<void> _togglePlayed() async {
    final item = _selectedItem;
    if (item == null) return;
    final next = !_isPlayed;
    final uc = await ref.read(setPlayedUseCaseProvider.future);
    final res = await uc.execute(PlayedSetReq(itemId: item.Id, played: next));
    if (!mounted) return;
    setState(() => _isPlayed = res.played);
    unawaited(_syncTraktPlayedChange(item, res.played));
    final serverId = _detailServerId;
    if (serverId != null && serverId.isNotEmpty) {
      ref.invalidate(
        mediaDetailDataProvider((serverId: serverId, itemId: item.Id)),
      );
    }
  }

  Future<void> _syncTraktPlayedChange(EmbyRawItem item, bool played) async {
    try {
      final service = await ref.read(traktSyncServiceProvider.future);
      await service.syncEmbyPlayedChange(
        item: item,
        played: played,
        seriesItem: _tmdbSeriesItem,
      );
    } catch (_) {}
  }

  Future<void> _goBack() async {
    if (_backInFlight || _isRestoringOriginServer) return;
    _backInFlight = true;
    final currentServerId = ref.read(activeSessionProvider)?.serverId;
    if (_originServerId != null && currentServerId != _originServerId) {
      setState(() => _isRestoringOriginServer = true);
      try {
        await ref
            .read(sessionProvider.notifier)
            .switchSession(_originServerId!);
      } catch (_) {
        if (!mounted) return;
        setState(() => _isRestoringOriginServer = false);
        _backInFlight = false;
        showAppSnackBar(context, '恢复原服务器失败', tone: AppSnackTone.error);
        return;
      }
    }
    if (!mounted) return;
    if (_isRestoringOriginServer) {
      setState(() => _isRestoringOriginServer = false);
    }
    if (context.canPop()) {
      context.pop();
    } else {
      context.go('/');
    }
  }

  Future<void> _launchPlayer(
    EmbyRawItem displayItem,
    List<EmbyRawMediaSource> sources, {
    bool fromStart = false,
    int? overrideSubtitleIdx,
    String? backdropUrl,
  }) async {
    if (_isLaunchingPlayer) return;
    setState(() => _isLaunchingPlayer = true);
    try {
      final crossServerCard = _selectedCrossServerCard;
      final selectedSrc = _selectedSourceIdx < sources.length
          ? sources[_selectedSourceIdx]
          : null;
      final subIdx = overrideSubtitleIdx ?? _selectedSubtitleIdx;
      final audioIdx = _selectedAudioIdx;
      final streamSelection = resolveStreamSelection(
        selectedSrc,
        subIdx,
        audioIdx,
      );
      final subStreamIndex = streamSelection.preferredSubtitleStreamIndex;
      final audioStreamIndex = streamSelection.preferredAudioStreamIndex;

      final crossServerMediaSourceId =
          crossServerCard?.mediaSourceId.isEmpty ?? true
          ? null
          : crossServerCard?.mediaSourceId;
      final playedItemId = crossServerCard?.landingItemId ?? displayItem.Id;
      final playServerId = crossServerCard?.serverId ?? _detailServerId;
      final playMediaSourceId = crossServerCard != null
          ? crossServerMediaSourceId
          : selectedSrc?.Id;
      final playStartTicks = crossServerCard != null
          ? _prefetchStartTimeTicksForCrossServer(crossServerCard)
          : (displayItem.UserData?.PlaybackPositionTicks ?? 0);
      final effectiveFromStart = crossServerCard != null
          ? (fromStart || playStartTicks <= 0)
          : fromStart;
      AppLogger.debug(
        _tag,
        '_launchPlayer displayItem.Id=$playedItemId name=${displayItem.Name} '
        'type=${displayItem.Type} selectedSourceIdx=$_selectedSourceIdx '
        'serverId=$playServerId crossServer=${crossServerCard != null} '
        'selectedSrc.Id=${selectedSrc?.Id} sources=${sources.length} '
        'subIdx=$subIdx audioIdx=$audioIdx '
        'subStreamIndex=$subStreamIndex audioStreamIndex=$audioStreamIndex '
        'fromStart=$effectiveFromStart '
        'positionTicks=$playStartTicks',
      );
      Future<PlayerExitResult?> doLaunch() => PlayerLauncher.launch(
        context: context,
        itemId: playedItemId,
        serverId: playServerId?.isEmpty ?? true ? null : playServerId,
        mediaSourceId: playMediaSourceId,
        preferredSubtitleStreamIndex: crossServerCard != null
            ? -1
            : subStreamIndex,
        preferredAudioStreamIndex: crossServerCard != null
            ? null
            : audioStreamIndex,
        startPositionTicks: effectiveFromStart ? null : playStartTicks,
        fromStart: effectiveFromStart,
        backdropUrl: backdropUrl,
      );
      final PlayerExitResult? exitResult;
      if (crossServerCard != null) {
        exitResult = await _withCrossServerSession(
          crossServerCard,
          action: doLaunch,
        );
      } else {
        exitResult = await doLaunch();
      }
      final positionTicks = exitResult?.positionTicks;
      if (mounted && positionTicks != null && positionTicks > 0) {
        final item = _selectedItem;
        if (item != null && item.Id == exitResult?.itemId) {
          final userData = (item.UserData ?? const EmbyRawUserData()).copyWith(
            PlaybackPositionTicks: positionTicks,
          );
          _refreshSelectedItemState(item.copyWith(UserData: userData));
          final exitSourceId = exitResult?.mediaSourceId;
          if (exitSourceId != null && exitSourceId.isNotEmpty) {
            final idx = sources.indexWhere((s) => s.Id == exitSourceId);
            if (idx != -1 && idx != _selectedSourceIdx) {
              _selectSourceIndex(idx);
            }
          }
        }
      }
      unawaited(
        _refreshSelectedItemProgress(
          displayItem.Id,
          optimisticTicks: positionTicks,
        ),
      );
    } finally {
      if (mounted && _isLaunchingPlayer) {
        setState(() => _isLaunchingPlayer = false);
      }
    }
  }

  Future<T> _withCrossServerSession<T>(
    CrossServerSourceCard card, {
    required Future<T> Function() action,
  }) async {
    _crossServerPlaybackInFlight = true;
    final detailServerId = _detailServerId;
    try {
      if (ref.read(activeSessionProvider)?.serverId != card.serverId) {
        await ref.read(sessionProvider.notifier).switchSession(card.serverId);
      }
      return await action();
    } finally {
      if (mounted &&
          detailServerId != null &&
          detailServerId.isNotEmpty &&
          ref.read(activeSessionProvider)?.serverId != detailServerId) {
        try {
          await ref
              .read(sessionProvider.notifier)
              .switchSession(detailServerId);
        } catch (_) {}
      }
      if (mounted) _crossServerPlaybackInFlight = false;
    }
  }

  Future<void> _refreshSelectedItemProgress(
    String itemId, {
    int? optimisticTicks,
  }) async {
    if (!mounted) return;
    try {
      final capturedServerId = _detailServerId ?? '';
      final uc = await ref.read(mediaDetailUseCaseProvider.future);
      if (!mounted) return;
      final res = await uc.execute(MediaDetailReq(itemId: itemId));
      final prev = _selectedItem;
      if (!mounted || prev?.Id != itemId) return;
      var merged = prev != null
          ? mergeSelectedEpisodeDetail(detail: res.base, listItem: prev)
          : res.base;
      if (optimisticTicks != null &&
          optimisticTicks > 0 &&
          merged.UserData?.Played != true) {
        final serverTicks = merged.UserData?.PlaybackPositionTicks ?? 0;
        if (optimisticTicks > serverTicks) {
          merged = merged.copyWith(
            UserData: (merged.UserData ?? const EmbyRawUserData()).copyWith(
              PlaybackPositionTicks: optimisticTicks,
            ),
          );
        }
      }
      _refreshSelectedItemState(merged);
      if (capturedServerId.isNotEmpty) {
        ref.invalidate(
          mediaDetailDataProvider((serverId: capturedServerId, itemId: itemId)),
        );
      }
    } catch (_) {}
  }

  @override
  Widget build(BuildContext context) {
    final session = ref.watch(activeSessionProvider);
    if (session == null) return const SizedBox.shrink();
    _syncDetailWithActiveServer(session.serverId);

    final imageHeaders = ref.watch(embyImageHeadersProvider).value;
    final priorities =
        ref.watch(versionPriorityProvider).value?.activePriorities ?? [];

    Widget buildBody(AsyncSnapshot<MediaDetailRes> snap) {
      if (Platform.isAndroid) {
        return EmbyDetailBodyAndroid(
          snap: snap,
          detailData: _data,
          seedItem: widget.seedItem,
          selectedItem: _selectedItem,
          tmdbSeriesItem: _tmdbSeriesItem,
          detailServerId: _detailServerId,
          initialSeasonId: widget.initialSeasonId,
          pendingMediaSourceId: _pendingMediaSourceId,

          scrollController: _scrollController,
          scrollProgress: _scrollProgress,
          imageHeaders: imageHeaders,
          session: session,
          priorities: priorities,
          selectedSourceIdx: _selectedSourceIdx,
          selectedSubtitleIdx: _selectedSubtitleIdx,
          selectedAudioIdx: _selectedAudioIdx,
          selectedCrossServerCard: _selectedCrossServerCard,
          isPlayed: _isPlayed,
          isFavorite: _isFavorite,
          isLaunchingPlayer: _isLaunchingPlayer,
          onBack: () => unawaited(_goBack()),
          onRetry: () => _reload(invalidateCache: true),
          onTogglePlayed: () => unawaited(_togglePlayed()),
          onToggleFavorite: () => unawaited(_toggleFavorite()),
          onSelectSourceIndex: _selectSourceIndex,
          onSelectSubtitleIndex: _selectSubtitleIndex,
          onSelectAudioIndex: _selectAudioIndex,
          onEpisodeTap: _onEpisodeTap,
          onSelectCrossServerSource: _selectCrossServerSource,
          onApplyPendingMediaSource: _applyPendingMediaSource,
          onClearPendingMediaSource: _clearPendingMediaSource,
          onLaunchPlayer: _launchPlayer,
        );
      }
      return buildEmbyDetailBody(
        context: context,
        ref: ref,
        snap: snap,
        detailData: _data,
        seedItem: widget.seedItem,
        selectedItem: _selectedItem,
        tmdbSeriesItem: _tmdbSeriesItem,
        detailServerId: _detailServerId,
        initialSeasonId: widget.initialSeasonId,
        pendingMediaSourceId: _pendingMediaSourceId,

        scrollController: _scrollController,
        scrollProgress: _scrollProgress,
        imageHeaders: imageHeaders,
        session: session,
        priorities: priorities,
        selectedSourceIdx: _selectedSourceIdx,
        selectedSubtitleIdx: _selectedSubtitleIdx,
        selectedAudioIdx: _selectedAudioIdx,
        selectedCrossServerCard: _selectedCrossServerCard,
        isPlayed: _isPlayed,
        isFavorite: _isFavorite,
        isLaunchingPlayer: _isLaunchingPlayer,
        onBack: () => unawaited(_goBack()),
        onRetry: () => _reload(invalidateCache: true),
        onTogglePlayed: () => unawaited(_togglePlayed()),
        onToggleFavorite: () => unawaited(_toggleFavorite()),
        onSelectSourceIndex: _selectSourceIndex,
        onSelectSubtitleIndex: _selectSubtitleIndex,
        onSelectAudioIndex: _selectAudioIndex,
        onEpisodeTap: _onEpisodeTap,
        onSelectCrossServerSource: _selectCrossServerSource,
        onApplyPendingMediaSource: _applyPendingMediaSource,
        onClearPendingMediaSource: _clearPendingMediaSource,
        onLaunchPlayer: _launchPlayer,
      );
    }

    final content = FutureBuilder<MediaDetailRes>(
      future: _detailFuture,
      builder: (_, snap) => buildBody(snap),
    );

    if (Platform.isAndroid) {
      return Theme(data: AppTheme.dark(), child: content);
    }

    return Theme(
      data: AppTheme.dark(),
      child: Scaffold(backgroundColor: Colors.black, body: content),
    );
  }
}
