/// Flutter-specific input adapter for the Wolf3D engine.
///
/// This class merges keyboard and pointer events into the frame-based fields
/// exposed by [Wolf3dInput]. It is designed to be owned by higher-level app
/// code and reused across menu and gameplay screens.
library;

import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
import 'package:wolf_3d_dart/wolf_3d_input.dart';

/// Translates Flutter keyboard and mouse state into engine-friendly actions.
class Wolf3dFlutterInput extends Wolf3dInput {
  /// Keyboard shortcut used by the Flutter host to cycle renderer modes.
  LogicalKeyboardKey rendererToggleKey = LogicalKeyboardKey.keyR;

  /// Keyboard shortcut used by the Flutter host to cycle ASCII themes.
  LogicalKeyboardKey asciiThemeCycleKey = LogicalKeyboardKey.keyT;

  /// Keyboard shortcut used by the Flutter host to toggle the FPS counter.
  LogicalKeyboardKey fpsToggleKey = LogicalKeyboardKey.backquote;

  /// Human-friendly label for [rendererToggleKey] shown in on-screen hints.
  String get rendererToggleKeyLabel => _formatShortcutLabel(rendererToggleKey);

  /// Human-friendly label for [asciiThemeCycleKey] shown in on-screen hints.
  String get asciiThemeCycleKeyLabel =>
      _formatShortcutLabel(asciiThemeCycleKey);

  /// Human-friendly label for [fpsToggleKey] shown in on-screen hints.
  String get fpsToggleKeyLabel => _formatShortcutLabel(fpsToggleKey);

  String _formatShortcutLabel(LogicalKeyboardKey key) {
    if (key == LogicalKeyboardKey.space) {
      return 'SPACE';
    }
    if (key == LogicalKeyboardKey.tab) {
      return 'TAB';
    }
    final label = key.keyLabel;
    if (label.isNotEmpty) {
      return label.toUpperCase();
    }
    final debugName = key.debugName;
    if (debugName == null || debugName.isEmpty) {
      return 'KEY';
    }
    return debugName.replaceFirst('Logical Keyboard Key ', '').toUpperCase();
  }

  /// Mapping from logical game actions to one or more keyboard bindings.
  ///
  /// Each action can be rebound by replacing the matching set. The defaults
  /// support both WASD and arrow-key movement for desktop hosts.
  Map<WolfInputAction, Set<LogicalKeyboardKey>> bindings = {
    WolfInputAction.forward: {
      LogicalKeyboardKey.keyW,
      LogicalKeyboardKey.arrowUp,
    },
    WolfInputAction.backward: {
      LogicalKeyboardKey.keyS,
      LogicalKeyboardKey.arrowDown,
    },
    WolfInputAction.turnLeft: {
      LogicalKeyboardKey.keyA,
      LogicalKeyboardKey.arrowLeft,
    },
    WolfInputAction.turnRight: {
      LogicalKeyboardKey.keyD,
      LogicalKeyboardKey.arrowRight,
    },
    WolfInputAction.fire: {
      LogicalKeyboardKey.controlLeft,
      LogicalKeyboardKey.controlRight,
    },
    WolfInputAction.interact: {
      LogicalKeyboardKey.space,
      LogicalKeyboardKey.enter,
      LogicalKeyboardKey.numpadEnter,
    },
    WolfInputAction.back: {LogicalKeyboardKey.escape},
    WolfInputAction.weapon1: {LogicalKeyboardKey.digit1},
    WolfInputAction.weapon2: {LogicalKeyboardKey.digit2},
    WolfInputAction.weapon3: {LogicalKeyboardKey.digit3},
    WolfInputAction.weapon4: {LogicalKeyboardKey.digit4},
  };

  /// Whether the primary mouse button is currently held.
  bool isMouseLeftDown = false;

  /// Whether the secondary mouse button is currently held.
  bool isMouseRightDown = false;
  double _mouseDeltaX = 0.0;
  double _mouseDeltaY = 0.0;
  bool _previousMouseRightDown = false;
  bool _queuedBack = false;

  // One-frame suppression lets host shortcuts (for example Alt+Enter) consume
  // overlapping gameplay keys before the engine reads keyboard state.
  final Set<WolfInputAction> _suppressedActionsOnce = <WolfInputAction>{};

  // Mouse-look is optional so touch or keyboard-only hosts can keep the same
  // adapter without incurring accidental pointer-driven movement.
  bool _isMouseLookEnabled = false;

  /// Whether pointer deltas should be interpreted as movement/turn input.
  bool get mouseLookEnabled => _isMouseLookEnabled;

  /// Enables or disables mouse-look style control.
  set mouseLookEnabled(bool value) {
    _isMouseLookEnabled = value;
    // Clear any built-up delta when turning it off so it doesn't
    // suddenly jump if they turn it back on.
    if (!value) {
      _mouseDeltaX = 0.0;
      _mouseDeltaY = 0.0;
    }
  }

  Set<LogicalKeyboardKey> _previousKeys = {};

  /// Rebinds [action] to a single [key], replacing any previous bindings.
  void bindKey(WolfInputAction action, LogicalKeyboardKey key) {
    bindings[action] = {};
    bindings[action]?.add(key);
  }

  /// Removes [key] from the current binding set for [action].
  void unbindKey(WolfInputAction action, LogicalKeyboardKey key) {
    bindings[action]?.remove(key);
  }

  /// Updates button state for a newly pressed pointer.
  void onPointerDown(PointerDownEvent event) {
    if (event.buttons & kPrimaryMouseButton != 0) isMouseLeftDown = true;
    if (event.buttons & kSecondaryMouseButton != 0) isMouseRightDown = true;
  }

  /// Updates button state when a pointer is released.
  void onPointerUp(PointerUpEvent event) {
    if (event.buttons & kPrimaryMouseButton == 0) isMouseLeftDown = false;
    if (event.buttons & kSecondaryMouseButton == 0) isMouseRightDown = false;
  }

  /// Accumulates pointer delta so it can be consumed on the next engine frame.
  void onPointerMove(PointerEvent event) {
    // Only capture movement if mouselook is actually enabled
    if (_isMouseLookEnabled) {
      _mouseDeltaX += event.delta.dx;
      _mouseDeltaY += event.delta.dy;
    }
  }

  /// Queues a one-frame back action, typically from system back gestures.
  void queueBackAction() {
    _queuedBack = true;
  }

  /// Suppresses [action] for the next [update] tick only.
  ///
  /// This is primarily used by host-level shortcuts that share physical keys
  /// with gameplay/menu actions.
  void suppressActionOnce(WolfInputAction action) {
    _suppressedActionsOnce.add(action);
  }

  /// Convenience helper for host shortcuts that consume Enter/Interact.
  void suppressInteractOnce() {
    suppressActionOnce(WolfInputAction.interact);
  }

  /// Returns whether any bound key for [action] is currently pressed.
  bool _isActive(WolfInputAction action, Set<LogicalKeyboardKey> pressedKeys) {
    // Host-consumed actions should appear inactive for exactly one update.
    if (_suppressedActionsOnce.contains(action)) {
      return false;
    }
    return bindings[action]!.any((key) => pressedKeys.contains(key));
  }

  /// Returns whether [action] was pressed during the current frame only.
  bool _isNewlyPressed(
    WolfInputAction action,
    Set<LogicalKeyboardKey> newlyPressed,
  ) {
    // Suppressed actions also block edge-triggered press detection.
    if (_suppressedActionsOnce.contains(action)) {
      return false;
    }
    return bindings[action]!.any((key) => newlyPressed.contains(key));
  }

  @override
  void update() {
    final pressedKeys = HardwareKeyboard.instance.logicalKeysPressed;
    final newlyPressedKeys = pressedKeys.difference(_previousKeys);

    // Resolve digital keyboard state first so mouse-look only augments input.
    bool kbForward = _isActive(WolfInputAction.forward, pressedKeys);
    bool kbBackward = _isActive(WolfInputAction.backward, pressedKeys);
    bool kbLeft = _isActive(WolfInputAction.turnLeft, pressedKeys);
    bool kbRight = _isActive(WolfInputAction.turnRight, pressedKeys);

    // Mouse-look intentionally maps pointer deltas back onto the engine's
    // simple boolean input contract instead of introducing analog turns.
    isMovingForward = kbForward || (_isMouseLookEnabled && _mouseDeltaY < -1.5);
    isMovingBackward =
        kbBackward || (_isMouseLookEnabled && _mouseDeltaY > 1.5);
    isTurningLeft = kbLeft || (_isMouseLookEnabled && _mouseDeltaX < -1.5);
    isTurningRight = kbRight || (_isMouseLookEnabled && _mouseDeltaX > 1.5);

    // Deltas are one-frame impulses, so consume them immediately after use.
    _mouseDeltaX = 0.0;
    _mouseDeltaY = 0.0;

    // Right click or Spacebar to interact
    isInteracting =
        _isNewlyPressed(WolfInputAction.interact, newlyPressedKeys) ||
        (mouseLookEnabled && isMouseRightDown && !_previousMouseRightDown);

    isBack =
        _isNewlyPressed(WolfInputAction.back, newlyPressedKeys) || _queuedBack;
    menuTapX = null;
    menuTapY = null;

    // Left click or Ctrl to fire
    isFiring =
        _isActive(WolfInputAction.fire, pressedKeys) ||
        (mouseLookEnabled && isMouseLeftDown);

    requestedWeapon = null;
    if (_isNewlyPressed(WolfInputAction.weapon1, newlyPressedKeys)) {
      requestedWeapon = WeaponType.knife;
    }
    if (_isNewlyPressed(WolfInputAction.weapon2, newlyPressedKeys)) {
      requestedWeapon = WeaponType.pistol;
    }
    if (_isNewlyPressed(WolfInputAction.weapon3, newlyPressedKeys)) {
      requestedWeapon = WeaponType.machineGun;
    }
    if (_isNewlyPressed(WolfInputAction.weapon4, newlyPressedKeys)) {
      requestedWeapon = WeaponType.chainGun;
    }

    // Preserve prior frame state so edge-triggered actions like interact and
    // weapon switching only fire once per physical key press.
    _previousKeys = Set.from(pressedKeys);
    _previousMouseRightDown = isMouseRightDown;
    _queuedBack = false;

    // Clear one-shot suppression so the action behaves normally next frame.
    _suppressedActionsOnce.clear();
  }
}
