part of '../video_controls.dart';

extension _PlexVideoControlsVisibilityMethods on _PlexVideoControlsState {
  /// Called when hasFirstFrame changes - start auto-hide timer when first frame is ready
  void _onFirstFrameReady() {
    final hasFrame = widget.hasFirstFrame?.value ?? true;
    widget.chromeController.setHasFirstFrame(hasFrame);
    if (hasFrame) {
      // Retry with network-first if initial cache-first returned empty
      if (_chapters.isEmpty && _markers.isEmpty) {
        _loadPlaybackExtras(forceRefresh: true);
      }
      _syncCurrentMarkerForCurrentPosition();
    } else {
      _clearCurrentMarker();
    }
  }

  /// Focus play/pause button if we're in keyboard navigation mode (desktop/TV only)
  void _focusPlayPauseIfKeyboardMode() {
    if (!mounted) return;
    if (!_videoPlayerNavigationEnabled) return;
    final isMobile = PlatformDetector.isMobile(context) && !PlatformDetector.isTV();
    if (!isMobile && InputModeTracker.isKeyboardMode(context)) {
      _desktopControlsKey.currentState?.requestPlayPauseFocus();
    }
  }

  /// Listen to playback state changes to manage auto-hide timer
  void _listenToPlayingState() {
    _playingSubscription = widget.player.streams.playing.listen((isPlaying) {
      widget.chromeController.setPlaying(isPlaying);
    });
  }

  /// Listen to completed stream to show controls when video ends
  void _listenToCompleted() {
    _completedSubscription = widget.player.streams.completed.listen((completed) {
      if (completed && mounted) {
        if (_isLongPressing) {
          _handleLongPressCancel();
        }
        widget.chromeController.show(restartAutoHide: false);
        widget.chromeController.cancelAutoHide();
      }
    });
  }

  /// Controls hide delay: 5s on mobile/TV/keyboard-nav, 3s on desktop with mouse.
  /// Maestro builds extend the delay because accessibility-tree queries can take
  /// longer than the production timeout on physical devices.
  Duration get _hideDelay {
    if (const bool.fromEnvironment('PLEZY_MAESTRO_E2E')) {
      return const Duration(seconds: 30);
    }
    final isMobile = (Platform.isIOS || Platform.isAndroid) && !PlatformDetector.isTV();
    if (isMobile || PlatformDetector.isTV() || _videoPlayerNavigationEnabled) {
      return const Duration(seconds: 5);
    }
    return const Duration(seconds: 3);
  }

  /// Shared hide logic: hides controls, notifies parent, updates traffic lights, restores focus.
  void _hideControls() {
    if (!mounted) return;
    widget.chromeController.hide();
  }

  void _startHideTimer() => widget.chromeController.startAutoHide();

  /// Restart the hide timer on user interaction for the current playback state.
  void _restartHideTimerForCurrentPlaybackState() => widget.chromeController.restartAutoHideForCurrentPlaybackState();

  void _handlePointerSignal(PointerSignalEvent event) {
    if (event is! PointerScrollEvent) return;
    _cancelAutoSkipFromUserInteraction();
    widget.volumeController.adjust(-event.scrollDelta.dy / 20);
    _showControlsFromPointerActivity();
  }

  /// Show controls in response to pointer activity (mouse/trackpad movement).
  void _showControlsFromPointerActivity() {
    widget.chromeController.recordPointerActivity();
  }

  void _toggleControls() {
    widget.chromeController.toggle();
  }

  void _toggleControlsFromSemantics() {
    if (_showControls) {
      widget.chromeController.hide();
      return;
    }
    widget.chromeController.show(restartAutoHide: false);
    widget.chromeController.cancelAutoHide();
  }

  /// Apply preferred orientations for the given lock state. Wired to
  /// [SettingsService.rotationLocked] via [bindEffect] so any change — from
  /// this toggle or from the settings screen — fires the same SystemChrome call.
  void _applyRotationLock(bool locked) {
    unawaited(
      SystemChrome.setPreferredOrientations(
        locked ? const [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight] : DeviceOrientation.values,
      ),
    );
  }

  void _toggleRotationLock() {
    unawaited(_settings.write(SettingsService.rotationLocked, !_isRotationLocked));
  }

  void _toggleScreenLock() {
    final locking = !_isScreenLocked;
    _setControlsState(() {
      _isScreenLocked = locking;
      if (locking) {
        _showLockIcon = true;
      }
    });
    if (locking) {
      _cancelEdgeAdjustmentGesture();
      widget.chromeController.hide(ignoreHolds: true);
      _startLockIconHideTimer();
    }
  }

  void _startLockIconHideTimer() {
    _lockIconTimer?.cancel();
    _lockIconTimer = Timer(const Duration(seconds: 3), () {
      if (mounted) _setControlsState(() => _showLockIcon = false);
    });
  }

  void _unlockScreen() {
    _setControlsState(() {
      _isScreenLocked = false;
      _showLockIcon = false;
    });
    _lockIconTimer?.cancel();
    widget.chromeController.show();
  }

  void _updateTrafficLightVisibility() async {
    final generation = ++_trafficLightVisibilityGeneration;
    // When maximized or fullscreen, always keep traffic lights visible so the
    // user can reach them without the controls-hide-on-mouse-leave race.
    // In normal windowed mode, toggle with controls as before.
    final isMaximizedOrFullscreen = await windowManager.isMaximized() || await MacOSWindowService.isFullscreen();
    if (!mounted || generation != _trafficLightVisibilityGeneration) return;
    final visible = isMaximizedOrFullscreen || _showControls;
    await MacOSWindowService.setTrafficLightsVisible(visible);
  }

  Future<void> _checkPipSupport() async {
    if (!PlatformDetector.supportsPictureInPicture()) {
      return;
    }

    try {
      final supported = await PipService.isSupported();
      if (mounted) {
        _setControlsState(() {
          _isPipSupported = supported;
        });
      }
    } catch (e) {
      return;
    }
  }

  Future<void> _toggleFullscreen() async {
    if (!PlatformDetector.isDesktopOS()) return;
    await FullscreenStateManager().toggleFullscreen();
  }

  /// Initialize always-on-top state from window manager (desktop only)
  Future<void> _initAlwaysOnTopState() async {
    final isOnTop = await windowManager.isAlwaysOnTop();
    if (mounted && isOnTop != _isAlwaysOnTop) {
      _setControlsState(() {
        _isAlwaysOnTop = isOnTop;
      });
    }
  }

  /// Toggle always-on-top window mode (desktop only)
  Future<void> _toggleAlwaysOnTop() async {
    if (!PlatformDetector.isDesktopOS()) return;

    final newValue = !_isAlwaysOnTop;
    await windowManager.setAlwaysOnTop(newValue);
    if (!mounted) return;
    _setControlsState(() {
      _isAlwaysOnTop = newValue;
    });
  }

  /// Show controls and optionally focus play/pause on keyboard input (desktop only)
  void _showControlsWithFocus({bool requestFocus = true}) {
    widget.chromeController.show();

    if (requestFocus) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        _desktopControlsKey.currentState?.requestPlayPauseFocus();
      });
    } else {
      // When not requesting focus on play/pause, ensure main focus node keeps focus
      // This prevents focus from being lost when controls become visible
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted && !_focusNode.hasFocus) {
          _focusNode.requestFocus();
        }
      });
    }
  }

  /// Show controls and focus timeline on LEFT/RIGHT input (TV/desktop)
  void _showControlsWithTimelineFocus() {
    widget.chromeController.show();

    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      _desktopControlsKey.currentState?.requestTimelineFocus();
    });
  }

  /// Hide controls when navigating up from timeline (keyboard mode)
  /// If skip marker button or Play Next dialog is visible, focus it instead of hiding controls
  void _hideControlsFromKeyboard() {
    if (widget.playNextFocusNode != null) {
      widget.playNextFocusNode!.requestFocus();
      return;
    }

    if (_currentMarker != null) {
      _skipMarkerFocusNode.requestFocus();
      return;
    }

    if (_showControls) {
      _hideControls();
    }
  }

  void _onChromeChanged() {
    if (!mounted) return;
    final controlsVisible = widget.chromeController.controlsVisible;
    final visibilityChanged = controlsVisible != _lastControlsVisible;
    final focusTarget = widget.chromeController.takeFocusTarget();
    _lastControlsVisible = controlsVisible;

    if (visibilityChanged && !controlsVisible) {
      _desktopControlsKey.currentState?.hideContentStrip();
      _cancelSkipButtonDismissTimer();
      _setControlsState(() {
        _controlsOpaque = false;
        if (_currentMarker != null) _skipButtonDismissed = true;
      });
      _reclaimFocusAfterControlsHide();
    } else if (visibilityChanged) {
      _setControlsState(() {
        _controlsMounted = true;
        _controlsOpaque = false;
      });
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted || !_showControls || !_controlsMounted) return;
        _setControlsState(() => _controlsOpaque = true);
      });
    } else if (controlsVisible && !_controlsMounted) {
      _setControlsState(() {
        _controlsMounted = true;
        _controlsOpaque = true;
      });
    }

    if (visibilityChanged && Platform.isMacOS) {
      _updateTrafficLightVisibility();
    }

    if (focusTarget != null) {
      _requestFocusTarget(focusTarget);
    }
  }

  void _reclaimFocusAfterControlsHide() {
    final sheetOpen = OverlaySheetController.maybeOf(context)?.isOpen ?? false;
    if (sheetOpen) return;
    _focusNode.requestFocus();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted && !_focusNode.hasPrimaryFocus) {
        _focusNode.requestFocus();
      }
    });
  }

  void _requestFocusTarget(PlayerChromeFocusTarget target) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted || !widget.chromeController.controlsVisible) return;
      // Never steal focus from an open sheet (same rule as
      // _reclaimFocusAfterControlsHide).
      if (OverlaySheetController.maybeOf(context)?.isOpen ?? false) return;
      switch (target) {
        case PlayerChromeFocusTarget.playPause:
          _desktopControlsKey.currentState?.requestPlayPauseFocus();
        case PlayerChromeFocusTarget.timeline:
          _desktopControlsKey.currentState?.requestTimelineFocus();
      }
    });
  }
}
