import 'dart:io' show Platform;

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

import '../../../core/contracts/library.dart';
import '../../../providers/di_providers.dart';
import '../../../providers/session_provider.dart';
import '../../../providers/tmdb_settings_provider.dart';
import '../../../shared/constants/breakpoints.dart';
import '../../../shared/utils/emby_ticks.dart';
import '../../../shared/utils/format_utils.dart';
import '../../../shared/utils/tmdb_episode_helpers.dart';
import '../../../shared/widgets/adaptive_modal.dart';
import '../../../shared/widgets/app_dialog.dart';
import '../../../shared/widgets/app_inline_alert.dart';
import '../../../shared/widgets/app_loading_ring.dart';
import '../../../shared/widgets/card_progress_bar.dart';
import '../../../shared/widgets/episode_thumb.dart';
import '../../../shared/widgets/frosted_panel.dart';
import '../../../shared/widgets/hover_scrollable_row.dart';
import '../../../shared/widgets/season_selector.dart';
import 'episode_selection_resolver.dart';

typedef EpisodeTapWithSeason =
    void Function(EmbyRawItem episode, int? seasonNumber);

class EpisodeListSection extends ConsumerStatefulWidget {
  final String seriesId;
  final String? tmdbSeriesId;
  final String? currentEpisodeId;


  final String? currentEpisodeSeasonId;


  final int? currentEpisodeSeasonNumber;
  final int? currentEpisodeIndexNumber;
  final String? initialSeasonId;
  final void Function(EmbyRawItem episode) onEpisodeTap;
  final EpisodeTapWithSeason? onEpisodeTapWithSeason;
  final void Function(EmbyRawItem episode)? onPlaybackPrefetch;
  final bool embedded;


  final String? serverId;


  final bool autoSelectFirstEpisode;


  final String? autoSelectEpisodeId;


  final String? autoSelectSeasonId;


  final Map<String, String>? showProviderIds;
  final String? showTitle;
  final int? showYear;


  final Widget? leading;

  const EpisodeListSection({
    super.key,
    required this.seriesId,
    this.tmdbSeriesId,
    this.currentEpisodeId,
    this.currentEpisodeSeasonId,
    this.currentEpisodeSeasonNumber,
    this.currentEpisodeIndexNumber,
    this.initialSeasonId,
    required this.onEpisodeTap,
    this.onEpisodeTapWithSeason,
    this.onPlaybackPrefetch,
    this.embedded = false,
    this.serverId,
    this.autoSelectFirstEpisode = true,
    this.autoSelectEpisodeId,
    this.autoSelectSeasonId,
    this.showProviderIds,
    this.showTitle,
    this.showYear,
    this.leading,
  });

  @override
  ConsumerState<EpisodeListSection> createState() => _EpisodeListSectionState();
}

class _EpisodeListSectionState extends ConsumerState<EpisodeListSection> {
  String? _selectedSeasonId;

  void _selectSeason(String seasonId) {
    if (_selectedSeasonId == seasonId) return;
    setState(() => _selectedSeasonId = seasonId);
  }

  @override
  Widget build(BuildContext context) {
    final scopedServerId = widget.serverId;
    late final String targetServerId;
    late final String baseUrl;
    late final String token;

    if (scopedServerId != null) {
      final authAsync = ref.watch(tmdbServerScopedAuthProvider(scopedServerId));
      final auth = authAsync.value;
      if (auth == null) {
        return authAsync.isLoading
            ? const Padding(
                padding: EdgeInsets.symmetric(vertical: 32),
                child: Center(child: AppLoadingRing()),
              )
            : const SizedBox.shrink();
      }
      targetServerId = scopedServerId;
      baseUrl = auth.baseUrl;
      token = auth.token;
    } else {
      final session = ref.watch(activeSessionProvider);
      if (session == null) return const SizedBox.shrink();
      targetServerId = session.serverId;
      baseUrl = session.serverUrl;
      token = session.token;
    }

    final width = MediaQuery.sizeOf(context).width;
    final compact = width < Breakpoints.detail;
    final hPad = compact ? 16.0 : 32.0;

    final seasonsAsync = ref.watch(
      seriesSeasonsDataProvider((
        serverId: targetServerId,
        seriesId: widget.seriesId,
      )),
    );

    return seasonsAsync.when(
      loading: () => const Padding(
        padding: EdgeInsets.symmetric(vertical: 32),
        child: Center(child: AppLoadingRing()),
      ),
      error: (error, stackTrace) => const SizedBox.shrink(),
      data: (seasons) {
        if (seasons.isEmpty) return const SizedBox.shrink();

        final String? effectiveSeasonId =
            _selectedSeasonId ??
            resolveInitialSeasonId(
              seasons: seasons,
              autoSelectSeasonId: widget.autoSelectSeasonId,
              initialSeasonId: widget.initialSeasonId,
              currentEpisodeSeasonId: widget.currentEpisodeSeasonId,
              currentEpisodeSeasonNumber: widget.currentEpisodeSeasonNumber,
            );
        EmbyRawSeason? selectedSeason;
        for (final season in seasons) {
          if (season.Id == effectiveSeasonId) {
            selectedSeason = season;
            break;
          }
        }

        final content = Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (widget.embedded && widget.leading != null) widget.leading!,
            const SizedBox(height: 16),
            if (effectiveSeasonId != null)
              _EpisodesBody(
                seasonTabsWidget: seasons.length > 1
                    ? SeasonSelector(
                        seasons: seasons,
                        selectedId: effectiveSeasonId,
                        onSelect: _selectSeason,
                      )
                    : null,
                seriesId: widget.seriesId,
                seasonId: effectiveSeasonId,
                seasonNumber: selectedSeason == null
                    ? null
                    : seasonNumberFrom(selectedSeason),
                tmdbSeriesId: widget.tmdbSeriesId,
                currentEpisodeId: widget.currentEpisodeId,
                currentEpisodeSeasonId: widget.currentEpisodeSeasonId,
                currentEpisodeSeasonNumber: widget.currentEpisodeSeasonNumber,
                currentEpisodeIndexNumber: widget.currentEpisodeIndexNumber,
                serverId: targetServerId,
                baseUrl: baseUrl,
                token: token,
                autoSelectFirstEpisode: widget.autoSelectFirstEpisode,
                autoSelectEpisodeId: widget.autoSelectEpisodeId,
                onEpisodeTap: widget.onEpisodeTap,
                onEpisodeTapWithSeason: widget.onEpisodeTapWithSeason,
                onPlaybackPrefetch: widget.onPlaybackPrefetch,
                showProviderIds: widget.showProviderIds,
                showTitle: widget.showTitle,
                showYear: widget.showYear,
              ),
          ],
        );

        if (widget.embedded) return content;

        return Padding(
          padding: EdgeInsets.symmetric(horizontal: hPad),
          child: FrostedPanel(
            radius: 16,
            padding: EdgeInsets.all(hPad),
            blurSigma: 15,
            border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
            color: Colors.black.withValues(alpha: 0.3),
            child: content,
          ),
        );
      },
    );
  }
}

class _EpisodesBody extends ConsumerStatefulWidget {
  final String seriesId;
  final String seasonId;
  final int? seasonNumber;
  final String? tmdbSeriesId;
  final String? currentEpisodeId;
  final String? currentEpisodeSeasonId;
  final int? currentEpisodeSeasonNumber;
  final int? currentEpisodeIndexNumber;
  final String serverId;
  final String baseUrl;
  final String token;
  final bool autoSelectFirstEpisode;
  final String? autoSelectEpisodeId;
  final void Function(EmbyRawItem episode) onEpisodeTap;
  final EpisodeTapWithSeason? onEpisodeTapWithSeason;
  final void Function(EmbyRawItem episode)? onPlaybackPrefetch;
  final Map<String, String>? showProviderIds;
  final String? showTitle;
  final int? showYear;
  final Widget? seasonTabsWidget;

  const _EpisodesBody({
    required this.seriesId,
    required this.seasonId,
    this.seasonNumber,
    this.tmdbSeriesId,
    required this.currentEpisodeId,
    this.currentEpisodeSeasonId,
    this.currentEpisodeSeasonNumber,
    this.currentEpisodeIndexNumber,
    required this.serverId,
    required this.baseUrl,
    required this.token,
    required this.autoSelectFirstEpisode,
    this.autoSelectEpisodeId,
    required this.onEpisodeTap,
    this.onEpisodeTapWithSeason,
    this.onPlaybackPrefetch,
    this.showProviderIds,
    this.showTitle,
    this.showYear,
    this.seasonTabsWidget,
  });

  @override
  ConsumerState<_EpisodesBody> createState() => _EpisodesBodyState();
}

class _EpisodesBodyState extends ConsumerState<_EpisodesBody> {
  static final double _itemWidth = Platform.isAndroid ? 180.0 : 260.0;
  static const _itemGap = 12.0;
  static final double _rowHeight = Platform.isAndroid ? 150.0 : 210.0;

  final ScrollController _scrollController = ScrollController();
  bool _hasScrolledToActive = false;
  bool _hasAutoSelected = false;

  @override
  void didUpdateWidget(covariant _EpisodesBody oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.seasonId != widget.seasonId ||
        oldWidget.currentEpisodeId != widget.currentEpisodeId) {
      _hasScrolledToActive = false;
    }
    if (oldWidget.seasonId != widget.seasonId ||
        oldWidget.autoSelectEpisodeId != widget.autoSelectEpisodeId) {
      _hasAutoSelected = false;
    }
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }


  String? _resolveActiveEpisodeId(List<EmbyRawItem> episodes) =>
      resolveActiveEpisodeId(
        episodes: episodes,
        currentEpisodeId: widget.currentEpisodeId,
        displayedSeasonId: widget.seasonId,
        displayedSeasonNumber: widget.seasonNumber,
        currentEpisodeSeasonId: widget.currentEpisodeSeasonId,
        currentEpisodeSeasonNumber: widget.currentEpisodeSeasonNumber,
        currentEpisodeIndexNumber: widget.currentEpisodeIndexNumber,
      );

  void _scrollToActive(List<EmbyRawItem> episodes, String? activeEpisodeId) {
    if (_hasScrolledToActive) return;
    if (activeEpisodeId == null) return;
    final idx = episodes.indexWhere((e) => e.Id == activeEpisodeId);
    if (idx < 0) return;
    _hasScrolledToActive = true;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted || !_scrollController.hasClients) return;
      final position = _scrollController.position;
      final itemExtent = _itemWidth + _itemGap;
      final viewport = position.viewportDimension;
      final target = idx * itemExtent - (viewport - _itemWidth) / 2;
      _scrollController.jumpTo(target.clamp(0.0, position.maxScrollExtent));
    });
  }

  EmbyRawItem? _prefetchTarget(
    List<EmbyRawItem> episodes,
    String? activeEpisodeId,
  ) {
    if (episodes.isEmpty) return null;
    if (activeEpisodeId != null && activeEpisodeId.isNotEmpty) {
      for (final episode in episodes) {
        if (episode.Id == activeEpisodeId) return episode;
      }
    }
    return episodes.first;
  }

  void _handleEpisodeTap(EmbyRawItem episode) {
    final tapWithSeason = widget.onEpisodeTapWithSeason;
    if (tapWithSeason != null) {
      tapWithSeason(episode, widget.seasonNumber);
      return;
    }
    widget.onEpisodeTap(episode);
  }

  void _showAllEpisodes(
    List<EmbyRawItem> episodes,
    String? activeEpisodeId,
    Map<int, String> tmdbStillUrls,
  ) {
    showAdaptiveModal<void>(
      context: context,
      maxWidth: 720,
      builder: (sheetContext) => _EpisodeGridSheet(
        episodes: episodes,
        seriesId: widget.seriesId,
        activeEpisodeId: activeEpisodeId,
        tmdbStillUrls: tmdbStillUrls,
        baseUrl: widget.baseUrl,
        token: widget.token,
        onEpisodeTap: (ep) {
          Navigator.of(sheetContext).pop();
          _handleEpisodeTap(ep);
        },
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final tmdbSettings = ref.watch(tmdbSettingsProvider).value;
    final canUseTmdbSeason =
        (tmdbSettings?.canRequest ?? false) &&
        widget.tmdbSeriesId != null &&
        widget.tmdbSeriesId!.isNotEmpty &&
        widget.seasonNumber != null;
    final tmdbSeason = canUseTmdbSeason
        ? ref
              .watch(
                tmdbSeasonDetailProvider((
                  tmdbId: widget.tmdbSeriesId!,
                  seasonNumber: widget.seasonNumber!,
                )),
              )
              .value
        : null;
    final tmdbStillUrls = tmdbStillUrlsByEpisode(
      tmdbSeason,
      tmdbSettings?.effectiveImageBaseUrl,
    );
    final episodesAsync = ref.watch(
      seriesEpisodesDataProvider((
        serverId: widget.serverId,
        seriesId: widget.seriesId,
        seasonId: widget.seasonId,
      )),
    );

    return episodesAsync.when(
      loading: () => const Padding(
        padding: EdgeInsets.symmetric(vertical: 32),
        child: Center(child: AppLoadingRing()),
      ),
      error: (error, stackTrace) => const Padding(
        padding: EdgeInsets.symmetric(vertical: 16),
        child: AppInlineAlert(message: '剧集列表加载失败'),
      ),
      data: (episodes) {
        if (episodes.isEmpty) {
          return Padding(
            padding: const EdgeInsets.symmetric(vertical: 16),
            child: Text(
              '暂无分集',
              style: TextStyle(
                fontSize: 13,
                color: Colors.white.withValues(alpha: 0.6),
              ),
            ),
          );
        }
        if (widget.currentEpisodeId == null &&
            !_hasAutoSelected &&
            episodes.isNotEmpty) {
          final targetId = widget.autoSelectEpisodeId;
          EmbyRawItem? target;
          if (targetId != null && targetId.isNotEmpty) {
            for (final e in episodes) {
              if (e.Id == targetId) {
                target = e;
                break;
              }
            }
          }
          final pick =
              target ?? (widget.autoSelectFirstEpisode ? episodes.first : null);
          if (pick != null) {
            _hasAutoSelected = true;
            WidgetsBinding.instance.addPostFrameCallback((_) {
              if (mounted) _handleEpisodeTap(pick);
            });
          }
        }
        final activeEpisodeId = _resolveActiveEpisodeId(episodes);
        final target = _prefetchTarget(episodes, activeEpisodeId);
        if (target != null) widget.onPlaybackPrefetch?.call(target);
        _scrollToActive(episodes, activeEpisodeId);
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                if (widget.seasonTabsWidget != null)
                  Expanded(child: widget.seasonTabsWidget!)
                else
                  const Spacer(),
                const SizedBox(width: 12),
                TextButton(
                  onPressed: () => _showAllEpisodes(
                    episodes,
                    activeEpisodeId,
                    tmdbStillUrls,
                  ),
                  style: TextButton.styleFrom(
                    foregroundColor: Colors.white.withValues(alpha: 0.6),
                    minimumSize: Size.zero,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 8,
                      vertical: 6,
                    ),
                    tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                    visualDensity: VisualDensity.compact,
                  ),
                  child: const Text('查看全部', style: TextStyle(fontSize: 13)),
                ),
              ],
            ),
            const SizedBox(height: 8),
            SizedBox(
              height: _rowHeight,
              child: HoverScrollableRow(
                controller: _scrollController,
                buttonInset: 4,
                scrollFraction: 0.5,
                builder: (_, controller) => ListView.builder(
                  controller: controller,
                  scrollDirection: Axis.horizontal,
                  itemCount: episodes.length,
                  itemBuilder: (_, i) {
                    final ep = episodes[i];
                    final tmdbStillUrl = ep.IndexNumber == null
                        ? null
                        : tmdbStillUrls[ep.IndexNumber];
                    return Padding(
                      padding: EdgeInsets.only(
                        right: i < episodes.length - 1 ? _itemGap : 0,
                      ),
                      child: _EpisodeCard(
                        episode: ep,
                        seriesId: widget.seriesId,
                        baseUrl: widget.baseUrl,
                        token: widget.token,
                        tmdbStillUrl: tmdbStillUrl,
                        isActive: ep.Id == activeEpisodeId,
                        onTap: () => _handleEpisodeTap(ep),
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        );
      },
    );
  }
}

class _EpisodeCard extends StatefulWidget {
  final EmbyRawItem episode;
  final String seriesId;
  final String baseUrl;
  final String token;
  final String? tmdbStillUrl;
  final bool isActive;
  final VoidCallback onTap;


  final double? width;
  final int overviewMaxLines;

  const _EpisodeCard({
    required this.episode,
    required this.seriesId,
    required this.baseUrl,
    required this.token,
    required this.tmdbStillUrl,
    required this.isActive,
    required this.onTap,
    this.width,
    this.overviewMaxLines = 1,
  });

  @override
  State<_EpisodeCard> createState() => _EpisodeCardState();
}

class _EpisodeCardState extends State<_EpisodeCard> {
  bool _hovered = false;

  void _showEpisodeOverviewDialog(BuildContext context, String overview) {
    showAppRawDialog<void>(
      context,
      constraints: const BoxConstraints(maxWidth: 480, maxHeight: 400),
      builder: (ctx) => ColoredBox(
        color: const Color(0xFF1a1c22),
        child: Padding(
          padding: const EdgeInsets.fromLTRB(24, 24, 24, 16),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                widget.episode.IndexNumber != null
                    ? '${formatEpisodeNumber(widget.episode.IndexNumber)}  ${widget.episode.Name}'
                    : widget.episode.Name,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w600,
                  color: Colors.white,
                ),
              ),
              const SizedBox(height: 14),
              Flexible(
                child: SingleChildScrollView(
                  child: Text(
                    overview,
                    style: TextStyle(
                      fontSize: 13,
                      height: 1.75,
                      color: Colors.white.withValues(alpha: 0.82),
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 12),
              Align(
                alignment: Alignment.centerRight,
                child: TextButton(
                  onPressed: () => Navigator.of(ctx).pop(),
                  child: Text(
                    '关闭',
                    style: TextStyle(
                      color: Colors.white.withValues(alpha: 0.6),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final ep = widget.episode;
    final progress = playbackProgress(widget.episode);
    final epNum = formatEpisodeNumber(ep.IndexNumber);

    return MouseRegion(
      onEnter: (_) => setState(() => _hovered = true),
      onExit: (_) => setState(() => _hovered = false),
      child: GestureDetector(
        onTap: widget.onTap,
        child: SizedBox(
          width: widget.width ?? _EpisodesBodyState._itemWidth,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              AspectRatio(
                aspectRatio: 16 / 9,
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: Stack(
                    fit: StackFit.expand,
                    children: [
                      EpisodeThumb(
                        episode: widget.episode,
                        seriesId: widget.seriesId,
                        baseUrl: widget.baseUrl,
                        token: widget.token,
                        tmdbStillUrl: widget.tmdbStillUrl,
                        aspectRatio: 16 / 9,
                      ),

                      if (_hovered)
                        Container(
                          color: Colors.black.withValues(alpha: 0.35),
                          child: const Center(
                            child: Icon(
                              Icons.play_circle_outline,
                              color: Colors.white,
                              size: 36,
                            ),
                          ),
                        ),

                      if (progress > 0) CardProgressBar(progress: progress),

                      if (epNum != null)
                        Positioned(
                          top: 6,
                          left: 6,
                          child: Container(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 6,
                              vertical: 2,
                            ),
                            decoration: BoxDecoration(
                              color: Colors.black.withValues(alpha: 0.65),
                              borderRadius: BorderRadius.circular(4),
                            ),
                            child: Text(
                              epNum,
                              style: const TextStyle(
                                color: Colors.white,
                                fontSize: 11,
                                fontWeight: FontWeight.w700,
                              ),
                            ),
                          ),
                        ),

                      if (widget.isActive)
                        Positioned.fill(
                          child: Container(
                            decoration: BoxDecoration(
                              border: Border.all(
                                color: Colors.white,
                                width: 2.5,
                              ),
                              borderRadius: BorderRadius.circular(8),
                            ),
                          ),
                        ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 6),
              Text(
                ep.IndexNumber != null
                    ? '${formatEpisodeNumber(ep.IndexNumber)}  ${ep.Name}'
                    : ep.Name,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 13,
                  fontWeight: widget.isActive
                      ? FontWeight.w600
                      : FontWeight.w500,
                  color: Colors.white.withValues(alpha: 0.9),
                ),
              ),
              if (ep.Overview != null && ep.Overview!.isNotEmpty)
                GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () =>
                      _showEpisodeOverviewDialog(context, ep.Overview!),
                  child: Text(
                    ep.Overview!,
                    maxLines: widget.overviewMaxLines,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.white.withValues(alpha: 0.6),
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}


class _EpisodeGridSheet extends StatefulWidget {
  final List<EmbyRawItem> episodes;
  final String seriesId;
  final String? activeEpisodeId;
  final Map<int, String> tmdbStillUrls;
  final String baseUrl;
  final String token;
  final void Function(EmbyRawItem episode) onEpisodeTap;

  const _EpisodeGridSheet({
    required this.episodes,
    required this.seriesId,
    required this.activeEpisodeId,
    required this.tmdbStillUrls,
    required this.baseUrl,
    required this.token,
    required this.onEpisodeTap,
  });

  @override
  State<_EpisodeGridSheet> createState() => _EpisodeGridSheetState();
}

class _EpisodeGridSheetState extends State<_EpisodeGridSheet> {
  static const _maxCrossAxisExtent = 240.0;
  static const _gridSpacing = 12.0;

  static const _childAspectRatio = 1.05;
  static const _hPad = 20.0;

  ScrollController? _controller;

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  ScrollController _controllerFor(double gridWidth) {
    final existing = _controller;
    if (existing != null) return existing;

    double offset = 0;
    final idx = widget.episodes.indexWhere(
      (e) => e.Id == widget.activeEpisodeId,
    );
    if (idx > 0) {

      final columns = (gridWidth / (_maxCrossAxisExtent + _gridSpacing))
          .ceil()
          .clamp(1, 100);
      final itemWidth = (gridWidth - (columns - 1) * _gridSpacing) / columns;
      final rowHeight = itemWidth / _childAspectRatio + _gridSpacing;
      offset = (idx ~/ columns) * rowHeight;
    }
    return _controller = ScrollController(initialScrollOffset: offset);
  }

  @override
  Widget build(BuildContext context) {

    return ColoredBox(
      color: const Color(0xFF1a1c22),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(_hPad, 16, _hPad, 12),
            child: Text(
              '${widget.episodes.length} 集',
              style: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w600,
                color: Colors.white,
              ),
            ),
          ),
          Flexible(
            child: LayoutBuilder(
              builder: (context, constraints) {
                final gridWidth = constraints.maxWidth - _hPad * 2;
                return GridView.builder(
                  controller: _controllerFor(gridWidth),
                  padding: const EdgeInsets.fromLTRB(_hPad, 0, _hPad, _hPad),
                  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                    maxCrossAxisExtent: _maxCrossAxisExtent,
                    mainAxisSpacing: _gridSpacing,
                    crossAxisSpacing: _gridSpacing,
                    childAspectRatio: _childAspectRatio,
                  ),
                  itemCount: widget.episodes.length,
                  itemBuilder: (_, i) {
                    final ep = widget.episodes[i];
                    final tmdbStillUrl = ep.IndexNumber == null
                        ? null
                        : widget.tmdbStillUrls[ep.IndexNumber];
                    return _EpisodeCard(
                      episode: ep,
                      seriesId: widget.seriesId,
                      baseUrl: widget.baseUrl,
                      token: widget.token,
                      tmdbStillUrl: tmdbStillUrl,
                      isActive: ep.Id == widget.activeEpisodeId,
                      width: double.infinity,
                      overviewMaxLines: 2,
                      onTap: () => widget.onEpisodeTap(ep),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
