using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static;
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;
using PkmnLib.Static.Utils.Errors;

namespace PkmnLib.Dynamic.Models;

/// <summary>
/// Data for a single hit on a target.
/// </summary>
public interface IHitData
{
    /// <summary>
    /// Whether the hit is critical.
    /// </summary>
    bool IsCritical { get; }

    /// <summary>
    /// The base power of the hit.
    /// </summary>
    ushort BasePower { get; }

    /// <summary>
    /// The effectiveness of the hit.
    /// </summary>
    float Effectiveness { get; }

    /// <summary>
    /// The damage done by the hit.
    /// </summary>
    uint Damage { get; }

    /// <summary>
    /// The type of the hit. Null if the move is typeless.
    /// </summary>
    TypeIdentifier? Type { get; }

    /// <summary>
    /// Whether the hit is a contact hit. This is used to determine whether abilities that trigger on contact should be activated.
    /// </summary>
    bool IsContact { get; }

    /// <summary>
    /// Whether the hit has failed.
    /// </summary>
    bool HasFailed { get; }

    /// <summary>
    /// Fails the hit.
    /// </summary>
    void Fail();

    /// <summary>
    /// Sets a flag on the hit data. This is used to mark certain conditions or states
    /// </summary>
    void SetFlag(StringKey flag);

    /// <summary>
    /// Checks whether a flag is set on the hit data.
    /// </summary>
    bool HasFlag(StringKey flag);
}

/// <inheritdoc />
public record HitData : IHitData
{
    /// <inheritdoc />
    public bool IsCritical { get; internal set; }

    /// <inheritdoc />
    public ushort BasePower { get; internal set; }

    /// <inheritdoc />
    public float Effectiveness { get; internal set; }

    /// <inheritdoc />
    public uint Damage { get; internal set; }

    /// <inheritdoc />
    public TypeIdentifier? Type { get; internal set; }

    /// <inheritdoc />
    public bool IsContact { get; internal set; }

    /// <inheritdoc />
    public bool HasFailed { get; private set; }

    /// <inheritdoc />
    public void Fail() => HasFailed = true;

    private HashSet<StringKey>? _flags;

    /// <inheritdoc />
    public void SetFlag(StringKey flag)
    {
        _flags ??= [];
        _flags.Add(flag);
    }

    /// <inheritdoc />
    public bool HasFlag(StringKey flag) => _flags != null && _flags.Contains(flag);
}

/// <summary>
/// An executing move is the data of the move for while it is executing.
/// </summary>
public interface IExecutingMove : IScriptSource
{
    /// <summary>
    /// The number of targets this move has.
    /// </summary>
    int TargetCount { get; }

    /// <summary>
    /// The number of hits this move has per target.
    /// </summary>
    byte NumberOfHits { get; }

    /// <summary>
    /// The user of the move.
    /// </summary>
    IPokemon User { get; }

    /// <summary>
    /// The move the user has actually chosen to do.
    /// </summary>
    ILearnedMove ChosenMove { get; }

    /// <summary>
    /// The move that the user is actually going to do. This can be different from the chosen move, for example
    /// when metronome is used, in which case the chosen move will be metronome, and the movedata will be the
    /// move that metronome has chosen.
    /// </summary>
    IMoveData UseMove { get; }

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

    /// <summary>
    /// Gets a hit data for a target, with a specific index.
    /// </summary>
    IHitData GetHitData(IPokemon target, byte hit);

    /// <summary>
    /// Checks whether a Pokémon is a target for this move.
    /// </summary>
    bool IsPokemonTarget(IPokemon target);

    /// <summary>
    /// Gets the index of the hits in this move where the hits for a specific target start.
    /// </summary>
    int GetTargetIndex(IPokemon target);

    /// <summary>
    /// Gets a hit based on its raw index.
    /// </summary>
    IHitData GetDataFromRawIndex(int index);

    /// <summary>
    /// Gets the targets of this move.
    /// </summary>
    IReadOnlyList<IPokemon?> Targets { get; }

    /// <summary>
    /// The underlying move choice.
    /// </summary>
    IMoveChoice MoveChoice { get; }

    /// <summary>
    /// Returns all the hits of this move.
    /// </summary>
    IReadOnlyList<IHitData> Hits { get; }

    /// <summary>
    /// The battle this move is being executed in.
    /// </summary>
    IBattle Battle { get; }
}

/// <inheritdoc cref="IExecutingMove"/>
public class ExecutingMoveImpl : ScriptSource, IExecutingMove
{
    private readonly IReadOnlyList<IPokemon?> _targets;
    private readonly IHitData[] _hits;
    private readonly IBattle _battle;

    /// <inheritdoc cref="ExecutingMoveImpl"/>
    public ExecutingMoveImpl(IReadOnlyList<IPokemon?> targets, byte numberOfHits, ILearnedMove chosenMove,
        IMoveData useMove, IMoveChoice moveChoice, IBattle battle)
    {
        _targets = targets;
        NumberOfHits = numberOfHits;
        ChosenMove = chosenMove;
        UseMove = useMove;
        MoveChoice = moveChoice;
        _battle = battle;

        var totalHits = targets.Count * numberOfHits;
        _hits = new IHitData[totalHits];
        for (var i = 0; i < totalHits; i++)
        {
            _hits[i] = new HitData();
        }
    }

    /// <inheritdoc />
    public int TargetCount => _targets.Count;

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

    /// <inheritdoc />
    public IPokemon User => MoveChoice.User;

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

    /// <inheritdoc />
    public IMoveData UseMove { get; }

    /// <inheritdoc />
    public ScriptContainer Script => MoveChoice.Script;

    /// <summary>
    /// The volatile scripts that are applicable to this move.
    /// </summary>
    public IScriptSet Volatile => MoveChoice.Volatile;

    /// <inheritdoc />
    public IHitData GetHitData(IPokemon target, byte hit)
    {
        var targetIndex = _targets.IndexOf(target);
        if (targetIndex == -1)
        {
            throw new ArgumentException("The target is not a target of this move.");
        }

        var index = targetIndex * NumberOfHits + hit;
        return _hits[index];
    }

    /// <inheritdoc />
    public bool IsPokemonTarget(IPokemon target) => _targets.Contains(target);

    /// <inheritdoc />
    public int GetTargetIndex(IPokemon target)
    {
        var targetIndex = _targets.IndexOf(target);
        if (targetIndex == -1)
            throw new ArgumentException("The target is not a target of this move.");

        return targetIndex * NumberOfHits;
    }

    /// <inheritdoc />
    public IHitData GetDataFromRawIndex(int index)
    {
        if (index < 0 || index >= _hits.Length)
            throw new OutOfRangeException("Hit", index, _hits.Length - 1);
        return _hits[index];
    }

    /// <inheritdoc />
    public IReadOnlyList<IPokemon?> Targets => _targets.ToList();

    /// <inheritdoc />
    public IMoveChoice MoveChoice { get; }

    /// <inheritdoc />
    public IReadOnlyList<IHitData> Hits => _hits;

    /// <inheritdoc />
    public IBattle Battle => _battle;

    /// <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);
    }
}