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

import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/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;
  int lives = 3;

  // Damage flash
  double damageFlash = 0.0; // 0.0 is none, 1.0 is maximum red
  final double damageFlashFadeSpeed = 0.05; // How fast it fades per tick

  // 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);

  // --- General Update ---

  void tick(Duration elapsed) {
    // Fade the damage flash over time
    if (damageFlash > 0.0) {
      // Assuming 60fps, we fade it out
      damageFlash = math.max(0.0, damageFlash - damageFlashFadeSpeed);
    }

    updateWeaponSwitch();
  }

  // --- 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;

    log("[PLAYER] Requesting weapon switch to ${weaponType.name}.");
  }

  // --- Health & Damage ---

  void takeDamage(int damage) {
    health = math.max(0, health - damage);

    // Spike the damage flash based on how much damage was taken
    // A 10 damage hit gives a 0.5 flash, a 20 damage hit maxes it out at 1.0
    damageFlash = math.min(1.0, damageFlash + (damage * 0.05));

    if (health <= 0) {
      log("[PLAYER] Died! Final Score: $score");
    } else {
      log("[PLAYER] Took $damage damage ($health)");
    }
  }

  void heal(int amount) {
    final int newHealth = math.min(100, health + amount);
    if (health < 100) {
      log("[PLAYER] Healed for $amount ($health -> $newHealth)");
    }
    health = newHealth;
  }

  void addAmmo(int amount) {
    final int newAmmo = math.min(99, ammo + amount);
    if (ammo < 99) {
      log("[PLAYER] Picked up ammo $amount ($ammo -> $newAmmo)");
    }
    ammo = newAmmo;
  }

  void addLives(int amount) {
    lives = math.min(9, lives + amount);
    log("[PLAYER] Adding $amount extra lives (Total Lives: $lives)");
  }

  /// Attempts to collect [item] and returns the SFX to play.
  ///
  /// Returns `null` when the item was not collected (for example: full health).
  SoundEffect? tryPickup(Collectible item) {
    final effect = item.tryCollect(
      CollectiblePickupContext(
        health: health,
        ammo: ammo,
        hasGoldKey: hasGoldKey,
        hasSilverKey: hasSilverKey,
        hasMachineGun: hasMachineGun,
        hasChainGun: hasChainGun,
        currentWeaponType: currentWeapon.type,
      ),
    );
    if (effect == null) return null;

    log("[PLAYER] Collected ${item.runtimeType} - Effect: $effect");

    if (effect.healthToRestore > 0) {
      heal(effect.healthToRestore);
    }

    if (effect.ammoToAdd > 0) {
      addAmmo(effect.ammoToAdd);
    }

    if (effect.scoreToAdd > 0) {
      log("[PLAYER] Adding ${effect.scoreToAdd} score.");

      score += effect.scoreToAdd;
    }

    if (effect.extraLivesToAdd > 0) {
      addLives(effect.extraLivesToAdd);
    }

    if (effect.grantGoldKey) {
      log("[PLAYER] Collected Gold Key.");
      hasGoldKey = true;
    }

    if (effect.grantSilverKey) {
      log("[PLAYER] Collected Silver Key.");
      hasSilverKey = true;
    }

    if (effect.grantWeapon case final weaponType?) {
      if (weapons[weaponType] == null) {
        weapons[weaponType] = switch (weaponType) {
          WeaponType.machineGun => MachineGun(),
          WeaponType.chainGun => ChainGun(),
          WeaponType.pistol => Pistol(),
          WeaponType.knife => Knife(),
        };
      }

      if (weaponType == WeaponType.machineGun) hasMachineGun = true;
      if (weaponType == WeaponType.chainGun) hasChainGun = true;

      log("[PLAYER] Collected ${weaponType.name}.");
    }

    if (effect.requestWeaponSwitch case final weaponType?) {
      requestWeaponSwitch(weaponType);
    }

    return effect.pickupSoundEffect;
  }

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

    // 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--;
    }

    return shotFired;
  }

  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) {
          score += killedEnemy.scoreValue;
          log(
            "[PLAYER] Killed ${killedEnemy.runtimeType}! +${killedEnemy.scoreValue} (Score: $score)",
          );
        },
      );
    }

    if (currentWeapon.state == WeaponState.idle &&
        ammo <= 0 &&
        currentWeapon.type != WeaponType.knife) {
      log("[PLAYER] Out of ammo, switching to knife.");
      requestWeaponSwitch(WeaponType.knife);
    }
  }
}
