import 'dart:developer';
import 'dart:math' as math;

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

enum WeaponState { idle, firing }

enum WeaponType { knife, pistol, machineGun, chainGun }

abstract class Weapon {
  final WeaponType type;
  final int idleSprite;
  final List<int> fireFrames;
  final int damage;
  final int msPerFrame;
  final bool isAutomatic;

  WeaponState state = WeaponState.idle;
  int frameIndex = 0;
  int lastFrameTime = 0;
  bool _triggerReleased = true;

  Weapon({
    required this.type,
    required this.idleSprite,
    required this.fireFrames,
    required this.damage,
    this.msPerFrame = 100,
    this.isAutomatic = true,
  });

  int getCurrentSpriteIndex(int maxSprites) {
    int baseSprite = state == WeaponState.idle
        ? idleSprite
        : fireFrames[frameIndex];

    // Retail VSWAP typically has exactly 436 sprites (indices 0 to 435).
    // The 20 weapon sprites are ALWAYS placed at the very end of the sprite block.
    // This dynamically aligns the base index to the end of any VSWAP file!
    int dynamicOffset = 436 - maxSprites;
    int calculatedIndex = baseSprite - dynamicOffset;

    // Safety check!
    if (calculatedIndex < 0 || calculatedIndex >= maxSprites) {
      log("WARNING: Weapon sprite index $calculatedIndex out of bounds!");
      return 0;
    }
    return calculatedIndex;
  }

  void releaseTrigger() {
    _triggerReleased = true;
  }

  bool fire(int currentTime, {required int currentAmmo}) {
    if (state == WeaponState.idle && currentAmmo > 0) {
      if (!isAutomatic && !_triggerReleased) {
        return false;
      }

      state = WeaponState.firing;
      frameIndex = 0;
      lastFrameTime = currentTime;
      _triggerReleased = false;

      log("[WEAPON] $type fired.");

      return true;
    }

    return false;
  }

  void update(int currentTime) {
    if (state == WeaponState.firing) {
      if (currentTime - lastFrameTime > msPerFrame) {
        frameIndex++;
        lastFrameTime = currentTime;
        if (frameIndex >= fireFrames.length) {
          state = WeaponState.idle;
          frameIndex = 0;
        }
      }
    }
  }

  // NEW: The weapon calculates its own hits and applies damage!
  void performHitscan({
    required double playerX,
    required double playerY,
    required double playerAngle,
    required List<Entity> entities,
    required bool Function(int x, int y) isWalkable,
    required int currentTime,
    required void Function(Enemy killedEnemy) onEnemyKilled,
  }) {
    Enemy? closestEnemy;
    double minDistance = 15.0;

    for (Entity entity in entities) {
      if (entity is Enemy && entity.state != EntityState.dead) {
        double dx = entity.x - playerX;
        double dy = entity.y - playerY;
        double angleToEnemy = math.atan2(dy, dx);

        double angleDiff = playerAngle - angleToEnemy;
        while (angleDiff <= -math.pi) {
          angleDiff += 2 * math.pi;
        }
        while (angleDiff > math.pi) {
          angleDiff -= 2 * math.pi;
        }
        double dist = math.sqrt(dx * dx + dy * dy);
        double threshold = 0.2 / dist;

        if (angleDiff.abs() < threshold) {
          Coordinate2D source = Coordinate2D(playerX, playerY);

          if (entity.hasLineOfSightFrom(
            source,
            playerAngle,
            dist,
            isWalkable,
          )) {
            if (dist < minDistance) {
              minDistance = dist;
              closestEnemy = entity;
            }
          }
        }
      }
    }

    if (closestEnemy != null) {
      closestEnemy.takeDamage(damage, currentTime);
      // If the shot was fatal, pass the enemy back so the Player class
      // can calculate the correct score based on enemy type!
      if (closestEnemy.state == EntityState.dead) {
        onEnemyKilled(closestEnemy);
      }
    }
  }
}
