using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Godot;
using Theriapolis.Core.Persistence;
using Theriapolis.Core.World.Generation;
using Theriapolis.GodotHost.Platform;
using Theriapolis.GodotHost.UI;

namespace Theriapolis.GodotHost.Scenes;

/// <summary>
/// M7.1 — runs the 23-stage worldgen pipeline on a background thread
/// and shows per-stage progress. Transitions to <see cref="PlayScreenStub"/>
/// (which M7.2 will replace with the real PlayScreen) on completion.
///
/// Mirrors <c>Theriapolis.Game/Screens/WorldGenProgressScreen.cs</c>:
/// same volatile-field hand-off between the worker and the UI thread,
/// same soft stage-hash warning when restoring from a saved header.
///
/// Inputs (from <see cref="GameSession"/>):
///   - <c>Seed</c> — required.
///   - <c>PendingHeader</c> — present when restoring from save; triggers
///     the post-gen stage-hash diff against <c>WorldState.StageHashes</c>.
///
/// Outputs:
///   - <c>session.Ctx</c> set on success; consumed by the next screen.
///
/// Escape during generation: cancel the worker (honoured at the next
/// stage boundary), return to Title.
/// </summary>
public partial class WorldGenProgressScreen : Control
{
    private WorldGenContext? _ctx;
    private Task? _genTask;
    private CancellationTokenSource? _cts;
    private volatile float _progress;
    private volatile string _stageName = "Initialising…";
    private volatile bool _complete;
    private volatile string? _error;

    private Label _titleLabel = null!;
    private ProgressBar _progressBar = null!;
    private Label _stageLabel = null!;
    private bool _transitioned;

    public override void _Ready()
    {
        Theme = CodexTheme.Build();
        SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);

        // Backing panel so the dark palette Bg fills the viewport (the
        // Control itself paints nothing). Mirrors TitleScreen.cs.
        var bg = new Panel { MouseFilter = MouseFilterEnum.Ignore };
        AddChild(bg);
        bg.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
        MoveChild(bg, 0);

        BuildUI();
        StartGeneration();
    }

    private void BuildUI()
    {
        var center = new CenterContainer { MouseFilter = MouseFilterEnum.Ignore };
        AddChild(center);
        center.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);

        var col = new VBoxContainer { CustomMinimumSize = new Vector2(480, 0) };
        col.AddThemeConstantOverride("separation", 14);
        center.AddChild(col);

        var session = GameSession.From(this);
        col.AddChild(new Label
        {
            Text = "FORGING THE WORLD",
            ThemeTypeVariation = "Eyebrow",
            HorizontalAlignment = HorizontalAlignment.Center,
        });
        _titleLabel = new Label
        {
            Text = $"Seed 0x{session.Seed:X}",
            ThemeTypeVariation = "H2",
            HorizontalAlignment = HorizontalAlignment.Center,
        };
        col.AddChild(_titleLabel);

        _progressBar = new ProgressBar
        {
            MinValue = 0,
            MaxValue = 1,
            Step = 0.001,
            ShowPercentage = true,
            CustomMinimumSize = new Vector2(0, 22),
        };
        col.AddChild(_progressBar);

        _stageLabel = new Label
        {
            Text = "Starting…",
            HorizontalAlignment = HorizontalAlignment.Center,
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
        };
        col.AddChild(_stageLabel);

        col.AddChild(new Label
        {
            Text = "Esc to cancel · returns to title.",
            ThemeTypeVariation = "Eyebrow",
            HorizontalAlignment = HorizontalAlignment.Center,
        });
    }

    private void StartGeneration()
    {
        _cts = new CancellationTokenSource();
        var token = _cts.Token;
        var session = GameSession.From(this);
        ulong seed = session.Seed;
        string dataDir = ContentPaths.DataDir;

        _genTask = Task.Run(() =>
        {
            try
            {
                var ctx = new WorldGenContext(seed, dataDir)
                {
                    ProgressCallback = (name, frac) =>
                    {
                        _stageName = name;
                        _progress = frac;
                    },
                    Log = msg => GD.Print($"[worldgen] {msg}"),
                };
                WorldGenerator.RunAll(ctx);
                if (token.IsCancellationRequested) return;
                _ctx = ctx;
                _complete = true;
            }
            catch (Exception ex)
            {
                var inner = ex is AggregateException ae ? ae.Flatten().InnerException ?? ex : ex;
                _error = inner.ToString();
            }
        }, token);
    }

    public override void _Process(double delta)
    {
        if (_transitioned) return;
        if (_error is not null)
        {
            ShowError(_error);
            return;
        }
        if (_complete && _ctx is not null)
        {
            _transitioned = true;
            Transition();
            return;
        }
        _progressBar.Value = _progress;
        _stageLabel.Text = _stageName;
    }

    private void Transition()
    {
        var session = GameSession.From(this);
        if (session.PendingHeader is not null)
            CompareStageHashes(session.PendingHeader);

        session.Ctx = _ctx;

        // M7.2 — the real PlayScreen. PlayScreenStub is kept around as
        // a fallback for any future code path that hasn't been wired up
        // (e.g. mid-development load flows), but the live hand-off lands
        // in the play view.
        SwapTo(new PlayScreen());
    }

    private void SwapTo(Node next)
    {
        var parent = GetParent();
        if (parent is null) return;
        foreach (Node sibling in parent.GetChildren())
            if (sibling != this) sibling.QueueFree();
        parent.AddChild(next);
        QueueFree();
    }

    private void ShowError(string error)
    {
        _stageLabel.Text = "ERROR — press Escape to return to title";
        _progressBar.Value = 0;
        // Crop to the first line + 100 chars so the title label stays legible.
        int newline = error.IndexOf('\n');
        string headline = newline > 0 ? error[..newline] : error;
        _titleLabel.Text = headline.Length > 100 ? headline[..100] + "…" : headline;

        try
        {
            string logPath = ProjectSettings.GlobalizePath("user://worldgen_error.log");
            File.WriteAllText(logPath, $"[{DateTime.Now:u}] WorldGen ERROR\n{error}\n");
            GD.PushError($"[worldgen] Wrote {logPath}");
        }
        catch { /* best-effort */ }
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is InputEventKey { Pressed: true, Keycode: Key.Escape })
        {
            _cts?.Cancel();
            BackToTitle();
        }
    }

    public override void _ExitTree()
    {
        _cts?.Cancel();
        _cts?.Dispose();
        _cts = null;
    }

    private void BackToTitle()
    {
        var session = GameSession.From(this);
        session.ClearPending();
        session.Ctx = null;
        SwapTo(new TitleScreen());
    }

    private void CompareStageHashes(SaveHeader savedHeader)
    {
        if (_ctx is null) return;
        int mismatches = 0;
        foreach (var kv in _ctx.World.StageHashes)
        {
            if (!savedHeader.StageHashes.TryGetValue(kv.Key, out var sv)) continue;
            string current = $"0x{kv.Value:X}";
            if (!string.Equals(sv, current, StringComparison.OrdinalIgnoreCase))
            {
                mismatches++;
                GD.PushWarning($"[save-migration] Stage '{kv.Key}' hash drift: saved={sv}, current={current}");
            }
        }
        if (mismatches > 0)
            GD.PushWarning($"[save-migration] {mismatches} stage(s) drifted; loading anyway (soft).");
    }
}
