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

namespace LittleSim.Sim;

/// <summary>
/// Текущее выделение игрока (как в RimWorld): id выбранной сущности или -1. Общий источник истины
/// для системы пикинга (<see cref="SelectionSystem"/>) и панели осмотра (<c>InspectPanel</c>):
/// первая пишет, вторая читает и рисует рамку/инфо. Хранит id, а не <see cref="Entity"/>, чтобы
/// безопасно переживать удаление сущности (растение умерло) — валидность проверяется через
/// <c>EntityStore.TryGetEntityById</c>.
/// </summary>
public sealed class Selection
{
    /// <summary>Runtime-id выбранной сущности, или -1, если ничего не выбрано.</summary>
    public int EntityId { get; private set; } = -1;

    /// <summary>Есть ли активное выделение.</summary>
    public bool HasSelection => EntityId >= 0;

    /// <summary>Выбрать сущность по id.</summary>
    public void Select(int entityId) => EntityId = entityId;

    /// <summary>Снять выделение.</summary>
    public void Clear() => EntityId = -1;
}

/// <summary>
/// Пикинг мышью: ЛКМ выбирает верхнюю сущность под курсором (ближайшую по ограничивающей окружности
/// спрайта), клик по пустому месте или ПКМ снимает выделение. Молчит, пока ввод захвачен оверлеем
/// (консоль/инспектор) или активен <paramref name="blocked"/> (меню-пауза), и игнорирует клики над
/// UI-панелями (<paramref name="overUi"/>). Выбор работает и на паузе — мир можно осматривать стоя.
/// </summary>
public sealed class SelectionSystem(
    EntityStore store,
    InputManager input,
    Renderer2D renderer,
    Selection selection,
    Func<bool> blocked,
    Func<bool> overUi
) : BaseSystem
{
    protected override void OnUpdateGroup()
    {
        if (blocked() || overUi())
        {
            return; // меню-пауза/консоль или клик пришёлся на виджет UI — это не пикинг мира
        }

        if (input.IsMousePressed(MouseButton.Right))
        {
            selection.Clear();
            return;
        }

        if (!input.IsMousePressed(MouseButton.Left))
        {
            return;
        }

        var mouse = input.MousePosition;

        var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
        if (TryPick(world, out var id))
        {
            selection.Select(id);
        }
        else
        {
            selection.Clear(); // клик по пустой земле снимает выделение
        }
    }

    // Ближайшее растение, чья ограничивающая окружность спрайта накрывает точку мира. Запрос
    // ограничен PlantGrowth — так из пикинга выпадают спрайты рамки выделения и прочий не-контент.
    private bool TryPick(Vector2 world, out int id)
    {
        id = -1;
        var bestDistance = float.MaxValue;
        foreach (
            var (transforms, sprites, _, entities) in store
                .Query<Transform2D, Sprite, PlantGrowth>()
                .Chunks
        )
        {
            var t = transforms.Span;
            var s = sprites.Span;
            for (var i = 0; i < t.Length; i++)
            {
                if (s[i].Region is not { } region)
                {
                    continue;
                }

                var (center, radius) = CullingMath.SpriteBoundingCircle(
                    in t[i],
                    region,
                    s[i].Origin
                );
                var distance = Vector2.DistanceSquared(center, world);
                if (distance <= radius * radius && distance < bestDistance)
                {
                    bestDistance = distance;
                    id = entities.EntityAt(i).Id;
                }
            }
        }

        return id >= 0;
    }
}
