using System;
using System.Collections.Generic;
using Friflo.Engine.ECS;
using LittleSim.App;
using LittleSim.Content;
using LittleSim.Sim;
using LittleSim.UI;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MrGameEng.Assets;
using MrGameEng.Core;
using MrGameEng.DevConsole;
using MrGameEng.Formulas;
using MrGameEng.Genetics;
using MrGameEng.Graphics;
using MrGameEng.Host;
using MrGameEng.Input;
using MrGameEng.Inspector;
using MrGameEng.Lighting;
using MrGameEng.Tilemaps;
using MrGameEng.UI;
using Myra.Graphics2D;
using Myra.Graphics2D.UI;

namespace LittleSim.Scenes;

/// <summary>
/// Мир LittleSim: тайловый рельеф целиком описан дефами Core-мода и строится из
/// <see cref="WorldConfig"/> (размер/сид/масштаб деталей) процедурной генерацией движка
/// (<see cref="WorldGenerator"/>). Каждый тип клетки рисуется текстурой-поверхностью из атласа
/// (вода — тонированным тайлом). Поверх растёт растительность с геномом (рост по свету/температуре/
/// почве, размножение по Менделю и смерть), климат и день/ночь. UI: HUD, полоса скорости
/// (пауза/x1/x3/x6 + горячие клавиши), меню-пауза (Esc), дев-консоль и ECS-инспектор (F1).
/// </summary>
public sealed class WorldScene : Scene
{
    public const int CellSize = 16;

    /// <summary>
    /// Сколько секунд масштабированного времени длится один игровой день. База (x1) — 3 игровые
    /// минуты за 1 реальную секунду: сутки = 1440 мин ÷ 3 = 480 с.
    /// </summary>
    public const float SecondsPerDay = 480f;

    /// <summary>Максимум растений на клетку — потолок плотности для размножения.</summary>
    public const int DensityCap = 4;

    private readonly WorldConfig _config;
    private readonly WorldSave? _save;
    private readonly RectF _bounds;

    private GameSpeed _speed = null!;
    private PauseMenu _pause = null!;
    private readonly List<Action> _speedRefreshers = [];

    private PlantSet _plants = null!;
    private float[] _cellFertility = [];
    private bool[] _cellLand = [];
    private bool[] _cellOccluderBase = []; // горы (статично из террейна)
    private bool[] _cellOccluder = []; // горы + зрелые деревья (пересобирается лайтмапом)

    /// <summary>Новый мир из конфига.</summary>
    public WorldScene(WorldConfig config)
        : this(config, null) { }

    /// <summary>Мир, восстановленный из сохранения (рельеф детерминирован сидом конфига).</summary>
    public WorldScene(WorldConfig config, WorldSave? save)
    {
        _config = config;
        _save = save;
        _bounds = new RectF(0f, 0f, config.Width * CellSize, config.Height * CellSize);
    }

    protected override void OnLoad()
    {
        var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
        var content = Context.Services.Get<GameContent>();
        var atlases = Context.Services.Get<ModAtlases>();
        var device = Context.GetGraphicsDevice();
        var input = this.UseInput();
        _speed = Context.Services.Get<GameSpeed>();
        _speed.SetStep(0); // новый мир/загрузка стартуют на x1

        var renderer = this.UseRenderer2D(
            new Renderer2DOptions { VirtualResolution = new Point(1280, 720) }
        );
        GameLayers.EnsureRegistered(renderer);
        this.UseTilemaps();

        var calendar = Context.UseCalendar(SecondsPerDay);
        var climate = Context.UseClimate(ClimateSettings.Default);
        var dayNight = new DayNight(calendar, DayNightSettings.Default);

        // Рельеф детерминирован сидом. Растения: новый мир — скаттер из сида; загрузка — из сейва.
        _plants = new PlantSet(content, atlases, device);
        var loadingPlants = _save?.Plants is { Count: > 0 };
        BuildTerrain(
            content,
            atlases,
            device,
            assets,
            loadingPlants ? null : new Random(_config.Seed)
        );
        if (loadingPlants)
        {
            RestorePlants(content);
        }

        // Освещение: лайтмап (день/ночь × окклюзия от гор/крон + точечные) множится поверх мира,
        // а Lighting.SampleAt даёт локальный свет системе роста (подлесок под кронами растёт хуже).
        var lighting = this.UseLighting(
            renderer,
            dayNight,
            _config.Width,
            _config.Height,
            CellSize,
            Vector2.Zero,
            BuildOccluders
        );

        var camera = Store.CreateEntity(new Camera(_bounds.Center, zoom: 1f, bounds: _bounds));

        // HUD, полоса скорости и меню-пауза в одной корневой панели.
        var desktop = this.UseUI();
        var hudLabel = new Label { Left = 10, Top = 8 };
        var speedBar = BuildSpeedBar(content);
        _pause = new PauseMenu(
            Context,
            content,
            _speed,
            onSave: SaveWorld,
            onMainMenu: () => Switch(new MainMenuScene()),
            onQuit: () => Context.Services.Get<Game>().Exit()
        );
        desktop.Root = Ui.Screen(hudLabel, speedBar, _pause.Root);

        this.UseInspector(renderer);
        var console = this.UseDevConsole();
        RegisterCommands(console, content, atlases);
        console.Register(
            "light",
            "light [radius] — place a point light at the cursor (night shadows demo)",
            (c, args) =>
            {
                var radius =
                    args.Length > 0
                        ? float.Parse(args[0], System.Globalization.CultureInfo.InvariantCulture)
                        : 120f;
                var mouse = Mouse.GetState();
                var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
                Store.CreateEntity(
                    Transform2D.At(world),
                    new PointLight
                    {
                        Radius = radius,
                        Color = Color.White,
                        Intensity = 0.9f,
                    }
                );
                c.WriteLine($"light at {world.X:0},{world.Y:0} r{radius:0}");
            }
        );

        UpdateSystems.Add(new PlantGrowthSystem(_plants, calendar, climate, lighting, CellSize));
        UpdateSystems.Add(
            new PlantLifecycleSystem(
                Store,
                _plants,
                calendar,
                Context.Clock,
                _config.Width,
                _config.Height,
                CellSize,
                _cellFertility,
                _cellLand,
                DensityCap,
                _config.Seed
            )
        );
        UpdateSystems.Add(
            new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
        );
        UpdateSystems.Add(
            new HudSystem(
                Context,
                content.Languages,
                hudLabel,
                "hud.world",
                _config.Seed,
                calendar,
                climate
            )
        );
        UpdateSystems.Add(new CallbackSystem(() => Hotkeys(input)));

        Log.Info(
            $"World '{_config.Name}': {_config.Width}x{_config.Height}, seed {_config.Seed}"
                + (_save is null ? " (new)" : " (loaded)")
        );
    }

    /// <summary>
    /// Строит сущность-<see cref="Tilemap"/> (тайлсет из дефов рельефа + сетка по карте высот) и
    /// заполняет по-клеточные массивы плодородности/суши для жизненного цикла. Если
    /// <paramref name="scatterRandom"/> задан (новый мир) — рассыпает растительность по дефам;
    /// при загрузке передаётся null (растения восстанавливаются из сейва отдельно).
    /// </summary>
    private void BuildTerrain(
        GameContent content,
        ModAtlases atlases,
        Microsoft.Xna.Framework.Graphics.GraphicsDevice device,
        AssetManager assets,
        Random? scatterRandom
    )
    {
        var white = new Texture2DRegion(assets.Load(GameAssets.Textures.White));

        // Тайлсет из дефов: есть поверхность — текстура из атласа, нет — тонированный тайл (вода).
        var tiles = new TileSet();
        var tileByDef = new Dictionary<TerrainDef, ushort>();
        foreach (var terrain in content.Terrains.All)
        {
            tileByDef[terrain] = terrain.Surface is { } surface
                ? tiles.Add(atlases.GetRegion(device, surface))
                : tiles.Add(white, terrain.Tint);
        }

        // Рельеф детерминирован сидом и масштабом деталей — и для нового мира, и для загрузки.
        var heights = WorldGenerator.Generate(
            _config.Width,
            _config.Height,
            _config.Seed,
            _config.SmoothPasses
        );
        var grid = new TileGrid(_config.Width, _config.Height);
        var cells = _config.Width * _config.Height;
        _cellFertility = new float[cells];
        _cellLand = new bool[cells];
        _cellOccluderBase = new bool[cells];
        _cellOccluder = new bool[cells];
        for (var x = 0; x < _config.Width; x++)
        {
            for (var y = 0; y < _config.Height; y++)
            {
                var terrain = content.Terrains.Classify(heights[x, y]);
                grid[x, y] = tileByDef[terrain];
                var cell = y * _config.Width + x;
                _cellFertility[cell] = terrain.Fertility;
                _cellLand[cell] = terrain.IsLand;
                _cellOccluderBase[cell] = terrain.BlocksLight;
                if (scatterRandom is not null)
                {
                    ScatterSpawner.Spawn(
                        this,
                        content,
                        _plants,
                        terrain,
                        new Point(x, y),
                        CellSize,
                        scatterRandom
                    );
                }
            }
        }

        Store.CreateEntity(new Tilemap(grid, tiles, CellSize));
    }

    private HorizontalStackPanel BuildSpeedBar(GameContent content)
    {
        var bar = Ui.Row(6);
        bar.HorizontalAlignment = HorizontalAlignment.Center;
        bar.VerticalAlignment = VerticalAlignment.Bottom;
        bar.Margin = new Thickness(0, 0, 0, 12);

        var pause = new TextButton { Text = content.Languages.Get("speed.pause") };
        pause.Click += (_, _) => _speed.Pause();
        bar.Widgets.Add(pause);
        _speedRefreshers.Add(() => pause.TextColor = _speed.IsPaused ? Ui.Accent : Ui.Muted);

        for (var i = 0; i < _speed.Steps.Count; i++)
        {
            var index = i;
            var button = new TextButton { Text = $"x{_speed.Steps[i]:0}" };
            button.Click += (_, _) => _speed.SetStep(index);
            bar.Widgets.Add(button);
            _speedRefreshers.Add(() =>
                button.TextColor =
                    !_speed.IsPaused && _speed.StepIndex == index ? Ui.Accent : Ui.Muted
            );
        }

        void Refresh()
        {
            foreach (var refresh in _speedRefreshers)
            {
                refresh();
            }
        }

        _speed.Changed += Refresh;
        RegisterUnload(() => _speed.Changed -= Refresh);
        Refresh();
        return bar;
    }

    private void Hotkeys(InputManager input)
    {
        if (input.IsKeyPressed(Keys.Escape))
        {
            _pause.Toggle();
        }

        if (_pause.IsOpen)
        {
            return;
        }

        if (input.IsKeyPressed(Keys.Space))
        {
            _speed.TogglePause();
        }

        if (input.IsKeyPressed(Keys.D1))
        {
            _speed.SetStep(0);
        }

        if (input.IsKeyPressed(Keys.D2) && _speed.Steps.Count > 1)
        {
            _speed.SetStep(1);
        }

        if (input.IsKeyPressed(Keys.D3) && _speed.Steps.Count > 2)
        {
            _speed.SetStep(2);
        }
    }

    private string SaveWorld()
    {
        var save = new WorldSave
        {
            Name = _config.Name,
            Width = _config.Width,
            Height = _config.Height,
            Seed = _config.Seed,
            SmoothPasses = _config.SmoothPasses,
            Population = _config.Population,
            SavedUtc = DateTime.UtcNow,
            ElapsedSeconds = Context.Clock.TotalTime,
        };

        // Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
        Store
            .Query<PlantGrowth, PlantGenome, Transform2D>()
            .ForEachEntity(
                (ref PlantGrowth grow, ref PlantGenome gene, ref Transform2D transform, Entity _) =>
                {
                    save.Plants.Add(
                        new PlantSave
                        {
                            Species = _plants[grow.Species].Def.DefName,
                            X = transform.Position.X,
                            Y = transform.Position.Y,
                            AgeDays = grow.AgeDays,
                            Stage = grow.Stage,
                            CellFertility = grow.CellFertility,
                            Genome = gene,
                        }
                    );
                }
            );

        new SaveStore().Write(save);
        Log.Info($"World '{_config.Name}' saved ({save.Plants.Count} plants)");
        return _config.Name;
    }

    private void RestorePlants(GameContent content)
    {
        foreach (var plant in _save!.Plants)
        {
            if (!content.Defs.TryGet<PlantDef>(plant.Species, out var def))
            {
                continue; // вид пропал (мод убрали) — пропускаем растение
            }

            PlantFactory.Create(
                Store,
                _plants,
                _plants.IndexOf(def),
                new Vector2(plant.X, plant.Y),
                plant.AgeDays,
                plant.Genome,
                plant.CellFertility,
                CellSize
            );
        }
    }

    // Текущая сетка окклюдеров для лайтмапа: статичные горы + клетки со зрелыми деревьями.
    private bool[] BuildOccluders()
    {
        Array.Copy(_cellOccluderBase, _cellOccluder, _cellOccluder.Length);
        Store
            .Query<PlantGrowth, Transform2D>()
            .ForEachEntity(
                (ref PlantGrowth grow, ref Transform2D transform, Entity _) =>
                {
                    var species = _plants[grow.Species];
                    if (
                        species.Def.TrunkRadiusCells <= 0f
                        || grow.Stage < species.Stages.Length - 1
                    )
                    {
                        return; // затеняют только зрелые деревья (со стволом)
                    }

                    var cx = Math.Clamp(
                        (int)(transform.Position.X / CellSize),
                        0,
                        _config.Width - 1
                    );
                    var cy = Math.Clamp(
                        (int)(transform.Position.Y / CellSize),
                        0,
                        _config.Height - 1
                    );
                    _cellOccluder[cy * _config.Width + cx] = true;
                }
            );
        return _cellOccluder;
    }

    private void Switch(Scene scene)
    {
        if (!Context.Scenes.IsTransitioning)
        {
            _speed.Resume();
            Context.Scenes.Switch(scene, Transitions.Fade(0.5f));
        }
    }

    private void RegisterCommands(DevConsole console, GameContent content, ModAtlases atlases)
    {
        ContentCommands.Register(console, content, atlases);
        console.Register(
            "regen",
            "regen [seed] — regenerate the world with a new seed",
            (c, args) =>
            {
                if (Context.Scenes.IsTransitioning)
                {
                    return;
                }

                var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next();
                c.WriteLine($"regenerating world, seed {seed}");
                Context.Scenes.Switch(
                    new WorldScene(_config with { Seed = seed }),
                    Transitions.Fade(0.6f)
                );
            }
        );
        console.Register(
            "formula",
            "formula <expr> — compile and evaluate an expression (gene-formula engine demo)",
            (c, args) =>
            {
                if (args.Length == 0)
                {
                    c.WriteLine(
                        "usage: formula <expr>  e.g. formula clamp(lerp(0, 10, 0.5), 0, 8)"
                    );
                    return;
                }

                var expression = string.Join(' ', args);
                try
                {
                    c.WriteLine($"{expression} = {Formula.Compile(expression).Evaluate()}");
                }
                catch (FormulaException e)
                {
                    c.WriteLine($"error: {e.Message}");
                }
            }
        );
        console.Register(
            "gene",
            "gene [seed] — generate a genome from Gene defs, show alleles/traits and a bred child",
            (c, args) => RunGeneDemo(c, content, args)
        );
        console.Register(
            "menu",
            "menu — return to the main menu",
            (_, _) => Switch(new MainMenuScene())
        );
    }

    // Демонстрация генной системы (фаза G2): из Gene-дефов генерируем геном, печатаем аллели,
    // выраженные значения и признаки (вычисленные формулами), затем скрещиваем двух особей.
    private static void RunGeneDemo(DevConsole console, GameContent content, string[] args)
    {
        var genes = content.Defs.All<GeneDef>();
        if (genes.Count == 0)
        {
            console.WriteLine("no Gene defs loaded");
            return;
        }

        var registry = genes.ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal);
        var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next();
        var random = new Random(seed);

        console.WriteLine($"genome from {genes.Count} genes, seed {seed}:");
        var parentA = Genome.Generate(genes, random);
        foreach (var gene in genes)
        {
            var allele = parentA[gene.DefName];
            console.WriteLine(
                $"  {gene.DefName}: [{allele.A:0.##}, {allele.B:0.##}] -> {parentA.Express(gene):0.###}"
            );
        }

        console.WriteLine("traits:");
        foreach (var (trait, value) in Phenotype.Compute(parentA, registry).OrderBy(t => t.Key))
        {
            console.WriteLine($"  {trait} = {value:0.###}");
        }

        var parentB = Genome.Generate(genes, random);
        var child = Genome.Breed(parentA, parentB, registry, random);
        var childTraits = Phenotype.Compute(child, registry);
        console.WriteLine(
            $"bred child: {child.Alleles.Count} genes, "
                + $"vigor={childTraits.GetValueOrDefault("vigor"):0.###}, "
                + $"lifespan={childTraits.GetValueOrDefault("lifespan"):0.#}, "
                + $"variant={childTraits.GetValueOrDefault("variant"):0}"
        );
    }
}
