using System.Collections.Immutable;
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;

namespace PkmnLib.Static;

/// <summary>
/// An item category defines which bag slot items are stored in.
/// </summary>
public enum ItemCategory
{
    /// <summary>
    /// This is where most items should go.
    /// </summary>
    MiscItem,

    /// <summary>
    /// Pokeballs are used for capturing Pokémons.
    /// </summary>
    Pokeball,

    /// <summary>
    /// Medicine is used for healing HP, PP, and status effects.
    /// </summary>
    Medicine,

    /// <summary>
    /// Berry is used for all berries.
    /// </summary>
    Berry,

    /// <summary>
    /// TMHM is used for Technical and Hidden Machines.
    /// </summary>
    TmHm,

    /// <summary>
    /// Form Changer is used for items that change forms, such as mega stones.
    /// </summary>
    FormChanger,

    /// <summary>
    /// Key Items are single stored items, generally used for story progression.
    /// </summary>
    KeyItem,

    /// <summary>
    /// Mail is used for mail items.
    /// </summary>
    Mail,
}

/// <summary>
/// A battle item category defines how the item is categorized when in battle.
/// </summary>
public enum BattleItemCategory
{
    /// <summary>
    /// This item can't be used in battle.
    /// </summary>
    None,

    /// <summary>
    /// This item is used for healing Pokémon.
    /// </summary>
    Healing,

    /// <summary>
    /// This item is used for healing Pokémon from a status.
    /// </summary>
    StatusHealing,

    /// <summary>
    /// This item is used for capturing Pokémon.
    /// </summary>
    Pokeball,

    /// <summary>
    /// This item does not belong in above categories, but is still a battle item.
    /// </summary>
    MiscBattleItem,
}

/// <summary>
/// An item is an object which the player can pick up, keep in their Bag, and use in some manner.
/// </summary>
public interface IItem : INamedValue
{
    /// <summary>
    /// Which bag slot items are stored in.
    /// </summary>
    ItemCategory Category { get; }

    /// <summary>
    /// How the item is categorized when in battle.
    /// </summary>
    BattleItemCategory BattleCategory { get; }

    /// <summary>
    /// The buying value of the item.
    /// </summary>
    int Price { get; }

    /// <summary>
    /// A set of arbitrary flags that can be set on the item.
    /// </summary>
    ImmutableHashSet<StringKey> Flags { get; }

    /// <summary>
    /// The effect of the item when used outside of battle.
    /// </summary>
    ISecondaryEffect? Effect { get; }

    /// <summary>
    /// The effect of the item when used in battle.
    /// </summary>
    ISecondaryEffect? BattleEffect { get; }

    /// <summary>
    /// A set of arbitrary data that can be set on the item.
    /// </summary>
    IReadOnlyDictionary<StringKey, object?> AdditionalData { get; }

    /// <summary>
    /// Tries to get additional data from the item. If the data is not present, the value will be null.
    /// If the data is present, but cannot be converted to the requested type, the value will be null.
    /// </summary>
    bool TryGetAdditionalData<T>(StringKey key, out T? value);

    /// <summary>
    /// Checks whether the item has a specific flag.
    /// </summary>
    /// <param name="key">The flag to check for.</param>
    /// <returns>True if the item has the flag, false otherwise.</returns>
    bool HasFlag(StringKey key);
}

/// <inheritdoc />
public class ItemImpl : IItem
{
    /// <inheritdoc cref="ItemImpl"/>
    public ItemImpl(StringKey name, ItemCategory category, BattleItemCategory battleCategory, int price,
        IEnumerable<StringKey> flags, ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect,
        Dictionary<StringKey, object?> additionalData)
    {
        Name = name;
        Category = category;
        BattleCategory = battleCategory;
        Price = price;
        Effect = effect;
        BattleEffect = battleTriggerEffect;
        AdditionalData = additionalData;
        Flags = [..flags];
    }

    /// <inheritdoc />
    public StringKey Name { get; }

    /// <inheritdoc />
    public ItemCategory Category { get; }

    /// <inheritdoc />
    public BattleItemCategory BattleCategory { get; }

    /// <inheritdoc />
    public int Price { get; }

    /// <inheritdoc />
    public ImmutableHashSet<StringKey> Flags { get; }

    /// <inheritdoc />
    public ISecondaryEffect? Effect { get; }

    /// <inheritdoc />
    public ISecondaryEffect? BattleEffect { get; }

    /// <inheritdoc />
    public IReadOnlyDictionary<StringKey, object?> AdditionalData { get; }

    /// <inheritdoc />
    public bool TryGetAdditionalData<T>(StringKey key, out T? value)
    {
        if (AdditionalData.TryGetValue(key, out var obj))
        {
            switch (obj)
            {
                case T t:
                    value = t;
                    return true;
                case IConvertible convertible:
                    try
                    {
                        value = (T)convertible.ToType(typeof(T), null);
                        return true;
                    }
                    catch (InvalidCastException)
                    {
                        // Ignore
                    }
                    break;
            }
        }

        value = default;
        return false;
    }

    /// <inheritdoc />
    public bool HasFlag(StringKey key) => Flags.Contains(key);
}