part of '../video_controls.dart';

extension _PlexVideoControlsKeyEventMethods on _PlexVideoControlsState {
  Future<void> _initKeyboardService() async {
    _keyboardService = await KeyboardShortcutsService.getInstance();
  }

  void _showScreenshotToast() {
    widget.toastController.show(Symbols.photo_camera_rounded, t.videoControls.screenshotSaved);
  }

  bool _isDirectionalKey(LogicalKeyboardKey key) {
    return key == LogicalKeyboardKey.arrowUp ||
        key == LogicalKeyboardKey.arrowDown ||
        key == LogicalKeyboardKey.arrowLeft ||
        key == LogicalKeyboardKey.arrowRight;
  }

  bool _isSelectKey(LogicalKeyboardKey key) {
    return key == LogicalKeyboardKey.select ||
        key == LogicalKeyboardKey.enter ||
        key == LogicalKeyboardKey.numpadEnter ||
        key == LogicalKeyboardKey.gameButtonA;
  }

  /// Determine if the key event should toggle play/pause based on configured hotkeys.
  bool _isPlayPauseKey(KeyEvent event) {
    final logicalKey = event.logicalKey;
    final physicalKey = event.physicalKey;

    // Always accept hardware media play/pause keys (Android TV remotes)
    if (logicalKey == LogicalKeyboardKey.mediaPlayPause ||
        logicalKey == LogicalKeyboardKey.mediaPlay ||
        logicalKey == LogicalKeyboardKey.mediaPause) {
      return true;
    }

    // When the shortcuts service is available, respect the configured play/pause hotkey
    if (_keyboardService != null) {
      final hotkey = _keyboardService!.hotkeys['play_pause'];
      if (hotkey == null) return false;
      return hotkey.key == physicalKey;
    }

    // Fallback to defaults while the service is loading
    return physicalKey == PhysicalKeyboardKey.space || physicalKey == PhysicalKeyboardKey.mediaPlayPause;
  }

  bool _isMediaSeekKey(LogicalKeyboardKey key) {
    return key == LogicalKeyboardKey.mediaFastForward ||
        key == LogicalKeyboardKey.mediaRewind ||
        key == LogicalKeyboardKey.mediaSkipForward ||
        key == LogicalKeyboardKey.mediaSkipBackward;
  }

  bool _isMediaTrackKey(LogicalKeyboardKey key) {
    return key == LogicalKeyboardKey.mediaTrackNext || key == LogicalKeyboardKey.mediaTrackPrevious;
  }

  bool _isPlayPauseActivation(KeyEvent event) {
    return event is KeyDownEvent && _isPlayPauseKey(event);
  }

  void _activateHiddenControlsPrimaryAction() {
    if (!widget.canControl) {
      _showControlsWithFocus();
      return;
    }
    if (_isSkipMarkerButtonVisible) {
      _activateSkipMarker();
      return;
    }
    _playOrPause();
    _showControlsWithFocus();
  }

  KeyEventResult _handleLocalPlayerNavigationKeyEvent(KeyEvent event, PlayerNavigationKey navigationKey) {
    if (navigationKey == PlayerNavigationKey.none || navigationKey == PlayerNavigationKey.home) {
      return KeyEventResult.ignored;
    }
    if (PlatformDetector.isTV() && event is KeyDownEvent) {
      BackKeyCoordinator.markHandled();
    }

    final sheetController = OverlaySheetController.maybeOf(context);
    if (sheetController?.isOpen ?? false) {
      return handlePlayerNavigationKeyAction(event, navigationKey, sheetController!.pop);
    }

    if (widget.chromeController.contentStripVisible) {
      return handlePlayerNavigationKeyAction(event, navigationKey, () {
        _desktopControlsKey.currentState?.dismissContentStrip();
        widget.chromeController.setContentStripVisible(false);
        _restartHideTimerForCurrentPlaybackState();
      });
    }

    // The enclosing player screen is the sole owner of fullscreen, chrome,
    // prompt, and route-exit stages.
    return KeyEventResult.ignored;
  }

  KeyEventResult _dispatchShortcut(KeyEvent event, {VoidCallback? onSkipMarker}) {
    return _keyboardService!.handleVideoPlayerKeyEvent(
      event,
      widget.player,
      _toggleFullscreen,
      _toggleSubtitles,
      _nextAudioTrack,
      _nextSubtitleTrack,
      _nextChapter,
      _previousChapter,
      canControlPlayback: widget.canControl,
      canNavigateMediaItems: widget.canNavigateMediaItems,
      onPlayPause: () => unawaited(_playOrPause()),
      onToggleShader: _toggleShader,
      onSkipMarker: onSkipMarker,
      onNextEpisode: widget.onNext,
      onPreviousEpisode: widget.onPrevious,
      onScreenshot: _showScreenshotToast,
      onZoomIn: widget.onZoomIn,
      onZoomOut: widget.onZoomOut,
      onZoomReset: widget.onResetVideoZoom,
      onVolumeUp: () => widget.volumeController.adjust(10),
      onVolumeDown: () => widget.volumeController.adjust(-10),
      onToggleMute: widget.volumeController.toggleMute,
      onLiveSeekBy: widget.onLiveSeekBy,
      onSeekRequested: widget.onSeekRequested,
    );
  }

  /// Global key event handler for focus-independent shortcuts (desktop only)
  bool _handleGlobalKeyEvent(KeyEvent event) {
    if (!mounted) return false;
    if (ModalRoute.of(context)?.isCurrent != true) return false;

    // Any actionable key (keyboard / dpad / controller) cancels an in-progress
    // auto-skip countdown. Non-consuming — we fall through so the key still
    // performs its normal action. Single cancel point for keys.
    if (event.isActionable) _cancelAutoSkipFromUserInteraction();

    // When an overlay sheet is open (e.g. subtitle search with text fields),
    // don't consume key events — let text input work normally.
    if (OverlaySheetController.maybeOf(context)?.isOpen ?? false) {
      return false;
    }

    // Native key events also continue through the focus tree after global
    // handlers run. Player navigation must only mutate state there.
    if (classifyPlayerNavigationKey(event, isAppleTV: PlatformDetector.isAppleTV()) != PlayerNavigationKey.none) {
      return false;
    }

    // Only handle when video player navigation is disabled (desktop mode without D-pad nav)
    if (_videoPlayerNavigationEnabled) return false;

    // Skip on mobile (unless TV)
    final isMobile = PlatformDetector.isMobile(context) && !PlatformDetector.isTV();
    if (isMobile) return false;

    // Handle play/pause globally - works regardless of focus
    if (_isPlayPauseActivation(event)) {
      _playOrPause();
      _showControlsWithFocus(requestFocus: false);
      return true; // Event handled, stop propagation
    }

    // Fallback: handle all other shortcuts when focus has drifted away
    // (e.g. after controls auto-hide). The !hasFocus guard prevents
    // double-handling when the Focus onKeyEvent already processes the event.
    if (!_focusNode.hasFocus && _keyboardService != null) {
      final result = _dispatchShortcut(event);
      if (result == KeyEventResult.handled) {
        _focusNode.requestFocus(); // self-heal focus
        return true;
      }
    }

    return false;
  }

  KeyEventResult _handleControlsKeyEvent(KeyEvent event, bool isMobile) {
    final navigationKey = classifyPlayerNavigationKey(event, isAppleTV: PlatformDetector.isAppleTV());
    final navigationResult = _handleLocalPlayerNavigationKeyEvent(event, navigationKey);
    if (navigationResult != KeyEventResult.ignored) {
      return navigationResult;
    }
    if (navigationKey != PlayerNavigationKey.none) return KeyEventResult.ignored;

    // Only handle KeyDown and KeyRepeat events.
    // Consume KeyUp events for navigation keys to prevent leaking to previous routes.
    // Let non-navigation keys (volume, etc.) pass through to the OS.
    if (!event.isActionable) {
      if (!event.logicalKey.isNavigationKey) return KeyEventResult.ignored;
      return KeyEventResult.handled;
    }

    // Reset hide timer on any keyboard/controller input when controls are visible.
    if (_showControls) {
      _restartHideTimerForCurrentPlaybackState();
    }

    final key = event.logicalKey;
    final isPlayPauseKey = _isPlayPauseKey(event);

    // Always consume play/pause keys to prevent propagation to background routes.
    // On TV/mobile, handle play/pause here; on desktop, the global handler does it.
    if (isPlayPauseKey) {
      if (_videoPlayerNavigationEnabled || isMobile) {
        if (_isPlayPauseActivation(event)) {
          _playOrPause();
          _showControlsWithFocus(requestFocus: _videoPlayerNavigationEnabled);
        }
      }
      return KeyEventResult.handled;
    }

    // Handle media seek keys (Android TV remotes).
    // Uses chapter navigation if chapters are available, otherwise seeks by configured time.
    if (event is KeyDownEvent && _isMediaSeekKey(key)) {
      if (widget.canControl) {
        final isForward = key == LogicalKeyboardKey.mediaFastForward || key == LogicalKeyboardKey.mediaSkipForward;
        unawaited(_seekToChapter(forward: isForward));
      }
      _showControlsWithFocus(requestFocus: _videoPlayerNavigationEnabled);
      return KeyEventResult.handled;
    }

    // Handle next/previous track keys (Android TV remotes).
    // Uses same behavior as seek keys: chapter navigation or time-based seek.
    if (event is KeyDownEvent && _isMediaTrackKey(key)) {
      if (widget.canControl) {
        unawaited(_seekToChapter(forward: key == LogicalKeyboardKey.mediaTrackNext));
      }
      _showControlsWithFocus(requestFocus: _videoPlayerNavigationEnabled);
      return KeyEventResult.handled;
    }

    // Handle Select/Enter when controls are hidden.
    // Only intercept if this Focus node itself has primary focus (not a descendant).
    // When the skip marker button is the only visible affordance, Select activates
    // it; otherwise it falls back to play/pause + show controls.
    if (_isSelectKey(key) && !_showControls && _focusNode.hasPrimaryFocus) {
      return handleOneShotSelect(event, _activateHiddenControlsPrimaryAction);
    }

    // On desktop/TV, show controls on directional input.
    // LEFT/RIGHT focuses timeline for seeking, UP/DOWN focuses play/pause.
    if (!isMobile && _isDirectionalKey(key) && (_videoPlayerNavigationEnabled || PlatformDetector.isTV())) {
      if (!_showControls) {
        final isHorizontal = key == LogicalKeyboardKey.arrowLeft || key == LogicalKeyboardKey.arrowRight;
        if (isHorizontal) {
          _showControlsWithTimelineFocus();
          // A repeat may arrive before the post-frame focus handoff reaches
          // the timeline. Consume it here without adding another seek step;
          // once focused, the timeline owns intentional held-key repeats.
          if (shouldStartHiddenDirectionalSeek(event) && widget.canControl) {
            final forward = key == LogicalKeyboardKey.arrowRight;
            unawaited(_seekByTime(forward: forward));
          }
        } else {
          _showControlsWithFocus();
        }
        return KeyEventResult.handled;
      }
      // Children (DesktopVideoControls) handle navigation first via their own onKeyEvent.
      // If we reach here, children already declined the event — consume it to prevent leaking.
      return KeyEventResult.handled;
    }

    // Pass other events to the keyboard shortcuts service.
    if (_keyboardService == null) {
      return event.logicalKey.isNavigationKey ? KeyEventResult.handled : KeyEventResult.ignored;
    }

    final result = _dispatchShortcut(event, onSkipMarker: _performAutoSkip);
    if (!event.logicalKey.isNavigationKey) return result;
    // Never return .ignored for navigation keys — prevent leaking to previous routes.
    return result == KeyEventResult.ignored ? KeyEventResult.handled : result;
  }
}
