using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using Microsoft.Xna.Framework;
using MrGameEng.AI;
using MrGameEng.Graphics;

namespace LittleSim.Sim;

/// <summary>Что житель делает прямо сейчас — результат utility-выбора.</summary>
public enum PawnAction
{
    /// <summary>Бродит по миру, тратя энергию.</summary>
    Wander,

    /// <summary>Стоит и восстанавливает энергию.</summary>
    Rest,
}

/// <summary>
/// Потребности жителя. Пока одна — энергия (0 — без сил, 1 — полон сил); со временем
/// расходуется при блуждании и восстанавливается на отдыхе. Голод/жажда добавятся так же.
/// </summary>
public struct PawnNeeds : IComponent
{
    /// <summary>Запас сил в диапазоне [0, 1].</summary>
    public float Energy;
}

/// <summary>
/// «Мозг» жителя: выбранное действие и таймер до следующего пересмотра решения.
/// Решение принимает <see cref="PawnDecisionSystem"/> через движковый <see cref="UtilityAi{TContext}"/>.
/// </summary>
public struct PawnBrain : IComponent
{
    /// <summary>Текущее действие.</summary>
    public PawnAction Action;

    /// <summary>Секунды до следующего пересмотра решения — не пересчитываем выбор каждый кадр.</summary>
    public float DecideIn;
}

/// <summary>Снимок состояния жителя, который читают соображения utility-выбора.</summary>
public readonly record struct PawnContext(float Energy);

/// <summary>
/// Utility-выбор действия жителя на движковом <see cref="UtilityAi{TContext}"/>. Reasoner строится
/// один раз; каждый житель пересматривает решение раз в <see cref="ReevaluateInterval"/> секунд, а
/// не каждый кадр (энергия меняется медленно — частить незачем, и решение не дёргается на границе).
/// Выбор детерминирован: при равенстве очков побеждает первое действие.
/// </summary>
public sealed class PawnDecisionSystem : QuerySystem<PawnNeeds, PawnBrain>
{
    private const float ReevaluateInterval = 0.75f;

    private readonly UtilityAi<PawnContext> _brain = new(
        // Отдых тянет к себе только когда сил мало: (1 − energy)^3 быстро спадает к нулю выше ~0.3.
        new UtilityAction<PawnContext>(
            "rest",
            new Consideration<PawnContext>(
                "tired",
                c => c.Energy,
                curve: ResponseCurve.Polynomial(exponent: 3f, slope: -1f, xShift: 1f)
            )
        ),
        // Блуждание — поведение по умолчанию: тем привлекательнее, чем больше сил.
        new UtilityAction<PawnContext>(
            "wander",
            new Consideration<PawnContext>("rested", c => c.Energy)
        )
    );

    protected override void OnUpdate()
    {
        var delta = Tick.deltaTime;
        foreach (var (needs, brains, _) in Query.Chunks)
        {
            var n = needs.Span;
            var b = brains.Span;
            for (var i = 0; i < n.Length; i++)
            {
                ref var brain = ref b[i];
                brain.DecideIn -= delta;
                if (brain.DecideIn > 0f)
                {
                    continue;
                }

                brain.DecideIn = ReevaluateInterval;
                var choice = _brain.Select(new PawnContext(n[i].Energy));
                brain.Action = choice?.Name == "rest" ? PawnAction.Rest : PawnAction.Wander;
            }
        }
    }

    /// <summary>Очки последнего вычисленного выбора, по порядку действий — для дебаг-команды.</summary>
    public (string Name, float Score)[] Inspect(float energy)
    {
        _brain.Select(new PawnContext(energy));
        var scores = _brain.LastScores;
        var result = new (string, float)[_brain.Actions.Count];
        for (var i = 0; i < result.Length; i++)
        {
            result[i] = (_brain.Actions[i].Name, scores[i]);
        }

        return result;
    }
}

/// <summary>
/// Презентация состояния ИИ: уставший житель темнеет (альфа падает с энергией). Система читает
/// симуляцию и пишет только в <see cref="Sprite"/> — никаких draw-вызовов, граница sim/presentation цела.
/// </summary>
public sealed class PawnAppearanceSystem : QuerySystem<PawnNeeds, Sprite>
{
    private const float MinBrightness = 0.45f;

    protected override void OnUpdate()
    {
        foreach (var (needs, sprites, _) in Query.Chunks)
        {
            var n = needs.Span;
            var s = sprites.Span;
            for (var i = 0; i < n.Length; i++)
            {
                var brightness =
                    MinBrightness + (1f - MinBrightness) * Math.Clamp(n[i].Energy, 0f, 1f);
                s[i].Color = Color.White * brightness;
            }
        }
    }
}

/// <summary>
/// Расход и восстановление энергии по текущему действию: блуждающий устаёт, отдыхающий
/// восстанавливается. Скорости подобраны так, что отдых короткий, а блуждание долгое.
/// </summary>
public sealed class PawnNeedsSystem : QuerySystem<PawnNeeds, PawnBrain>
{
    private const float DrainPerSecond = 0.04f;
    private const float RegenPerSecond = 0.2f;

    protected override void OnUpdate()
    {
        var delta = Tick.deltaTime;
        foreach (var (needs, brains, _) in Query.Chunks)
        {
            var n = needs.Span;
            var b = brains.Span;
            for (var i = 0; i < n.Length; i++)
            {
                ref var energy = ref n[i].Energy;
                energy +=
                    b[i].Action == PawnAction.Rest
                        ? RegenPerSecond * delta
                        : -DrainPerSecond * delta;
                energy = Math.Clamp(energy, 0f, 1f);
            }
        }
    }
}
