import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';

enum WolfMenuScreen {
  introSplash,
  mainMenu,
  gameSelect,
  episodeSelect,
  difficultySelect,
  changeView,
  rendererOptions,
}

enum WolfIntroSlide { retailWarning, pg13, title }

enum _WolfIntroPhase { fadeIn, hold, fadeOut }

enum WolfMenuMainAction {
  newGame,
  sound,
  control,
  loadGame,
  saveGame,
  changeView,
  readThis,
  viewScores,
  endGame,
  backToGame,
  backToDemo,
  quit,
}

class WolfMenuMainEntry {
  const WolfMenuMainEntry({
    required this.action,
    required this.label,
    this.isEnabled = true,
  });

  final WolfMenuMainAction action;
  final String label;
  final bool isEnabled;
}

class WolfMenuRendererEntry {
  const WolfMenuRendererEntry({
    required this.mode,
    required this.label,
    required this.hasOptions,
    this.isEnabled = true,
    this.isChecked = false,
  });

  final WolfRendererMode mode;
  final String label;
  final bool hasOptions;
  final bool isEnabled;
  final bool isChecked;
}

class WolfMenuRendererOptionEntry {
  const WolfMenuRendererOptionEntry({
    required this.id,
    required this.label,
    this.isEnabled = true,
    this.isChecked = false,
  });

  final WolfRendererOptionId id;
  final String label;
  final bool isEnabled;
  final bool isChecked;
}

bool _isWiredMainMenuAction(WolfMenuMainAction action) {
  switch (action) {
    case WolfMenuMainAction.newGame:
    case WolfMenuMainAction.endGame:
    case WolfMenuMainAction.backToGame:
    case WolfMenuMainAction.backToDemo:
    case WolfMenuMainAction.quit:
      return true;
    case WolfMenuMainAction.changeView:
      return true;
    case WolfMenuMainAction.sound:
    case WolfMenuMainAction.control:
    case WolfMenuMainAction.loadGame:
    case WolfMenuMainAction.saveGame:
    case WolfMenuMainAction.readThis:
    case WolfMenuMainAction.viewScores:
      return false;
  }
}

/// Handles menu-only input state such as selection movement and edge triggers.
class MenuManager {
  static const int transitionDurationMs = 280;
  static const int introFadeDurationMs = 280;
  static const int _introRetailBackgroundRgb = 0xA00000;
  static const int _introPg13BackgroundRgb = 0x33A2E8;
  static const int _introTitleBackgroundRgb = 0x000000;

  WolfMenuScreen _activeMenu = WolfMenuScreen.difficultySelect;
  WolfMenuScreen? _transitionTarget;
  int _transitionElapsedMs = 0;
  bool _transitionSwappedMenu = false;
  WolfMenuScreen _introLandingMenu = WolfMenuScreen.mainMenu;
  int _introSlideIndex = 0;
  int _introElapsedMs = 0;
  _WolfIntroPhase _introPhase = _WolfIntroPhase.fadeIn;
  bool _introAdvanceRequested = false;
  List<WolfIntroSlide> _introSlides = <WolfIntroSlide>[
    WolfIntroSlide.pg13,
    WolfIntroSlide.title,
  ];

  int _selectedMainIndex = 0;
  int _selectedGameIndex = 0;
  int _selectedEpisodeIndex = 0;
  int _selectedDifficultyIndex = 0;
  int _selectedChangeViewIndex = 0;
  int _selectedRendererOptionIndex = 0;
  String _rendererOptionsTitle = 'CUSTOMIZE';
  List<WolfMenuRendererEntry> _changeViewEntries =
      const <WolfMenuRendererEntry>[];
  List<WolfMenuRendererOptionEntry> _rendererOptionEntries =
      const <WolfMenuRendererOptionEntry>[];
  bool _showResumeOption = false;
  int _gameCount = 1;

  bool _prevUp = false;
  bool _prevDown = false;
  bool _prevConfirm = false;
  bool _prevBack = false;

  /// Universal menu background color in 24-bit RGB used by menu screens.
  int menuBackgroundRgb = 0x890000;

  WolfMenuScreen get activeMenu => _activeMenu;

  bool get isTransitioning => _transitionTarget != null;

  bool get isIntroSplashActive => _activeMenu == WolfMenuScreen.introSplash;

  WolfIntroSlide get currentIntroSlide {
    if (_introSlides.isEmpty) {
      return WolfIntroSlide.title;
    }
    final int index = _introSlideIndex.clamp(0, _introSlides.length - 1);
    return _introSlides[index];
  }

  bool get isIntroRetailWarningSlide =>
      currentIntroSlide == WolfIntroSlide.retailWarning;

  bool get isIntroPg13Slide => currentIntroSlide == WolfIntroSlide.pg13;

  bool get isIntroTitleSlide => currentIntroSlide == WolfIntroSlide.title;

  int get introBackgroundRgb {
    switch (currentIntroSlide) {
      case WolfIntroSlide.retailWarning:
        return _introRetailBackgroundRgb;
      case WolfIntroSlide.pg13:
        return _introPg13BackgroundRgb;
      case WolfIntroSlide.title:
        return _introTitleBackgroundRgb;
    }
  }

  double get introOverlayAlpha {
    if (!isIntroSplashActive) {
      return 0.0;
    }
    switch (_introPhase) {
      case _WolfIntroPhase.fadeIn:
        return (1.0 - (_introElapsedMs / introFadeDurationMs)).clamp(0.0, 1.0);
      case _WolfIntroPhase.hold:
        return 0.0;
      case _WolfIntroPhase.fadeOut:
        return (_introElapsedMs / introFadeDurationMs).clamp(0.0, 1.0);
    }
  }

  /// Returns the fade alpha during transitions (0.0..1.0).
  double get transitionAlpha {
    if (!isTransitioning) {
      return 0.0;
    }
    final int half = transitionDurationMs ~/ 2;
    if (_transitionElapsedMs <= half) {
      return (_transitionElapsedMs / half).clamp(0.0, 1.0);
    }
    final int fadeInElapsed = _transitionElapsedMs - half;
    return (1.0 - (fadeInElapsed / half)).clamp(0.0, 1.0);
  }

  int get selectedMainIndex => _selectedMainIndex;

  int get selectedGameIndex => _selectedGameIndex;

  int get selectedEpisodeIndex => _selectedEpisodeIndex;

  int get selectedChangeViewIndex => _selectedChangeViewIndex;

  int get selectedRendererOptionIndex => _selectedRendererOptionIndex;

  String get rendererOptionsTitle => _rendererOptionsTitle;

  List<WolfMenuRendererEntry> get changeViewEntries =>
      List<WolfMenuRendererEntry>.unmodifiable(_changeViewEntries);

  List<WolfMenuRendererOptionEntry> get rendererOptionEntries =>
      List<WolfMenuRendererOptionEntry>.unmodifiable(_rendererOptionEntries);

  List<WolfMenuMainEntry> get mainMenuEntries {
    return List<WolfMenuMainEntry>.unmodifiable(
      <WolfMenuMainEntry>[
        _mainMenuEntry(
          action: WolfMenuMainAction.newGame,
          label: 'NEW GAME',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.sound,
          label: 'SOUND',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.control,
          label: 'CONTROL',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.loadGame,
          label: 'LOAD GAME',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.saveGame,
          label: 'SAVE GAME',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.changeView,
          label: 'CHANGE VIEW',
        ),
        _mainMenuEntry(
          action: WolfMenuMainAction.readThis,
          label: 'READ THIS!',
        ),
        _mainMenuEntry(
          action: _showResumeOption
              ? WolfMenuMainAction.endGame
              : WolfMenuMainAction.viewScores,
          label: _showResumeOption ? 'END GAME' : 'VIEW SCORES',
        ),
        _mainMenuEntry(
          action: _showResumeOption
              ? WolfMenuMainAction.backToGame
              : WolfMenuMainAction.backToDemo,
          label: _showResumeOption ? 'BACK TO GAME' : 'BACK TO DEMO',
        ),
        _mainMenuEntry(action: WolfMenuMainAction.quit, label: 'QUIT'),
      ],
    );
  }

  /// Current selected difficulty row index.
  int get selectedDifficultyIndex => _selectedDifficultyIndex;

  /// Resets menu state for startup, optionally skipping game selection.
  void beginSelectionFlow({
    required int gameCount,
    int initialGameIndex = 0,
    int initialEpisodeIndex = 0,
    Difficulty? initialDifficulty,
    bool hasResumableGame = false,
    bool initialGameIsRetail = false,
  }) {
    _gameCount = gameCount;
    _showResumeOption = hasResumableGame;
    _selectedMainIndex = _defaultMainMenuIndex();
    _selectedGameIndex = _clampIndex(initialGameIndex, gameCount);
    _selectedEpisodeIndex = initialEpisodeIndex < 0 ? 0 : initialEpisodeIndex;
    _selectedDifficultyIndex = initialDifficulty == null
        ? 0
        : Difficulty.values
              .indexOf(initialDifficulty)
              .clamp(0, Difficulty.values.length - 1);
    _introLandingMenu = WolfMenuScreen.mainMenu;
    if (gameCount > 1) {
      _activeMenu = WolfMenuScreen.gameSelect;
      _introElapsedMs = 0;
      _introPhase = _WolfIntroPhase.fadeIn;
      _introSlideIndex = 0;
      _introSlides = <WolfIntroSlide>[
        WolfIntroSlide.pg13,
        WolfIntroSlide.title,
      ];
    } else {
      _startIntroSequence(includeRetailWarning: initialGameIsRetail);
    }
    _transitionTarget = null;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _resetEdgeState();
  }

  /// Starts the intro splash sequence and lands on [landingMenu] when done.
  void beginIntroSplash({
    WolfMenuScreen landingMenu = WolfMenuScreen.mainMenu,
    bool includeRetailWarning = false,
  }) {
    _introLandingMenu = landingMenu;
    _transitionTarget = null;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _startIntroSequence(includeRetailWarning: includeRetailWarning);
    _resetEdgeState();
  }

  /// Resets menu navigation state for a new difficulty selection flow.
  void beginDifficultySelection({Difficulty? initialDifficulty}) {
    beginSelectionFlow(
      gameCount: 1,
      initialGameIndex: 0,
      initialEpisodeIndex: 0,
      initialDifficulty: initialDifficulty,
    );
    _activeMenu = WolfMenuScreen.difficultySelect;
  }

  void showMainMenu({required bool hasResumableGame}) {
    _showResumeOption = hasResumableGame;
    final int itemCount = mainMenuEntries.length;
    if (itemCount == 0) {
      _selectedMainIndex = 0;
    } else {
      _selectedMainIndex = _defaultMainMenuIndex();
    }
    _activeMenu = WolfMenuScreen.mainMenu;
    _transitionTarget = null;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _introElapsedMs = 0;
    _resetEdgeState();
  }

  int get _changeViewItemCount =>
      _changeViewEntries.length + _rendererOptionEntries.length;

  void setChangeViewEntries(List<WolfMenuRendererEntry> entries) {
    final WolfRendererMode? previouslySelectedMode =
        (_selectedChangeViewIndex >= 0 &&
            _selectedChangeViewIndex < _changeViewEntries.length)
        ? _changeViewEntries[_selectedChangeViewIndex].mode
        : null;

    _changeViewEntries = List<WolfMenuRendererEntry>.unmodifiable(entries);

    final int itemCount = _changeViewItemCount;
    if (itemCount == 0) {
      _selectedChangeViewIndex = 0;
      return;
    }

    if (previouslySelectedMode != null) {
      final int modeIndex = _changeViewEntries.indexWhere(
        (entry) => entry.mode == previouslySelectedMode,
      );
      if (modeIndex >= 0 && _isSelectableChangeViewIndex(modeIndex)) {
        _selectedChangeViewIndex = modeIndex;
        return;
      }
    }

    _selectedChangeViewIndex = _findSelectableIndex(
      _clampIndex(_selectedChangeViewIndex, itemCount),
      itemCount,
      _isSelectableChangeViewIndex,
    );
  }

  void setRendererOptionEntries({
    required String title,
    required List<WolfMenuRendererOptionEntry> entries,
  }) {
    final bool wasSelectingOption =
        _selectedChangeViewIndex >= _changeViewEntries.length;
    final WolfRendererOptionId? previousOption =
        (_selectedRendererOptionIndex >= 0 &&
            _selectedRendererOptionIndex < _rendererOptionEntries.length)
        ? _rendererOptionEntries[_selectedRendererOptionIndex].id
        : null;

    _rendererOptionsTitle = title;
    _rendererOptionEntries = List<WolfMenuRendererOptionEntry>.unmodifiable(
      entries,
    );

    final int totalCount = _changeViewItemCount;
    if (_rendererOptionEntries.isEmpty || totalCount == 0) {
      _selectedRendererOptionIndex = 0;
      if (_changeViewEntries.isNotEmpty) {
        _selectedChangeViewIndex = _findSelectableIndex(
          0,
          _changeViewEntries.length,
          _isSelectableChangeViewIndex,
        );
      }
      return;
    }

    if (previousOption != null) {
      final int previousIndex = _rendererOptionEntries.indexWhere(
        (entry) => entry.id == previousOption,
      );
      if (previousIndex >= 0 &&
          _isSelectableRendererOptionIndex(previousIndex)) {
        _selectedRendererOptionIndex = previousIndex;
        if (wasSelectingOption) {
          _selectedChangeViewIndex = _changeViewEntries.length + previousIndex;
        }
        return;
      }
    }

    _selectedRendererOptionIndex = _findSelectableIndex(
      _clampIndex(_selectedRendererOptionIndex, _rendererOptionEntries.length),
      _rendererOptionEntries.length,
      _isSelectableRendererOptionIndex,
    );

    if (wasSelectingOption) {
      _selectedChangeViewIndex =
          _changeViewEntries.length + _selectedRendererOptionIndex;
    } else {
      _selectedChangeViewIndex = _findSelectableIndex(
        _clampIndex(_selectedChangeViewIndex, totalCount),
        totalCount,
        _isSelectableChangeViewIndex,
      );
    }
  }

  void showChangeViewMenu() {
    _activeMenu = WolfMenuScreen.changeView;
    _selectedChangeViewIndex = _changeViewItemCount == 0
        ? 0
        : _findSelectableIndex(
            0,
            _changeViewItemCount,
            _isSelectableChangeViewIndex,
          );
    _transitionTarget = null;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _resetEdgeState();
  }

  void showRendererOptionsMenu() {
    _activeMenu = WolfMenuScreen.rendererOptions;
    _selectedRendererOptionIndex = _rendererOptionEntries.isEmpty
        ? 0
        : _findSelectableIndex(
            0,
            _rendererOptionEntries.length,
            _isSelectableRendererOptionIndex,
          );
    _transitionTarget = null;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _resetEdgeState();
  }

  /// Starts a menu transition. Input is locked until it completes.
  ///
  /// Hosts can reuse this fade timing for future pre-menu splash/image
  /// sequences so transitions feel consistent across the whole app.
  void startTransition(WolfMenuScreen target) {
    if (_activeMenu == target) {
      return;
    }
    _transitionTarget = target;
    _transitionElapsedMs = 0;
    _transitionSwappedMenu = false;
    _resetEdgeState();
  }

  /// Advances transition timers and swaps menu at midpoint.
  void tickTransition(int deltaMs) {
    if (isIntroSplashActive) {
      _tickIntro(deltaMs);
      return;
    }

    if (!isTransitioning) {
      return;
    }
    _transitionElapsedMs += deltaMs;
    final int half = transitionDurationMs ~/ 2;
    if (!_transitionSwappedMenu && _transitionElapsedMs >= half) {
      _activeMenu = _transitionTarget!;
      _transitionSwappedMenu = true;
    }
    if (_transitionElapsedMs >= transitionDurationMs) {
      _transitionTarget = null;
      _transitionElapsedMs = 0;
      _transitionSwappedMenu = false;
    }
  }

  void updateIntroSplash(EngineInput input) {
    if (!isIntroSplashActive) {
      return;
    }

    final bool confirmNow = input.isInteracting;
    if (confirmNow && !_prevConfirm) {
      if (_introPhase == _WolfIntroPhase.fadeOut) {
        // Ignore repeat confirms while already transitioning out.
      } else if (_introPhase == _WolfIntroPhase.hold) {
        _introPhase = _WolfIntroPhase.fadeOut;
        _introElapsedMs = 0;
      } else {
        // Queue advance while fade-in is still in progress.
        _introAdvanceRequested = true;
      }
    }

    _consumeEdgeState(input);
  }

  void _startIntroSequence({required bool includeRetailWarning}) {
    _activeMenu = WolfMenuScreen.introSplash;
    _introSlides = includeRetailWarning
        ? <WolfIntroSlide>[
            WolfIntroSlide.retailWarning,
            WolfIntroSlide.pg13,
            WolfIntroSlide.title,
          ]
        : <WolfIntroSlide>[WolfIntroSlide.pg13, WolfIntroSlide.title];
    _introSlideIndex = 0;
    _introElapsedMs = 0;
    _introPhase = _WolfIntroPhase.fadeIn;
    _introAdvanceRequested = false;
  }

  void _tickIntro(int deltaMs) {
    if (!isIntroSplashActive) {
      return;
    }

    _introElapsedMs += deltaMs;

    switch (_introPhase) {
      case _WolfIntroPhase.fadeIn:
        if (_introElapsedMs >= introFadeDurationMs) {
          _introElapsedMs = 0;
          if (_introAdvanceRequested) {
            _introPhase = _WolfIntroPhase.fadeOut;
            _introAdvanceRequested = false;
          } else {
            _introPhase = _WolfIntroPhase.hold;
          }
        }
        break;
      case _WolfIntroPhase.hold:
        // Hold indefinitely until the user confirms.
        _introElapsedMs = 0;
        if (_introAdvanceRequested) {
          _introPhase = _WolfIntroPhase.fadeOut;
          _introAdvanceRequested = false;
        }
        break;
      case _WolfIntroPhase.fadeOut:
        if (_introElapsedMs >= introFadeDurationMs) {
          _advanceIntroSlide();
        }
        break;
    }
  }

  void _advanceIntroSlide() {
    if (_introSlideIndex < _introSlides.length - 1) {
      _introSlideIndex += 1;
      _introElapsedMs = 0;
      _introPhase = _WolfIntroPhase.fadeIn;
      _introAdvanceRequested = false;
      return;
    }

    _activeMenu = _introLandingMenu;
    _introElapsedMs = 0;
    _introPhase = _WolfIntroPhase.fadeIn;
    _introAdvanceRequested = false;
  }

  void clearEpisodeSelection() {
    _selectedEpisodeIndex = 0;
  }

  /// Consumes current input as the new edge baseline.
  void absorbInputState(EngineInput input) {
    _consumeEdgeState(input);
  }

  void setSelectedEpisodeIndex(int index, int episodeCount) {
    _selectedEpisodeIndex = _clampIndex(index, episodeCount);
  }

  void setSelectedGameIndex(int index, int gameCount) {
    _selectedGameIndex = _clampIndex(index, gameCount);
  }

  ({WolfMenuMainAction? selected, bool goBack}) updateMainMenu(
    EngineInput input,
  ) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (selected: null, goBack: false);
    }

    final _MenuAction action = _updateLinearSelection(
      input,
      currentIndex: _selectedMainIndex,
      itemCount: mainMenuEntries.length,
      isSelectableIndex: _isSelectableMainIndex,
    );
    _selectedMainIndex = action.index;
    return (
      selected: action.confirmed
          ? mainMenuEntries[_selectedMainIndex].action
          : null,
      goBack: action.goBack,
    );
  }

  ({
    WolfRendererMode? selectedMode,
    WolfRendererOptionId? selectedOption,
    bool goBack,
  })
  updateChangeViewMenu(EngineInput input) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (
        selectedMode: null,
        selectedOption: null,
        goBack: false,
      );
    }

    final _MenuAction action = _updateLinearSelection(
      input,
      currentIndex: _selectedChangeViewIndex,
      itemCount: _changeViewItemCount,
      isSelectableIndex: _isSelectableChangeViewIndex,
    );
    _selectedChangeViewIndex = action.index;

    if (!action.confirmed) {
      return (
        selectedMode: null,
        selectedOption: null,
        goBack: action.goBack,
      );
    }

    if (_selectedChangeViewIndex < _changeViewEntries.length) {
      final WolfMenuRendererEntry entry =
          _changeViewEntries[_selectedChangeViewIndex];
      return (
        selectedMode: entry.mode,
        selectedOption: null,
        goBack: action.goBack,
      );
    }

    final int optionIndex =
        _selectedChangeViewIndex - _changeViewEntries.length;
    if (optionIndex < 0 || optionIndex >= _rendererOptionEntries.length) {
      return (
        selectedMode: null,
        selectedOption: null,
        goBack: action.goBack,
      );
    }
    _selectedRendererOptionIndex = optionIndex;

    return (
      selectedMode: null,
      selectedOption: _rendererOptionEntries[optionIndex].id,
      goBack: action.goBack,
    );
  }

  ({WolfRendererOptionId? selectedOption, bool goBack})
  updateRendererOptionsMenu(EngineInput input) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (selectedOption: null, goBack: false);
    }

    final _MenuAction action = _updateLinearSelection(
      input,
      currentIndex: _selectedRendererOptionIndex,
      itemCount: _rendererOptionEntries.length,
      isSelectableIndex: _isSelectableRendererOptionIndex,
    );
    _selectedRendererOptionIndex = action.index;

    return (
      selectedOption: action.confirmed && _rendererOptionEntries.isNotEmpty
          ? _rendererOptionEntries[_selectedRendererOptionIndex].id
          : null,
      goBack: action.goBack,
    );
  }

  /// Returns a menu action snapshot for this frame.
  ({Difficulty? selected, bool goBack}) updateDifficultySelection(
    EngineInput input,
  ) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (selected: null, goBack: false);
    }

    final upNow = input.isMovingForward;
    final downNow = input.isMovingBackward;
    final confirmNow = input.isInteracting || input.isFiring;
    final backNow = input.isBack;

    if (upNow && !_prevUp) {
      _selectedDifficultyIndex =
          (_selectedDifficultyIndex - 1 + Difficulty.values.length) %
          Difficulty.values.length;
    }

    if (downNow && !_prevDown) {
      _selectedDifficultyIndex =
          (_selectedDifficultyIndex + 1) % Difficulty.values.length;
    }

    Difficulty? selected;
    if (confirmNow && !_prevConfirm) {
      selected = Difficulty.values[_selectedDifficultyIndex];
    }

    final bool goBack = backNow && !_prevBack;

    _prevUp = upNow;
    _prevDown = downNow;
    _prevConfirm = confirmNow;
    _prevBack = backNow;

    return (selected: selected, goBack: goBack);
  }

  ({int? selectedIndex, bool goBack}) updateGameSelection(
    EngineInput input, {
    required int gameCount,
  }) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (selectedIndex: null, goBack: false);
    }
    final _MenuAction action = _updateLinearSelection(
      input,
      currentIndex: _selectedGameIndex,
      itemCount: gameCount,
    );
    _selectedGameIndex = action.index;
    return (
      selectedIndex: action.confirmed ? _selectedGameIndex : null,
      goBack: action.goBack,
    );
  }

  ({int? selectedIndex, bool goBack}) updateEpisodeSelection(
    EngineInput input, {
    required int episodeCount,
  }) {
    if (isTransitioning) {
      _consumeEdgeState(input);
      return (selectedIndex: null, goBack: false);
    }
    final _MenuAction action = _updateLinearSelection(
      input,
      currentIndex: _selectedEpisodeIndex,
      itemCount: episodeCount,
    );
    _selectedEpisodeIndex = action.index;
    return (
      selectedIndex: action.confirmed ? _selectedEpisodeIndex : null,
      goBack: action.goBack,
    );
  }

  _MenuAction _updateLinearSelection(
    EngineInput input, {
    required int currentIndex,
    required int itemCount,
    bool Function(int index)? isSelectableIndex,
  }) {
    final upNow = input.isMovingForward;
    final downNow = input.isMovingBackward;
    final confirmNow = input.isInteracting || input.isFiring;
    final backNow = input.isBack;

    int nextIndex = _clampIndex(currentIndex, itemCount);
    final bool Function(int index) selectable =
        isSelectableIndex ?? ((_) => true);

    if (itemCount > 0 && !selectable(nextIndex)) {
      nextIndex = _findSelectableIndex(nextIndex, itemCount, selectable);
    }

    if (itemCount > 0) {
      if (upNow && !_prevUp) {
        nextIndex = _moveSelectableIndex(nextIndex, itemCount, -1, selectable);
      }

      if (downNow && !_prevDown) {
        nextIndex = _moveSelectableIndex(nextIndex, itemCount, 1, selectable);
      }
    }

    final bool confirmed = confirmNow && !_prevConfirm && selectable(nextIndex);
    final bool goBack = backNow && !_prevBack;

    _prevUp = upNow;
    _prevDown = downNow;
    _prevConfirm = confirmNow;
    _prevBack = backNow;

    return _MenuAction(index: nextIndex, confirmed: confirmed, goBack: goBack);
  }

  void _consumeEdgeState(EngineInput input) {
    _prevUp = input.isMovingForward;
    _prevDown = input.isMovingBackward;
    _prevConfirm = input.isInteracting || input.isFiring;
    _prevBack = input.isBack;
  }

  void _resetEdgeState() {
    _prevUp = false;
    _prevDown = false;
    _prevConfirm = false;
    _prevBack = false;
  }

  int _defaultMainMenuIndex() {
    final WolfMenuMainAction target = _showResumeOption
        ? WolfMenuMainAction.backToGame
        : WolfMenuMainAction.newGame;
    final int found = mainMenuEntries.indexWhere(
      (entry) => entry.action == target,
    );
    return found >= 0
        ? found
        : _findSelectableIndex(
            0,
            mainMenuEntries.length,
            _isSelectableMainIndex,
          );
  }

  bool get canGoBackToGameSelection => !_showResumeOption && _gameCount > 1;

  bool _isSelectableMainIndex(int index) {
    if (index < 0 || index >= mainMenuEntries.length) {
      return false;
    }
    return mainMenuEntries[index].isEnabled;
  }

  bool _isSelectableChangeViewIndex(int index) {
    if (index < 0) {
      return false;
    }
    if (index < _changeViewEntries.length) {
      return _changeViewEntries[index].isEnabled;
    }
    final int optionIndex = index - _changeViewEntries.length;
    if (optionIndex >= 0 && optionIndex < _rendererOptionEntries.length) {
      return _rendererOptionEntries[optionIndex].isEnabled;
    }
    return false;
  }

  bool _isSelectableRendererOptionIndex(int index) {
    if (index < 0 || index >= _rendererOptionEntries.length) {
      return false;
    }
    return _rendererOptionEntries[index].isEnabled;
  }

  WolfMenuMainEntry _mainMenuEntry({
    required WolfMenuMainAction action,
    required String label,
  }) {
    return WolfMenuMainEntry(
      action: action,
      label: label,
      isEnabled: _isWiredMainMenuAction(action),
    );
  }

  int _findSelectableIndex(
    int startIndex,
    int itemCount,
    bool Function(int index) selectable,
  ) {
    if (itemCount <= 0) {
      return 0;
    }
    for (int offset = 0; offset < itemCount; offset++) {
      final int index = (startIndex + offset) % itemCount;
      if (selectable(index)) {
        return index;
      }
    }
    return _clampIndex(startIndex, itemCount);
  }

  int _moveSelectableIndex(
    int currentIndex,
    int itemCount,
    int delta,
    bool Function(int index) selectable,
  ) {
    if (itemCount <= 0) {
      return 0;
    }
    int index = currentIndex;
    for (int step = 0; step < itemCount; step++) {
      index = (index + delta + itemCount) % itemCount;
      if (selectable(index)) {
        return index;
      }
    }
    return currentIndex;
  }

  int _clampIndex(int index, int itemCount) {
    if (itemCount <= 0) {
      return 0;
    }
    return index.clamp(0, itemCount - 1);
  }

  /// Whether to show the alternate cursor frame at [elapsedMs].
  bool isCursorAltFrame(int elapsedMs) => ((elapsedMs ~/ 220) % 2) == 1;
}

class _MenuAction {
  const _MenuAction({
    required this.index,
    required this.confirmed,
    required this.goBack,
  });

  final int index;
  final bool confirmed;
  final bool goBack;
}
