using Theriapolis.Core.Data;

namespace Theriapolis.Core.Items;

/// <summary>
/// A character's items: everything they're carrying plus the per-slot equipped
/// references. Equipped items remain in <see cref="Items"/> — equipping does
/// not move the instance, it just sets <see cref="ItemInstance.EquippedAt"/>
/// and registers it in <see cref="Equipped"/> for fast slot lookup.
///
/// Phase 5 M2 ships the basic plumbing (add/remove/equip/unequip with size
/// checks). Encumbrance speed effects, proficiency-driven attack disadvantage,
/// and the equip UI itself land in M3.
/// </summary>
public sealed class Inventory
{
    public List<ItemInstance> Items { get; } = new();

    /// <summary>Slot → currently-equipped instance. Missing key = empty slot.</summary>
    public Dictionary<EquipSlot, ItemInstance> Equipped { get; } = new();

    public float TotalWeightLb
    {
        get
        {
            float w = 0f;
            foreach (var i in Items) w += i.TotalWeightLb;
            return w;
        }
    }

    public ItemInstance Add(ItemDef def, int qty = 1)
    {
        var inst = new ItemInstance(def, qty);
        Items.Add(inst);
        return inst;
    }

    public bool Remove(ItemInstance inst)
    {
        if (inst.EquippedAt is { } slot)
            Equipped.Remove(slot);
        return Items.Remove(inst);
    }

    /// <summary>
    /// Equip <paramref name="item"/> into <paramref name="slot"/>. Returns false
    /// with an error message if the slot is occupied, the item isn't in this
    /// inventory, or there's a basic structural mismatch (two-handed weapon
    /// when OffHand is taken, etc.).
    ///
    /// Note: this method does NOT enforce proficiency or size disadvantage —
    /// those are computed at attack-resolution time so the player can equip a
    /// wrong-size weapon and accept the penalty. Hard structural blocks only.
    /// </summary>
    public bool TryEquip(ItemInstance item, EquipSlot slot, out string error)
    {
        error = "";
        if (!Items.Contains(item))
        {
            error = "Item is not in this inventory.";
            return false;
        }
        if (Equipped.TryGetValue(slot, out var existing) && existing != item)
        {
            error = $"Slot {slot} is already occupied by {existing.Def.Name}.";
            return false;
        }

        // Two-handed weapon must clear OffHand first.
        if (slot == EquipSlot.MainHand &&
            HasProperty(item.Def, "two_handed") &&
            Equipped.TryGetValue(EquipSlot.OffHand, out var offHand))
        {
            error = $"Cannot wield two-handed weapon: OffHand holds {offHand.Def.Name}.";
            return false;
        }

        // Equipping into OffHand while MainHand has a two-handed weapon is invalid.
        if (slot == EquipSlot.OffHand &&
            Equipped.TryGetValue(EquipSlot.MainHand, out var mainHand) &&
            HasProperty(mainHand.Def, "two_handed"))
        {
            error = $"Cannot use OffHand: MainHand holds two-handed {mainHand.Def.Name}.";
            return false;
        }

        // Natural-weapon enhancer must go into a NaturalWeapon* slot matching its declared anatomy.
        if (item.Def.Kind == "natural_weapon_enhancer")
        {
            var declared = EquipSlotExtensions.FromEnhancerSlot(item.Def.EnhancerSlot);
            if (declared is null)
            {
                error = $"Item '{item.Def.Id}' has invalid enhancer_slot '{item.Def.EnhancerSlot}'.";
                return false;
            }
            if (slot != declared.Value)
            {
                error = $"Enhancer '{item.Def.Name}' fits {declared.Value}, not {slot}.";
                return false;
            }
        }

        // Unequip the item from any prior slot first.
        if (item.EquippedAt is { } prior && prior != slot)
            Equipped.Remove(prior);

        Equipped[slot]    = item;
        item.EquippedAt   = slot;
        return true;
    }

    public bool TryUnequip(EquipSlot slot, out string error)
    {
        error = "";
        if (!Equipped.TryGetValue(slot, out var inst))
        {
            error = $"Slot {slot} is empty.";
            return false;
        }
        Equipped.Remove(slot);
        inst.EquippedAt = null;
        return true;
    }

    public ItemInstance? GetEquipped(EquipSlot slot) =>
        Equipped.TryGetValue(slot, out var i) ? i : null;

    private static bool HasProperty(ItemDef def, string prop)
    {
        foreach (var p in def.Properties)
            if (string.Equals(p, prop, StringComparison.OrdinalIgnoreCase))
                return true;
        return false;
    }
}
