using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.Models.Choices;

/// <summary>
/// The choice of a Pokémon to use a move.
/// </summary>
public interface IMoveChoice : ITurnChoice
{
    /// <summary>
    /// The move that is used.
    /// </summary>
    ILearnedMove ChosenMove { get; }

    /// <summary>
    /// The side the move is targeted at.
    /// </summary>
    byte TargetSide { get; }

    /// <summary>
    /// The position the move is targeted at.
    /// </summary>
    byte TargetPosition { get; }

    /// <summary>
    /// The priority of the move.
    /// </summary>
    sbyte Priority { get; set; }

    /// <summary>
    /// The underlying script of the move.
    /// </summary>
    ScriptContainer Script { get; set; }

    /// <summary>
    /// Additional data that can be used by scripts to store information about the choice.
    /// </summary>
    Dictionary<StringKey, object?>? AdditionalData { get; }

    void SetAdditionalData(StringKey key, object? value);

    /// <summary>
    /// Volatile effects that are applied to the move choice.
    /// </summary>
    IScriptSet Volatile { get; }
}

/// <inheritdoc cref="IMoveChoice"/>
[PublicAPI]
public class MoveChoice : TurnChoice, IMoveChoice
{
    /// <inheritdoc cref="MoveChoice"/>
    public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
    {
        ChosenMove = usedMove;
        TargetSide = targetSide;
        TargetPosition = targetPosition;
        Volatile = new ScriptSet(this);

        var secondaryEffect = usedMove.MoveData.SecondaryEffect;
        if (secondaryEffect != null)
        {
            if (user.Library.ScriptResolver.TryResolve(ScriptCategory.Move, secondaryEffect.Name,
                    secondaryEffect.Parameters, out var script))
            {
                Script.Set(script);
                script.OnAddedToParent(this);
            }
        }
    }

    /// <inheritdoc />
    public ILearnedMove ChosenMove { get; }

    /// <inheritdoc />
    public byte TargetSide { get; }

    /// <inheritdoc />
    public byte TargetPosition { get; }

    /// <inheritdoc />
    public sbyte Priority { get; set; }

    /// <inheritdoc />
    public ScriptContainer Script { get; set; } = new();

    /// <inheritdoc />
    public Dictionary<StringKey, object?>? AdditionalData { get; private set; }

    /// <inheritdoc />
    public void SetAdditionalData(StringKey key, object? value)
    {
        AdditionalData ??= new Dictionary<StringKey, object?>();
        AdditionalData[key] = value;
    }

    /// <inheritdoc />
    public IScriptSet Volatile { get; }

    /// <inheritdoc />
    public override int ScriptCount => 2 + User.ScriptCount;

    /// <inheritdoc />
    public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
    {
        scripts.Add(Volatile);
        scripts.Add(Script);
    }

    /// <inheritdoc />
    public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
    {
        GetOwnScripts(scripts);
        User.CollectScripts(scripts);
    }

    /// <inheritdoc />
    public override string ToString() =>
        $"MoveChoice: {ChosenMove.MoveData.Name}, Target: {TargetSide}:{TargetPosition}, User: {User}";

    protected bool Equals(MoveChoice other) =>
        other.User == User && other.ChosenMove.Equals(ChosenMove) && other.TargetSide == TargetSide &&
        other.TargetPosition == TargetPosition;

    /// <inheritdoc />
    public override bool Equals(object? obj)
    {
        if (obj is null)
            return false;
        if (ReferenceEquals(this, obj))
            return true;
        if (obj is not MoveChoice other)
            return false;
        return Equals(other);
    }

    /// <inheritdoc />
    public override int GetHashCode() =>
        HashCode.Combine(User, ChosenMove, TargetSide, TargetPosition);
}