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

/// Immutable snapshot of player state needed to evaluate pickup rules.
class CollectiblePickupContext {
  final int health;
  final int ammo;
  final bool hasGoldKey;
  final bool hasSilverKey;
  final bool hasMachineGun;
  final bool hasChainGun;
  final WeaponType currentWeaponType;

  const CollectiblePickupContext({
    required this.health,
    required this.ammo,
    required this.hasGoldKey,
    required this.hasSilverKey,
    required this.hasMachineGun,
    required this.hasChainGun,
    required this.currentWeaponType,
  });
}

/// Generic result returned when a collectible is consumed.
class CollectiblePickupEffect {
  final int healthToRestore;
  final int ammoToAdd;
  final int scoreToAdd;
  final int extraLivesToAdd;
  final SoundEffect pickupSoundEffect;
  final bool grantGoldKey;
  final bool grantSilverKey;
  final WeaponType? grantWeapon;
  final WeaponType? requestWeaponSwitch;

  const CollectiblePickupEffect({
    this.healthToRestore = 0,
    this.ammoToAdd = 0,
    this.scoreToAdd = 0,
    this.extraLivesToAdd = 0,
    required this.pickupSoundEffect,
    this.grantGoldKey = false,
    this.grantSilverKey = false,
    this.grantWeapon,
    this.requestWeaponSwitch,
  });

  @override
  String toString() {
    return 'CollectiblePickupEffect(healthToRestore: $healthToRestore, ammoToAdd: $ammoToAdd, scoreToAdd: $scoreToAdd, extraLivesToAdd: $extraLivesToAdd, pickupSoundEffect: $pickupSoundEffect, grantGoldKey: $grantGoldKey, grantSilverKey: $grantSilverKey, grantWeapon: $grantWeapon, requestWeaponSwitch: $requestWeaponSwitch)';
  }
}

abstract class Collectible extends Entity {
  Collectible._({
    required super.x,
    required super.y,
    required super.spriteIndex,
    required super.mapId,
  }) : super(state: EntityState.staticObj);

  /// Additional score granted when this item is collected.
  int get scoreValue => 0;

  /// Returns `null` when pickup should be ignored (full health, full ammo, etc).
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context);

  static int _spriteIndexFor(int objId) => objId - 21;

  // Define which Map IDs are actually items you can pick up
  static bool isCollectible(int objId) {
    return (objId >= 43 && objId <= 44) || // Keys
        (objId >= 47 && objId <= 56); // Health, Ammo, Weapons, Treasure, 1-Up
  }

  static Collectible? trySpawn(
    int objId,
    double x,
    double y,
    Difficulty difficulty, {
    bool isSharewareMode = false,
  }) {
    return switch (objId) {
      MapObject.goldKey || MapObject.silverKey => KeyCollectible(
        x: x,
        y: y,
        mapId: objId,
      ),
      MapObject.food || MapObject.medkit => HealthCollectible(
        x: x,
        y: y,
        mapId: objId,
      ),
      MapObject.ammoClip => AmmoCollectible(x: x, y: y),
      MapObject.machineGun || MapObject.chainGun => WeaponCollectible(
        x: x,
        y: y,
        mapId: objId,
      ),
      MapObject.cross ||
      MapObject.chalice ||
      MapObject.chest ||
      MapObject.crown ||
      MapObject.extraLife => TreasureCollectible(x: x, y: y, mapId: objId),
      _ => null,
    };
  }
}

class HealthCollectible extends Collectible {
  HealthCollectible({
    required super.x,
    required super.y,
    required super.mapId,
  }) : super._(spriteIndex: Collectible._spriteIndexFor(mapId));

  @override
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context) {
    if (context.health >= 100) return null;

    final bool isFood = mapId == MapObject.food;
    return CollectiblePickupEffect(
      healthToRestore: isFood ? 10 : 25,
      pickupSoundEffect: isFood
          ? SoundEffect.healthSmall
          : SoundEffect.healthLarge,
    );
  }
}

class AmmoCollectible extends Collectible {
  final int ammoAmount;

  AmmoCollectible({
    required super.x,
    required super.y,
    this.ammoAmount = 8,
  }) : super._(
         mapId: MapObject.ammoClip,
         spriteIndex: Collectible._spriteIndexFor(MapObject.ammoClip),
       );

  @override
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context) {
    if (context.ammo >= 99) return null;

    final bool shouldAutoswitchToPistol =
        context.currentWeaponType == WeaponType.knife && context.ammo <= 0;

    return CollectiblePickupEffect(
      ammoToAdd: ammoAmount,
      pickupSoundEffect: SoundEffect.getAmmo,
      requestWeaponSwitch: shouldAutoswitchToPistol ? WeaponType.pistol : null,
    );
  }
}

class SmallAmmoCollectible extends AmmoCollectible {
  SmallAmmoCollectible({required super.x, required super.y})
    : super(ammoAmount: 4);
}

class WeaponCollectible extends Collectible {
  WeaponCollectible({
    required super.x,
    required super.y,
    required super.mapId,
  }) : super._(spriteIndex: Collectible._spriteIndexFor(mapId));

  @override
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context) {
    if (mapId == MapObject.machineGun) {
      return const CollectiblePickupEffect(
        ammoToAdd: 6,
        pickupSoundEffect: SoundEffect.getMachineGun,
        grantWeapon: WeaponType.machineGun,
        requestWeaponSwitch: WeaponType.machineGun,
      );
    }

    if (mapId == MapObject.chainGun) {
      return const CollectiblePickupEffect(
        ammoToAdd: 6,
        pickupSoundEffect: SoundEffect.getChainGun,
        grantWeapon: WeaponType.chainGun,
        requestWeaponSwitch: WeaponType.chainGun,
      );
    }

    return null;
  }
}

class KeyCollectible extends Collectible {
  KeyCollectible({
    required super.x,
    required super.y,
    required super.mapId,
  }) : super._(spriteIndex: Collectible._spriteIndexFor(mapId));

  @override
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context) {
    if (mapId == MapObject.goldKey) {
      if (context.hasGoldKey) return null;
      return const CollectiblePickupEffect(
        pickupSoundEffect: SoundEffect.getAmmo,
        grantGoldKey: true,
      );
    }

    if (mapId == MapObject.silverKey) {
      if (context.hasSilverKey) return null;
      return const CollectiblePickupEffect(
        pickupSoundEffect: SoundEffect.getAmmo,
        grantSilverKey: true,
      );
    }

    return null;
  }
}

class TreasureCollectible extends Collectible {
  TreasureCollectible({
    required super.x,
    required super.y,
    required super.mapId,
  }) : super._(spriteIndex: Collectible._spriteIndexFor(mapId));

  @override
  int get scoreValue => switch (mapId) {
    MapObject.cross => 100,
    MapObject.chalice => 500,
    MapObject.chest => 1000,
    MapObject.crown => 5000,
    _ => 0,
  };

  @override
  CollectiblePickupEffect? tryCollect(CollectiblePickupContext context) {
    if (mapId == MapObject.extraLife) {
      return const CollectiblePickupEffect(
        healthToRestore: 99,
        ammoToAdd: 25,
        extraLivesToAdd: 1,
        pickupSoundEffect: SoundEffect.extraLife,
      );
    }

    return CollectiblePickupEffect(
      scoreToAdd: scoreValue,
      pickupSoundEffect: switch (mapId) {
        MapObject.cross => SoundEffect.treasure1,
        MapObject.chalice => SoundEffect.treasure2,
        MapObject.chest => SoundEffect.treasure3,
        MapObject.crown => SoundEffect.treasure4,
        _ => SoundEffect.getAmmo,
      },
    );
  }
}
