import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';

import '../../media/media_item.dart';
import '../../mpv/mpv.dart';
import '../../models/livetv_capture_buffer.dart';
import '../../media/media_source_info.dart';
import '../../services/scrub_preview_source.dart';
import '../../utils/desktop_window_padding.dart';
import '../../i18n/strings.g.dart';
import 'widgets/circular_control_button.dart';
import 'widgets/content_strip.dart';
import 'widgets/first_frame_guard.dart';
import 'widgets/play_pause_stream_builder.dart';
import 'widgets/live_timeline_bar.dart';
import 'widgets/video_controls_header.dart';
import 'widgets/video_timeline_bar.dart';

/// Mobile video controls layout for Plex video player
///
/// Displays a full-screen overlay with:
/// - Top bar: Back button, title, and track/chapter controls
/// - Center: Large playback controls (seek backward, play/pause, seek forward)
/// - Bottom bar: Timeline slider with chapter markers and timestamps
///
/// When chapters or queue are available, the user can swipe up on the bottom
/// area to slide a content strip into view. The playback controls and timeline
/// fade out while the strip slides up — only the top bar stays fixed.
class MobileVideoControls extends StatefulWidget {
  final Player player;
  final MediaItem metadata;
  final List<MediaChapter> chapters;
  final bool chaptersLoaded;
  final bool showChapterMarkersOnTimeline;
  final int seekTimeSmall;
  final Widget trackChapterControls;
  final Function(Duration) onSeek;
  final Function(Duration) onSeekEnd;
  final Function(Duration)? onSeekCompleted;
  final VoidCallback onPlayPause;
  final VoidCallback? onCancelAutoHide;
  final VoidCallback? onStartAutoHide;
  final VoidCallback? onBack;
  final VoidCallback? onNext;
  final VoidCallback? onPrevious;

  /// Whether the user can control playback (false in host-only mode for non-host).
  final bool canControl;

  /// Notifier for whether first video frame has rendered (shows loading state when false).
  final ValueNotifier<bool>? hasFirstFrame;

  /// Optional callback that returns thumbnail image bytes for a given timestamp.
  final ScrubFrame? Function(Duration time)? thumbnailDataBuilder;

  /// Whether this is a live TV stream
  final bool isLive;

  /// Channel name for live TV display
  final String? liveChannelName;

  // Live TV time-shift
  final CaptureBuffer? captureBuffer;
  final bool isAtLiveEdge;
  final double streamStartEpoch;
  final ValueChanged<int>? onLiveSeek;

  /// Server ID for chapter thumbnails in the content strip
  final String? serverId;

  /// Whether to show the queue tab in the content strip
  final bool showQueueTab;

  /// Callback when a queue item is selected from the content strip
  final Function(MediaItem)? onQueueItemSelected;

  /// Notifier for controls visibility (used to reset strip on hide)
  final ValueNotifier<bool>? controlsVisible;

  /// Called when the content strip visibility changes
  final ValueChanged<bool>? onStripVisibilityChanged;

  const MobileVideoControls({
    super.key,
    required this.player,
    required this.metadata,
    required this.chapters,
    required this.chaptersLoaded,
    this.showChapterMarkersOnTimeline = true,
    required this.seekTimeSmall,
    required this.trackChapterControls,
    required this.onSeek,
    required this.onSeekEnd,
    required this.onPlayPause,
    this.onSeekCompleted,
    this.onCancelAutoHide,
    this.onStartAutoHide,
    this.onBack,
    this.onNext,
    this.onPrevious,
    this.canControl = true,
    this.hasFirstFrame,
    this.thumbnailDataBuilder,
    this.isLive = false,
    this.liveChannelName,
    this.captureBuffer,
    this.isAtLiveEdge = true,
    this.streamStartEpoch = 0,
    this.onLiveSeek,
    this.serverId,
    this.showQueueTab = false,
    this.onQueueItemSelected,
    this.controlsVisible,
    this.onStripVisibilityChanged,
  });

  @override
  State<MobileVideoControls> createState() => _MobileVideoControlsState();
}

class _MobileVideoControlsState extends State<MobileVideoControls> with SingleTickerProviderStateMixin {
  late final AnimationController _stripAnim;
  bool _stripVisible = false;

  /// Drag distance (in pixels) required to fully reveal the strip.
  static const _dragExtent = 150.0;

  bool get _hasStripContent =>
      widget.chapters.isNotEmpty || (widget.showQueueTab && widget.onQueueItemSelected != null);

  @override
  void initState() {
    super.initState();
    _stripAnim = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
    _stripAnim.addListener(_onStripAnimChanged);
    widget.controlsVisible?.addListener(_onControlsVisibilityChanged);
  }

  @override
  void didUpdateWidget(MobileVideoControls oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.controlsVisible != widget.controlsVisible) {
      oldWidget.controlsVisible?.removeListener(_onControlsVisibilityChanged);
      widget.controlsVisible?.addListener(_onControlsVisibilityChanged);
    }
  }

  @override
  void dispose() {
    widget.controlsVisible?.removeListener(_onControlsVisibilityChanged);
    _stripAnim.removeListener(_onStripAnimChanged);
    _stripAnim.dispose();
    super.dispose();
  }

  void _onStripAnimChanged() {
    final visible = _stripAnim.value > 0.5;
    if (visible != _stripVisible) {
      _stripVisible = visible;
      widget.onStripVisibilityChanged?.call(visible);
    }
  }

  void _onControlsVisibilityChanged() {
    if (widget.controlsVisible?.value == false && _stripVisible) {
      // Just notify parent that strip is no longer active — don't animate,
      // let the overlay fade out with the strip still showing.
      _stripVisible = false;
      widget.onStripVisibilityChanged?.call(false);
    } else if (widget.controlsVisible?.value == true && _stripAnim.value > 0) {
      // Reset strip when controls reappear so page 0 is shown.
      _stripAnim.value = 0;
    }
  }

  void _onVerticalDragUpdate(DragUpdateDetails details) {
    // Negative primaryDelta = swipe up = reveal strip (increase value)
    _stripAnim.value -= (details.primaryDelta ?? 0) / _dragExtent;
  }

  void _onVerticalDragEnd(DragEndDetails details) {
    final velocity = details.primaryVelocity ?? 0;
    // Fast swipe up or past halfway without fast swipe down → show strip
    if (velocity < -200 || (_stripAnim.value > 0.5 && velocity < 200)) {
      _stripAnim.forward();
    } else {
      _stripAnim.reverse();
    }
  }

  @override
  Widget build(BuildContext context) {
    if (!_hasStripContent) {
      return Column(
        children: [
          _buildTopBar(context),
          const Spacer(),
          _buildPlaybackControls(context),
          const Spacer(),
          _buildBottomBar(context),
        ],
      );
    }

    // Top bar stays fixed. Everything below it is a Stack where the controls
    // fade out and the content strip slides up from the bottom on swipe.
    return Column(
      children: [
        _buildTopBar(context),
        Expanded(
          child: GestureDetector(
            onVerticalDragUpdate: _onVerticalDragUpdate,
            onVerticalDragEnd: _onVerticalDragEnd,
            behavior: HitTestBehavior.translucent,
            child: ListenableBuilder(
              listenable: _stripAnim,
              builder: (context, _) {
                final t = _stripAnim.value;
                return Stack(
                  clipBehavior: Clip.none,
                  children: [
                    // Normal controls — fade out as strip appears
                    Positioned.fill(
                      child: IgnorePointer(
                        ignoring: t > 0.5,
                        child: Opacity(
                          opacity: (1 - t * 2).clamp(0.0, 1.0),
                          child: Stack(
                            children: [
                              Column(
                                children: [
                                  const Spacer(),
                                  _buildPlaybackControls(context),
                                  const Spacer(),
                                  _buildBottomBar(context),
                                ],
                              ),
                              const Positioned(
                                left: 0,
                                right: 0,
                                bottom: 12,
                                child: Icon(Symbols.keyboard_arrow_up_rounded, color: Colors.white24, size: 24),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                    // Content strip — slides up from below the bottom edge
                    Align(
                      alignment: Alignment.bottomCenter,
                      child: FractionalTranslation(
                        translation: Offset(0, 1 - t),
                        child: IgnorePointer(
                          ignoring: t < 0.5,
                          child: Opacity(
                            opacity: (t * 2).clamp(0.0, 1.0),
                            child: Container(
                              padding: const EdgeInsets.only(top: 32),
                              decoration: BoxDecoration(
                                gradient: LinearGradient(
                                  begin: Alignment.topCenter,
                                  end: Alignment.bottomCenter,
                                  colors: [
                                    Colors.transparent,
                                    Colors.black.withValues(alpha: 0.65),
                                    Colors.black.withValues(alpha: 0.7),
                                  ],
                                  stops: const [0.0, 0.42, 1.0],
                                ),
                              ),
                              child: Column(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  const Icon(Symbols.keyboard_arrow_down_rounded, color: Colors.white38, size: 20),
                                  const SizedBox(height: 4),
                                  ContentStrip(
                                    player: widget.player,
                                    chapters: widget.chapters,
                                    chaptersLoaded: widget.chaptersLoaded,
                                    serverId: widget.serverId,
                                    showQueueTab: widget.showQueueTab,
                                    onQueueItemSelected: widget.onQueueItemSelected,
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ],
                );
              },
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildTopBar(BuildContext context) {
    final topBar = _conditionalSafeArea(
      context: context,
      bottom: false, // Only respect top safe area when in portrait
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: VideoControlsHeader(
          metadata: widget.metadata,
          style: VideoHeaderStyle.multiLine,
          trailing: widget.trackChapterControls,
          onBack: widget.onBack,
        ),
      ),
    );

    return DesktopAppBarHelper.wrapWithGestureDetector(topBar, opaque: true);
  }

  Widget _buildPlaybackControls(BuildContext _) {
    // Hide all playback controls in host-only mode for non-host
    if (!widget.canControl) {
      return const SizedBox.shrink();
    }

    return FirstFrameGuard(
      hasFirstFrame: widget.hasFirstFrame,
      builder: (context) => _buildPlaybackControlsContent(context),
    );
  }

  Widget _buildPlaybackControlsContent(BuildContext _) {
    return PlayPauseStreamBuilder(
      player: widget.player,
      builder: (context, isPlaying) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (!widget.isLive) ...[
              // Previous episode button (greyed out when unavailable)
              CircularControlButton(
                semanticLabel: t.videoControls.previousButton,
                icon: Symbols.skip_previous_rounded,
                iconSize: 48,
                onPressed: widget.onPrevious,
              ),
              const SizedBox(width: 24),
            ],
            CircularControlButton(
              semanticLabel: isPlaying ? t.videoControls.pauseButton : t.videoControls.playButton,
              icon: isPlaying ? Symbols.pause_rounded : Symbols.play_arrow_rounded,
              iconSize: 72,
              onPressed: () {
                if (isPlaying) {
                  widget.player.pause();
                  widget.onCancelAutoHide?.call(); // Cancel auto-hide when paused
                } else {
                  widget.player.play();
                  widget.onStartAutoHide?.call(); // Start auto-hide when playing
                }
              },
            ),
            if (!widget.isLive) ...[
              const SizedBox(width: 24),
              // Next episode button (greyed out when unavailable)
              CircularControlButton(
                semanticLabel: t.videoControls.nextButton,
                icon: Symbols.skip_next_rounded,
                iconSize: 48,
                onPressed: widget.onNext,
              ),
            ],
          ],
        );
      },
    );
  }

  Widget _buildBottomBar(BuildContext _) {
    if (widget.isLive) {
      if (widget.captureBuffer != null) {
        // Live TV with time-shift: show seekable timeline
        return FirstFrameGuard(
          hasFirstFrame: widget.hasFirstFrame,
          builder: (context) => LiveTimelineBar(
            player: widget.player,
            captureBuffer: widget.captureBuffer!,
            streamStartEpoch: widget.streamStartEpoch,
            isAtLiveEdge: widget.isAtLiveEdge,
            onSeekEnd: widget.onLiveSeek,
            horizontalLayout: false,
            enabled: widget.canControl,
          ),
        );
      }
      // Fallback: static LIVE badge (no capture buffer)
      return Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: const BoxDecoration(color: Colors.red, borderRadius: BorderRadius.all(Radius.circular(4))),
              child: Text(
                t.liveTv.live,
                style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
              ),
            ),
          ],
        ),
      );
    }
    return FirstFrameGuard(hasFirstFrame: widget.hasFirstFrame, builder: (context) => _buildBottomBarContent(context));
  }

  Widget _buildBottomBarContent(BuildContext context) {
    return _conditionalSafeArea(
      context: context,
      top: false, // Only respect bottom safe area when in portrait
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: VideoTimelineBar(
          player: widget.player,
          chapters: widget.chapters,
          chaptersLoaded: widget.chaptersLoaded,
          showChapterMarkersOnTimeline: widget.showChapterMarkersOnTimeline,
          onSeek: widget.onSeek,
          onSeekEnd: widget.onSeekEnd,
          horizontalLayout: false,
          enabled: widget.canControl,
          showFinishTime: true,
          thumbnailDataBuilder: widget.thumbnailDataBuilder,
        ),
      ),
    );
  }

  /// Conditionally wraps child with SafeArea only in portrait mode
  Widget _conditionalSafeArea({
    required BuildContext context,
    required Widget child,
    bool top = true,
    bool bottom = true,
  }) {
    final orientation = MediaQuery.orientationOf(context);
    final isPortrait = orientation == Orientation.portrait;

    // Only apply SafeArea in portrait mode
    if (isPortrait) {
      return SafeArea(top: top, bottom: bottom, child: child);
    }

    // In landscape, return child without SafeArea
    return child;
  }
}
