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

namespace PkmnLib.Static.Moves;

/// <summary>
/// The move category defines what global kind of move this move is.
/// </summary>
public enum MoveCategory
{
    /// <summary>
    /// A physical move uses the physical attack stats and physical defense stats to calculate damage.
    /// </summary>
    Physical = 0,

    /// <summary>
    /// A special move uses the special attack stats and special defense stats to calculate damage.
    /// </summary>
    Special = 1,

    /// <summary>
    /// A status move does not do damage, and only runs a secondary effect.
    /// </summary>
    Status = 2,
}

/// <summary>
/// The move target defines what kind of targets the move can touch.
/// </summary>
public enum MoveTarget
{
    /// <summary>
    /// Adjacent allows a move to target any Pokémon that is either directly to the left or right of
    /// the user, opposed to the user, or left or right of the slot that is opposing the user.
    /// </summary>
    Adjacent = 0,

    /// <summary>
    /// AdjacentAlly allows a move to target any Pokémon that is directly to the left or right of
    /// the user.
    /// </summary>
    AdjacentAlly,

    /// <summary>
    /// AdjacentAllySelf allows a move to target any Pokémon that is either directly to the left or
    /// right of the user, or the user itself.
    /// </summary>
    AdjacentAllySelf,

    /// <summary>
    /// AdjacentOpponent allows a move to target any Pokémon that is either the opponent, or directly
    /// to the left or right of it.
    /// </summary>
    AdjacentOpponent,

    /// <summary>
    /// All makes the move target everything on the field.
    /// </summary>
    All,

    /// <summary>
    /// AllAdjacent makes the move target everything adjacent on the field.
    /// </summary>
    AllAdjacent,

    /// <summary>
    /// AllAdjacentOpponent makes the move target everything adjacent to the opponent, and the opponent.
    /// </summary>
    AllAdjacentOpponent,

    /// <summary>
    /// AllAlly targets all Pokémon on the same side as the user.
    /// </summary>
    AllAlly,

    /// <summary>
    /// AllOpponent targets all Pokémon on an opposing side from the user.
    /// </summary>
    AllOpponent,

    /// <summary>
    /// Any allows a move to target a single Pokémon, in any position.
    /// </summary>
    Any,

    /// <summary>
    /// RandomOpponent allows a move to target a single Pokémon, in a random position.
    /// </summary>
    RandomOpponent,

    /// <summary>
    /// SelfUse makes the move target the user itself.
    /// </summary>
    SelfUse,
}

/// <summary>
/// A move is the skill Pokémon primarily use in battle. This is the data related to that.
/// </summary>
public interface IMoveData : INamedValue
{
    /// <summary>
    /// The attacking type of the move.
    /// </summary>
    TypeIdentifier MoveType { get; }

    /// <summary>
    /// The category of the move.
    /// </summary>
    MoveCategory Category { get; }

    /// <summary>
    /// The base power, not considering any modifiers, the move has.
    /// </summary>
    byte BasePower { get; }

    /// <summary>
    /// The accuracy of the move in percentage. Should be 255 for moves that always hit.
    /// </summary>
    byte Accuracy { get; }

    /// <summary>
    /// The number of times the move can be used. This can be modified on actually learned moves using PP-Ups
    /// </summary>
    byte BaseUsages { get; }

    /// <summary>
    /// How the move handles targets.
    /// </summary>
    MoveTarget Target { get; }

    /// <summary>
    /// The priority of the move. A higher priority means the move should go before other moves.
    /// </summary>
    sbyte Priority { get; }

    /// <summary>
    /// The optional secondary effect the move has.
    /// </summary>
    ISecondaryEffect? SecondaryEffect { get; }

    /// <summary>
    /// Arbitrary flags that can be applied to the move.
    /// </summary>
    bool HasFlag(StringKey key);

    /// <summary>
    /// A readonly list of all flags on the move.
    /// </summary>
    IReadOnlyCollection<StringKey> Flags { get; }
}

/// <inheritdoc />
public class MoveDataImpl : IMoveData
{
    /// <inheritdoc cref="MoveDataImpl" />
    public MoveDataImpl(StringKey name, TypeIdentifier moveType, MoveCategory category, byte basePower, byte accuracy,
        byte baseUsages, MoveTarget target, sbyte priority, ISecondaryEffect? secondaryEffect,
        IEnumerable<StringKey> flags)
    {
        Name = name;
        MoveType = moveType;
        Category = category;
        BasePower = basePower;
        Accuracy = accuracy;
        BaseUsages = baseUsages;
        Target = target;
        Priority = priority;
        SecondaryEffect = secondaryEffect;
        _flags = [..flags];
    }

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

    /// <inheritdoc />
    public TypeIdentifier MoveType { get; }

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

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

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

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

    /// <inheritdoc />
    public MoveTarget Target { get; }

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

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

    private readonly ImmutableHashSet<StringKey> _flags;

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

    /// <inheritdoc />
    public IReadOnlyCollection<StringKey> Flags => _flags;
}

public static class MoveTargetHelpers
{
    public static bool TargetsFoe(this MoveTarget target) =>
        target is MoveTarget.AdjacentOpponent or MoveTarget.RandomOpponent or MoveTarget.AllAdjacentOpponent
            or MoveTarget.AllOpponent or MoveTarget.Adjacent or MoveTarget.AllAdjacent or MoveTarget.Any
            or MoveTarget.All;
}