import 'dart:math' as math;

import 'package:wolf_3d_dart/src/entities/entities/enemies/enemy_animation.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';

/// Defines the primary enemy types and their behavioral metadata.
///
/// This enum maps level object IDs to their corresponding sprite ranges and
/// handles version-specific availability (e.g., Mutants only appearing in Retail).
enum EnemyType {
  /// Standard Brown Guard (The most common enemy).
  guard(
    mapData: EnemyMapData(MapObject.guardStart),
    animations: EnemyAnimationMap(
      idle: SpriteFrameRange(50, 57),
      walking: SpriteFrameRange(58, 89),
      dying: SpriteFrameRange(90, 93),
      pain: SpriteFrameRange(94, 94),
      dead: SpriteFrameRange(95, 95),
      attacking: SpriteFrameRange(96, 98),
    ),
  ),

  /// Attack Dog (Fast melee enemy).
  dog(
    mapData: EnemyMapData(MapObject.dogStart),
    animations: EnemyAnimationMap(
      idle: SpriteFrameRange(99, 106),
      walking: SpriteFrameRange(107, 130),
      attacking: SpriteFrameRange(135, 137),
      pain: SpriteFrameRange(0, 0),
      dying: SpriteFrameRange(131, 133),
      dead: SpriteFrameRange(134, 134),
    ),
  ),

  /// SS Officer (Blue uniform, machine gun).
  ss(
    mapData: EnemyMapData(MapObject.ssStart),
    animations: EnemyAnimationMap(
      idle: SpriteFrameRange(138, 145),
      walking: SpriteFrameRange(146, 177),
      attacking: SpriteFrameRange(184, 186),
      pain: SpriteFrameRange(182, 182),
      dying: SpriteFrameRange(178, 181),
      dead: SpriteFrameRange(183, 183),
    ),
  ),

  /// Undead Mutant (Exclusive to later episodes/retail).
  mutant(
    mapData: EnemyMapData(MapObject.mutantStart),
    animations: EnemyAnimationMap(
      idle: SpriteFrameRange(187, 194),
      walking: SpriteFrameRange(195, 226),
      attacking: SpriteFrameRange(234, 237),
      pain: SpriteFrameRange(231, 231),
      dying: SpriteFrameRange(227, 232, excluded: {231}),
      dead: SpriteFrameRange(233, 233),
    ),
    existsInShareware: false,
  ),

  /// High-ranking Officer (White uniform, fast pistol).
  officer(
    mapData: EnemyMapData(MapObject.officerStart),
    animations: EnemyAnimationMap(
      idle: SpriteFrameRange(238, 245),
      walking: SpriteFrameRange(246, 277),
      attacking: SpriteFrameRange(285, 287),
      pain: SpriteFrameRange(282, 282),
      dying: SpriteFrameRange(278, 283, excluded: {282}),
      dead: SpriteFrameRange(284, 284),
    ),
    existsInShareware: false,
  )
  ;

  /// Logic for identifying the enemy block in map data.
  final EnemyMapData mapData;

  /// The animation ranges for this enemy type.
  final EnemyAnimationMap animations;

  /// If false, this enemy type will be ignored when loading shareware data.
  final bool existsInShareware;

  const EnemyType({
    required this.mapData,
    required this.animations,
    this.existsInShareware = true,
  });

  /// Identifies an [EnemyType] based on a tile ID from the map's object layer.
  static EnemyType? fromMapId(int id) {
    // Each enemy occupies exactly 48 IDs across 3 states.
    if (id >= 108 && id <= 155) return EnemyType.guard;
    if (id >= 156 && id <= 203) return EnemyType.dog;
    if (id >= 204 && id <= 251) return EnemyType.ss;
    if (id >= 252 && id <= 299) return EnemyType.mutant;
    if (id >= 300 && id <= 347) return EnemyType.officer;
    return null;
  }

  /// Safely retrieves the animation map based on the active [GameVersion].
  EnemyAnimationMap? getAnimations(bool isShareware) {
    if (isShareware && !existsInShareware) return null;
    return animations;
  }

  /// Checks if a [spriteIndex] belongs to this enemy type.
  bool claimsSpriteIndex(int index, {bool isShareware = false}) {
    return getAnimations(isShareware)?.getAnimation(index) != null;
  }

  /// Resolves the [EnemyAnimation] state for a specific [spriteIndex].
  EnemyAnimation? getAnimationFromSprite(
    int spriteIndex, {
    bool isShareware = false,
  }) {
    return getAnimations(isShareware)?.getAnimation(spriteIndex);
  }

  /// Calculates the correct sprite index based on animation state, time, and camera angle.
  ///
  /// Wolfenstein 3D uses "Octant" billboarding, meaning idle and walking states
  /// have 8 different rotations based on the angle between the player and the enemy.
  int getSpriteFromAnimation({
    required EnemyAnimation animation,
    required int elapsedMs,
    required int lastActionTime,
    double angleDiff = 0,
    int? walkFrameOverride,
  }) {
    final range = animations.getRange(animation);

    // --- Octant Calculation ---
    // We split the circle into 8 segments (octants).
    // Adding pi/8 offsets the segments so "North" covers the +/- 22.5 degree range.
    int octant = ((angleDiff + (math.pi / 8)) / (math.pi / 4)).floor() % 8;
    if (octant < 0) octant += 8;

    return switch (animation) {
      // Idle sprites are stored sequentially for all 8 octants.
      EnemyAnimation.idle => range.start + octant,

      // Walking frames alternate: [Frame 1 Octants 0-7], [Frame 2 Octants 0-7], etc.
      EnemyAnimation.walking => () {
        int framesPerAngle = range.length ~/ 8;
        if (framesPerAngle < 1) framesPerAngle = 1;

        int frame = walkFrameOverride ?? (elapsedMs ~/ 150) % framesPerAngle;
        return range.start + (frame * 8) + octant;
      }(),

      // Attack and Dying animations are "Global," meaning they look the same
      // regardless of which way the enemy is facing.
      EnemyAnimation.attacking => () {
        int time = elapsedMs - lastActionTime;
        int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
        return range.start + mappedFrame;
      }(),

      EnemyAnimation.pain => range.start,

      EnemyAnimation.dying => () {
        int time = elapsedMs - lastActionTime;
        int mappedFrame = (time ~/ 150).clamp(0, range.length - 1);
        return range.start + mappedFrame;
      }(),

      EnemyAnimation.dead => range.start,
    };
  }

  /// Checks if this enemy shares any sprite frames with [other].
  bool sharesFramesWith(EnemyType other) {
    return animations.overlapsWith(other.animations);

    // * Usage example:
    // void checkEnemyOverlaps() {
    //     for (final enemyA in EnemyType.values) {
    //       for (final enemyB in EnemyType.values) {
    //         if (enemyA != enemyB && enemyA.sharesFramesWith(enemyB)) {
    //           print(
    //             'Warning: ${enemyA.name} and ${enemyB.name} have overlapping sprites!',
    //           );
    //         }
    //       }
    //     }
    //   }
  }
}
