import 'dart:math' as math;

import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart';

enum WeaponSwitchState { idle, lowering, raising }

class Player {
  // Spatial
  double x;
  double y;
  double angle;

  // Stats
  int health = 100;
  int ammo = 8;
  int score = 0;

  // Inventory
  bool hasGoldKey = false;
  bool hasSilverKey = false;
  bool hasMachineGun = false;
  bool hasChainGun = false;

  // Weapon System
  late Weapon currentWeapon;
  final Map<WeaponType, Weapon?> weapons = {
    WeaponType.knife: Knife(),
    WeaponType.pistol: Pistol(),
    WeaponType.machineGun: null,
    WeaponType.chainGun: null,
  };

  WeaponSwitchState switchState = WeaponSwitchState.idle;
  WeaponType? pendingWeaponType;

  // 0.0 is resting, 500.0 is fully off-screen
  double weaponAnimOffset = 0.0;

  // How fast the weapon drops/raises per tick
  final double switchSpeed = 30.0;

  Player({
    required this.x,
    required this.y,
    required this.angle,
  }) {
    currentWeapon = weapons[WeaponType.pistol]!;
  }

  // Helper getter to interface with the RaycasterPainter
  Coordinate2D get position => Coordinate2D(x, y);

  // --- Weapon Switching & Animation Logic ---

  void updateWeaponSwitch() {
    if (switchState == WeaponSwitchState.lowering) {
      // If the map doesn't contain the pending weapon, stop immediately
      if (weapons[pendingWeaponType] == null) {
        switchState = WeaponSwitchState.idle;
        return;
      }

      weaponAnimOffset += switchSpeed;
      if (weaponAnimOffset >= 500.0) {
        weaponAnimOffset = 500.0;

        // We already know it's not null now, but we can keep the
        // fallback to pistol just to be extra safe.
        currentWeapon = weapons[pendingWeaponType]!;

        switchState = WeaponSwitchState.raising;
      }
    } else if (switchState == WeaponSwitchState.raising) {
      weaponAnimOffset -= switchSpeed;
      if (weaponAnimOffset <= 0) {
        weaponAnimOffset = 0.0;
        switchState = WeaponSwitchState.idle;
      }
    }
  }

  void requestWeaponSwitch(WeaponType weaponType) {
    if (switchState != WeaponSwitchState.idle) return;
    if (currentWeapon.state != WeaponState.idle) return;
    if (weaponType == currentWeapon.type) return;
    if (!weapons.containsKey(weaponType)) return;
    if (weaponType != WeaponType.knife && ammo <= 0) return;

    pendingWeaponType = weaponType;
    switchState = WeaponSwitchState.lowering;
  }

  // --- Health & Damage ---

  void takeDamage(int damage) {
    health = math.max(0, health - damage);
    if (health <= 0) {
      print("YOU DIED!");
    } else {
      print("Ouch! ($health)");
    }
  }

  void heal(int amount) {
    final int newHealth = math.min(100, health + amount);
    if (health < 100) {
      print("Feelin' better. ($newHealth)");
    }
    health = newHealth;
  }

  void addAmmo(int amount) {
    final int newAmmo = math.min(99, ammo + amount);
    if (ammo < 99) {
      print("Hell yeah. ($newAmmo)");
    }
    ammo = newAmmo;
  }

  bool tryPickup(Collectible item) {
    bool pickedUp = false;

    switch (item.type) {
      case CollectibleType.health:
        if (health >= 100) return false;
        heal(item.mapId == MapObject.dogFoodDecoration ? 4 : 25);
        pickedUp = true;
        break;

      case CollectibleType.ammo:
        if (ammo >= 99) return false;
        int previousAmmo = ammo;
        addAmmo(8);
        if (currentWeapon is Knife && previousAmmo <= 0) {
          requestWeaponSwitch(WeaponType.pistol);
        }
        pickedUp = true;
        break;

      case CollectibleType.treasure:
        if (item.mapId == MapObject.cross) score += 100;
        if (item.mapId == MapObject.chalice) score += 500;
        if (item.mapId == MapObject.chest) score += 1000;
        if (item.mapId == MapObject.crown) score += 5000;
        if (item.mapId == MapObject.extraLife) {
          heal(100);
          addAmmo(25);
        }
        pickedUp = true;
        break;

      case CollectibleType.weapon:
        if (item.mapId == MapObject.machineGun) {
          if (weapons[WeaponType.machineGun] == null) {
            weapons[WeaponType.machineGun] = MachineGun();
            hasMachineGun = true;
          }
          addAmmo(8);
          requestWeaponSwitch(WeaponType.machineGun);
          pickedUp = true;
        }
        if (item.mapId == MapObject.chainGun) {
          if (weapons[WeaponType.chainGun] == null) {
            weapons[WeaponType.chainGun] = ChainGun();
            hasChainGun = true;
          }
          addAmmo(8);
          requestWeaponSwitch(WeaponType.chainGun);
          pickedUp = true;
        }
        break;

      case CollectibleType.key:
        if (item.mapId == MapObject.goldKey) hasGoldKey = true;
        if (item.mapId == MapObject.silverKey) hasSilverKey = true;
        pickedUp = true;
        break;
    }
    return pickedUp;
  }

  void fire(int currentTime) {
    if (switchState != WeaponSwitchState.idle) return;

    // We pass the isFiring state to handle automatic vs semi-auto behavior
    bool shotFired = currentWeapon.fire(
      currentTime,
      currentAmmo: ammo,
    );

    if (shotFired && currentWeapon.type != WeaponType.knife) {
      ammo--;
    }
  }

  void releaseTrigger() {
    currentWeapon.releaseTrigger();
  }

  /// Returns true only on the specific frame where the hit should be calculated
  void updateWeapon({
    required int currentTime,
    required List<Entity> entities,
    required bool Function(int x, int y) isWalkable,
  }) {
    int oldFrame = currentWeapon.frameIndex;
    currentWeapon.update(currentTime);

    // If we just crossed into the firing frame...
    if (currentWeapon.state == WeaponState.firing &&
        oldFrame == 0 &&
        currentWeapon.frameIndex == 1) {
      currentWeapon.performHitscan(
        playerX: x,
        playerY: y,
        playerAngle: angle,
        entities: entities,
        isWalkable: isWalkable,
        currentTime: currentTime,
        onEnemyKilled: (Enemy killedEnemy) {
          // Dynamic scoring based on the enemy type!
          int pointsToAdd = 0;

          switch (killedEnemy.runtimeType.toString()) {
            case 'BrownGuard':
              pointsToAdd = 100;
              break;
            case 'Dog':
              pointsToAdd = 200;
              break;
            // You can easily plug in future enemies here!
            // case 'SSOfficer': pointsToAdd = 500; break;
            default:
              pointsToAdd = 100; // Fallback
          }

          score += pointsToAdd;
          // Optional: Print to console so you can see it working
          print(
            "Killed ${killedEnemy.runtimeType}! +$pointsToAdd (Score: $score)",
          );
        },
      );
    }

    if (currentWeapon.state == WeaponState.idle &&
        ammo <= 0 &&
        currentWeapon.type != WeaponType.knife) {
      requestWeaponSwitch(WeaponType.knife);
    }
  }
}
