using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using LittleSim.Content;
using Microsoft.Xna.Framework;
using MrGameEng.Core;
using MrGameEng.Graphics;

namespace LittleSim.Sim;

/// <summary>
/// Жизненный цикл растений: размножение (наследование по Менделю) и смерть от старости. Запускается
/// не каждый кадр, а раз в <see cref="IntervalDays"/> игрового дня. За тик: строит по-клеточную сетку
/// (счётчик плотности + «представитель» вида/генома), собирает умерших по возрасту, затем зрелые с
/// вероятностью <c>elapsed/ReproduceInterval</c> сеют потомка — приоритет перекрёстного опыления
/// (зрелый сосед того же вида в радиусе расселения), иначе самоопыление по гену. Семя падает в
/// случайную клетку радиуса, если это суша и не превышен лимит плотности. Структурные изменения
/// (создание/удаление сущностей) применяются после проходов по запросу — это безопасно.
/// </summary>
public sealed class PlantLifecycleSystem : BaseSystem
{
    private const float IntervalDays = 0.25f;

    private readonly EntityStore _store;
    private readonly PlantSet _plants;
    private readonly Calendar _calendar;
    private readonly GameClock _clock;
    private readonly int _width;
    private readonly int _height;
    private readonly int _cellSize;
    private readonly float[] _cellFertility;
    private readonly bool[] _cellLand;
    private readonly int _densityCap;
    private readonly Random _rng;
    private readonly ArchetypeQuery<PlantGrowth, PlantGenome, Transform2D> _query;

    private readonly int[] _count;
    private readonly CellRep[] _rep;
    private readonly List<Entity> _deaths = [];
    private readonly List<Candidate> _candidates = [];
    private readonly List<Birth> _births = [];
    private float _accumulator;

    public PlantLifecycleSystem(
        EntityStore store,
        PlantSet plants,
        Calendar calendar,
        GameClock clock,
        int width,
        int height,
        int cellSize,
        float[] cellFertility,
        bool[] cellLand,
        int densityCap,
        int seed
    )
    {
        _store = store;
        _plants = plants;
        _calendar = calendar;
        _clock = clock;
        _width = width;
        _height = height;
        _cellSize = cellSize;
        _cellFertility = cellFertility;
        _cellLand = cellLand;
        _densityCap = densityCap;
        _rng = new Random(seed);
        _query = store.Query<PlantGrowth, PlantGenome, Transform2D>();
        _count = new int[width * height];
        _rep = new CellRep[width * height];
    }

    protected override void OnUpdateGroup()
    {
        _accumulator += _clock.DeltaTime / _calendar.SecondsPerDay; // прошло игровых дней (с учётом паузы/скорости)
        if (_accumulator < IntervalDays)
        {
            return;
        }

        var elapsed = _accumulator;
        _accumulator = 0f;

        BuildGridAndCollect();
        Reproduce(elapsed);
        Apply();
    }

    // Проход 1: сетка плотности + представители, сбор умерших и зрелых-кандидатов.
    private void BuildGridAndCollect()
    {
        Array.Clear(_count);
        for (var c = 0; c < _rep.Length; c++)
        {
            _rep[c].Species = -1;
        }

        _deaths.Clear();
        _candidates.Clear();
        _births.Clear();

        foreach (var (growths, genomes, transforms, entities) in _query.Chunks)
        {
            var g = growths.Span;
            var dna = genomes.Span;
            var t = transforms.Span;
            for (var i = 0; i < g.Length; i++)
            {
                ref var grow = ref g[i];
                ref var gene = ref dna[i];
                if (grow.AgeDays > gene.Lifespan)
                {
                    _deaths.Add(entities.EntityAt(i)); // умер от старости
                    continue;
                }

                var pos = t[i].Position;
                var cx = Math.Clamp((int)(pos.X / _cellSize), 0, _width - 1);
                var cy = Math.Clamp((int)(pos.Y / _cellSize), 0, _height - 1);
                var cell = cy * _width + cx;
                _count[cell]++;

                var mature = grow.Stage >= _plants[grow.Species].Stages.Length - 1;
                _rep[cell] = new CellRep
                {
                    Species = grow.Species,
                    Mature = mature,
                    Genome = gene,
                };
                if (mature)
                {
                    _candidates.Add(
                        new Candidate
                        {
                            Cell = cell,
                            Species = grow.Species,
                            Genome = gene,
                        }
                    );
                }
            }
        }
    }

    // Проход 2: зрелые сеют потомка (перекрёстно или само-) в подходящую клетку радиуса.
    private void Reproduce(float elapsed)
    {
        foreach (var cand in _candidates)
        {
            var interval = MathF.Max(0.5f, cand.Genome.ReproduceInterval);
            if (_rng.NextSingle() >= elapsed / interval)
            {
                continue; // в этот тик не сеет
            }

            var range = Math.Clamp((int)MathF.Round(cand.Genome.DispersalRange), 1, 6);
            var cx = cand.Cell % _width;
            var cy = cand.Cell / _width;

            if (!TryFindPartner(cx, cy, range, cand.Species, out var partner))
            {
                if (_rng.NextSingle() >= cand.Genome.SelfPollination)
                {
                    continue; // нет партнёра и самоопыление не удалось
                }

                partner = cand.Genome; // самоопыление
            }

            if (!TryPickTarget(cx, cy, range, out var tcell))
            {
                continue; // некуда сеять (нет суши/свободного места рядом)
            }

            var child = PlantGenome.Breed(cand.Genome, partner, _rng);
            var tx = tcell % _width;
            var ty = tcell / _width;
            var position = new Vector2(
                (tx + 0.3f + _rng.NextSingle() * 0.4f) * _cellSize,
                (ty + 0.3f + _rng.NextSingle() * 0.4f) * _cellSize
            );
            _births.Add(
                new Birth
                {
                    Species = cand.Species,
                    Position = position,
                    Genome = child,
                    Fertility = _cellFertility[tcell],
                }
            );
            _count[tcell]++; // резервируем место, чтобы не переполнить клетку за тик
        }
    }

    private bool TryFindPartner(int cx, int cy, int range, int species, out PlantGenome partner)
    {
        for (var dy = -range; dy <= range; dy++)
        {
            for (var dx = -range; dx <= range; dx++)
            {
                if (dx == 0 && dy == 0)
                {
                    continue; // партнёр — другая клетка (приоритет перекрёстного)
                }

                var nx = cx + dx;
                var ny = cy + dy;
                if (nx < 0 || nx >= _width || ny < 0 || ny >= _height)
                {
                    continue;
                }

                ref var rep = ref _rep[ny * _width + nx];
                if (rep.Species == species && rep.Mature)
                {
                    partner = rep.Genome;
                    return true;
                }
            }
        }

        partner = default;
        return false;
    }

    private bool TryPickTarget(int cx, int cy, int range, out int cell)
    {
        for (var attempt = 0; attempt < 6; attempt++)
        {
            var tx = cx + _rng.Next(-range, range + 1);
            var ty = cy + _rng.Next(-range, range + 1);
            if (tx < 0 || tx >= _width || ty < 0 || ty >= _height)
            {
                continue;
            }

            var candidate = ty * _width + tx;
            if (_cellLand[candidate] && _count[candidate] < _densityCap)
            {
                cell = candidate;
                return true;
            }
        }

        cell = -1;
        return false;
    }

    private void Apply()
    {
        foreach (var entity in _deaths)
        {
            entity.DeleteEntity();
        }

        foreach (var birth in _births)
        {
            PlantFactory.Create(
                _store,
                _plants,
                birth.Species,
                birth.Position,
                ageDays: 0f,
                birth.Genome,
                birth.Fertility,
                _cellSize
            );
        }
    }

    private struct CellRep
    {
        public int Species;
        public bool Mature;
        public PlantGenome Genome;
    }

    private struct Candidate
    {
        public int Cell;
        public int Species;
        public PlantGenome Genome;
    }

    private struct Birth
    {
        public int Species;
        public Vector2 Position;
        public PlantGenome Genome;
        public float Fertility;
    }
}
