import 'item.dart';
import 'status_effect.dart';
import 'stat_modifier.dart';
import '../enums.dart';
import '../data/item_table.dart';
import '../config/game_config.dart';

class Character {
  String name;
  int hp;
  int baseMaxHp;
  int armor; // Current temporary shield/armor points in battle
  int baseAtk;
  int baseDefense; // Base defense stat

  int gold; // New: Currency
  String? image; // New: Image path
  Map<EquipmentSlot, Item> equipment = {};
  List<Item> inventory = [];
  final int maxInventorySize = GameConfig.maxInventorySize;

  // Active status effects
  List<StatusEffect> statusEffects = [];

  // Permanent stat modifiers (e.g. Ascension, Potions)
  List<PermanentStatModifier> permanentModifiers = [];

  Character({
    required this.name,
    int? hp,
    required int maxHp,
    required this.armor,
    required int atk,
    this.baseDefense = 0,
    this.gold = 0,
    this.image,
  }) : baseMaxHp = maxHp,
       baseAtk = atk,
       hp = hp ?? maxHp;

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'hp': hp,
      'baseMaxHp': baseMaxHp,
      'armor': armor,
      'baseAtk': baseAtk,
      'baseDefense': baseDefense,
      'gold': gold,
      'image': image,
      'equipment': equipment.map(
        (key, value) => MapEntry(key.name, value.id),
      ),
      'inventory': inventory.map((e) => e.id).toList(),
      'statusEffects': statusEffects.map((e) => e.toJson()).toList(),
      'permanentModifiers': permanentModifiers.map((e) => e.toJson()).toList(),
    };
  }

  factory Character.fromJson(Map<String, dynamic> json) {
    final char = Character(
      name: json['name'],
      hp: json['hp'],
      maxHp: json['baseMaxHp'],
      armor: json['armor'],
      atk: json['baseAtk'],
      baseDefense: json['baseDefense'],
      gold: json['gold'],
      image: json['image'],
    );

    // Restore Equipment
    final equipMap = json['equipment'] as Map<String, dynamic>;
    equipMap.forEach((slotName, itemId) {
      final template = ItemTable.get(itemId);
      if (template != null) {
        // Find slot enum
        final slot = EquipmentSlot.values.firstWhere(
          (e) => e.name == slotName,
          orElse: () => EquipmentSlot.weapon, // Fallback
        );
        char.equipment[slot] = template.createItem();
      }
    });

    // Restore Inventory
    final invList = json['inventory'] as List<dynamic>;
    for (var itemId in invList) {
      final template = ItemTable.get(itemId);
      if (template != null) {
        char.inventory.add(template.createItem());
      }
    }

    // Restore Status Effects
    if (json['statusEffects'] != null) {
      char.statusEffects = (json['statusEffects'] as List)
          .map((e) => StatusEffect.fromJson(e))
          .toList();
    }

    // Restore Permanent Modifiers
    if (json['permanentModifiers'] != null) {
      char.permanentModifiers = (json['permanentModifiers'] as List)
          .map((e) => PermanentStatModifier.fromJson(e))
          .toList();
    }

    return char;
  }

  /// Adds a status effect. If it already exists, it refreshes duration or stacks based on logic.
  /// For now, we'll implement a simple refresh/overwrite logic.
  void addStatusEffect(StatusEffect newEffect) {
    // Check if effect exists
    var existing = statusEffects
        .where((e) => e.type == newEffect.type)
        .firstOrNull;

    if (existing != null) {
      // Refresh duration if the new one is longer, or just reset it?
      // Let's max the duration for now.
      if (newEffect.duration > existing.duration) {
        existing.duration = newEffect.duration;
      }
      // Logic for 'value' (stacking bleed?) can be added here.
    } else {
      statusEffects.add(newEffect);
    }
  }

  /// Decrements duration of all effects and removes expired ones.
  /// Returns a list of expired effects if needed for UI logs.
  void updateStatusEffects() {
    // Remove effects with 0 or less duration first (safety cleanup)
    statusEffects.removeWhere((e) => e.duration <= 0);

    for (var effect in statusEffects) {
      effect.duration--;
    }

    // Remove effects that just expired (duration went to 0 or -1)
    statusEffects.removeWhere((e) => e.duration <= 0);
  }

  void addPermanentModifier(PermanentStatModifier modifier) {
    permanentModifiers.add(modifier);
  }

  void removePermanentModifier(String id) {
    permanentModifiers.removeWhere((m) => m.id == id);
  }

  /// Helper to check if character has a specific status
  bool hasStatus(StatusEffectType type) {
    return statusEffects.any((e) => e.type == type);
  }

  int get totalMaxHp {
    int bonus = equipment.values.fold(0, (sum, item) => sum + item.hpBonus);
    return baseMaxHp + bonus;
  }

  int get totalAtk {
    int bonus = equipment.values.fold(0, (sum, item) => sum + item.atkBonus);
    return baseAtk + bonus;
  }

  int get totalDefense {
    int bonus = equipment.values.fold(0, (sum, item) => sum + item.armorBonus);
    return baseDefense + bonus;
  }

  int get totalLuck {
    return equipment.values.fold(0, (sum, item) => sum + item.luck);
  }

  bool get isDead => hp <= 0;

  // Adds an item to inventory, returns true if successful, false if inventory is full
  bool addToInventory(Item item) {
    if (inventory.length < maxInventorySize) {
      inventory.add(item);
      return true;
    }
    return false;
  }

  // Equips an item (swapping if necessary)
  // Returns true if successful
  bool equip(Item newItem) {
    if (!inventory.contains(newItem)) return false;

    // 1. Calculate current HP ratio before any changes
    double hpRatio = totalMaxHp > 0
        ? hp / totalMaxHp
        : 0.0; // Avoid division by zero

    // 2. Handle Swap: If slot is occupied, unequip the old item first
    if (equipment.containsKey(newItem.slot)) {
      Item oldItem = equipment[newItem.slot]!;
      equipment.remove(newItem.slot);
      inventory.add(oldItem);
    }

    // 3. Move new item: Inventory -> Equipment
    inventory.remove(newItem);
    equipment[newItem.slot] = newItem;

    // 4. Update current HP based on the new totalMaxHp and previous ratio
    hp = (totalMaxHp * hpRatio).toInt();
    if (hp < 0) hp = 0; // Ensure HP does not go below zero
    if (hp > totalMaxHp) {
      hp =
          totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
    }

    return true;
  }

  // Unequips an item
  // Returns true if successful (inventory has space)
  bool unequip(Item item) {
    if (!equipment.containsValue(item)) return false;

    // 1. Calculate current HP ratio before any changes
    double hpRatio = totalMaxHp > 0
        ? hp / totalMaxHp
        : 0.0; // Avoid division by zero

    if (inventory.length < maxInventorySize) {
      equipment.remove(item.slot);
      inventory.add(item);

      // 2. Update current HP based on the new totalMaxHp and previous ratio
      hp = (totalMaxHp * hpRatio).toInt();
      if (hp < 0) hp = 0; // Ensure HP does not go below zero
      if (hp > totalMaxHp) {
        hp =
            totalMaxHp; // Final Safety Clamp, though hpRatio <= 1.0 should prevent this
      }
      return true;
    }

    return false;
  }

  void heal(int amount) {
    if (isDead) return; // Cannot heal if dead

    hp += amount;
    if (hp > totalMaxHp) {
      hp = totalMaxHp;
    }
  }
}
