//
//  EnemyPolicy.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Data.SqlTypes;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Selectors;
using NosSmooth.Game.Data.Characters;
using NosSmooth.Game.Data.Entities;
using NosSmooth.Game.Data.Info;
using Remora.Results;

namespace NosSmooth.Extensions.Combat.Policies;

/// <summary>
/// An enemy selector policy that selects the monsters based on their vnums.
/// </summary>
/// <param name="SelectPolicy">The policy to select an enemy.</param>
/// <param name="MonsterVNums">The vnums of the monsters to target.</param>
/// <param name="CombatArea">The area in which to get enemies from.</param>
public record EnemyPolicy
(
    EnemySelectPolicy SelectPolicy,
    int[]? MonsterVNums = default,
    CombatArea? CombatArea = default
) : IEnemySelector
{
    /// <inheritdoc />
    public Result<ILivingEntity> GetSelectedEntity(ICombatState combatState, ICollection<ILivingEntity> possibleTargets)
    {
        var targets = possibleTargets.OfType<Monster>();
        if (MonsterVNums is not null)
        {
            targets = targets.Where(x => MonsterVNums.Contains(x.VNum));
        }
        targets = targets.ToArray();

        if (!targets.Any())
        {
            return new EntityNotFoundError();
        }

        if (combatState.Game.Character is null)
        {
            return new CharacterNotInitializedError();
        }

        var position = combatState.Game.Character.Position;
        if (position is null)
        {
            return new CharacterNotInitializedError();
        }

        var characterPosition = position.Value;
        ILivingEntity? target = null;
        switch (SelectPolicy)
        {
            case EnemySelectPolicy.Aggressive:
                throw new NotImplementedException(); // TODO: implement aggressive policy
            case EnemySelectPolicy.Closest:
                target = targets
                    .Where(x => x.Position is not null && (CombatArea?.IsInside(x.Position.Value) ?? true))
                    .MinBy(x => x.Position!.Value.DistanceSquared(characterPosition))!;
                break;
            case EnemySelectPolicy.LowestHealth:
                target = targets.MinBy
                (
                    x =>
                    {
                        if (x.Hp is null)
                        {
                            return int.MaxValue;
                        }

                        if (x.Hp.Amount is not null)
                        {
                            return x.Hp.Amount;
                        }

                        if (x.Hp.Percentage is not null && x.Level is not null)
                        {
                            return x.Hp.Percentage * 100 * x.Level;
                        }

                        if (x.Hp.Maximum is not null)
                        {
                            return x.Hp.Maximum; // Assume max health, best guess.
                        }

                        return int.MaxValue;
                    }
                );
                break;
        }

        if (target is null)
        {
            return new EntityNotFoundError();
        }

        return Result<ILivingEntity>.FromSuccess(target);
    }
}

/// <summary>
/// A policy enemy selector.
/// </summary>
public enum EnemySelectPolicy
{
    /// <summary>
    /// Select the enemy with the lowest health.
    /// </summary>
    LowestHealth,

    /// <summary>
    /// Selects the enemy that targets the user.
    /// </summary>
    Aggressive,

    /// <summary>
    /// Selects the enemy that is the closest to the character.
    /// </summary>
    Closest
}

/// <summary>
/// The combat area around which to find enemies.
/// </summary>
/// <param name="CenterX">The area center x coordinate.</param>
/// <param name="CenterY">The area center y coordinate.</param>
/// <param name="Range">The maximum range from the center.</param>
public record CombatArea(short CenterX, short CenterY, short Range)
{
    /// <summary>
    /// Create a combat area around a specified entity.
    /// </summary>
    /// <param name="entity">The entity.</param>
    /// <param name="range">The range.</param>
    /// <returns>The combat area.</returns>
    /// <exception cref="ArgumentException">If the entity does not have a position.</exception>
    public static CombatArea CreateAroundEntity(IEntity entity, short range)
    {
        var position = entity.Position;
        if (position is null)
        {
            throw new ArgumentException(nameof(entity));
        }

        return new CombatArea(position.Value.X, position.Value.Y, range);
    }

    /// <summary>
    /// Gets whether the position is inside of the combat area.
    /// </summary>
    /// <param name="position">The position.</param>
    /// <returns>Whether the position is inside.</returns>
    public bool IsInside(Position position)
    {
        return position.DistanceSquared(new Position(CenterX, CenterY)) < Range * Range;
    }
}