using System;
using System.IO;
using Godot;
using Theriapolis.Core;
using Theriapolis.Core.Persistence;
using Theriapolis.Core.Rules.Character;
using Theriapolis.GodotHost.Platform;
using Theriapolis.GodotHost.UI;

namespace Theriapolis.GodotHost.Scenes;

/// <summary>
/// M7.4 — pause overlay. Pushed by PlayScreen when Esc is pressed.
/// Halts the game clock via <c>GetTree().Paused = true</c> so the
/// player + streamer + controller all freeze; the overlay itself
/// runs with <c>ProcessModeEnum.WhenPaused</c> so it stays
/// responsive to input.
///
/// Two sub-states: <see cref="BuildMain"/> shows the main menu
/// (Resume / Level Up / Save Game / Quicksave / Quit). Clicking
/// "Save Game" flips to <see cref="BuildSlotPicker"/> — a per-slot
/// row list that calls back into <see cref="PlayScreen.SaveTo"/>.
/// Esc backs out of the slot picker to main, then closes the
/// overlay on a second press.
///
/// Save-from-pause shows a status line inside the panel (separate
/// from PlayScreen's save-flash toast, which is suppressed while
/// the tree is paused).
/// </summary>
public partial class PauseMenuScreen : CanvasLayer
{
    private readonly PlayScreen _playScreen;
    private Control _root = null!;
    private PanelContainer _panel = null!;
    private VBoxContainer _content = null!;
    private Label _statusLabel = null!;
    private bool _showingSlots;
    // Edge-detection for the Esc that opened the overlay — without this
    // the same press both opens AND closes the menu on the next frame.
    private bool _consumedOpeningEsc;

    public PauseMenuScreen(PlayScreen playScreen)
    {
        _playScreen = playScreen ?? throw new ArgumentNullException(nameof(playScreen));
    }

    public override void _Ready()
    {
        Layer = 50;
        ProcessMode = ProcessModeEnum.WhenPaused;
        GetTree().Paused = true;

        _root = new Control
        {
            MouseFilter = Control.MouseFilterEnum.Stop,
            ProcessMode = ProcessModeEnum.WhenPaused,
        };
        _root.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
        AddChild(_root);

        // Half-opaque scrim so the world reads as backgrounded.
        var scrim = new ColorRect
        {
            Color = new Color(0, 0, 0, 0.55f),
            MouseFilter = Control.MouseFilterEnum.Ignore,
        };
        scrim.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
        _root.AddChild(scrim);

        var center = new CenterContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
        center.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
        _root.AddChild(center);

        // Apply the codex theme on the panel root so the cascade reaches
        // every button (overlays mount outside the PlayScreen Control tree,
        // so the parent theme doesn't cascade automatically).
        _panel = new PanelContainer
        {
            ThemeTypeVariation = "Card",
            Theme = CodexTheme.Build(),
            CustomMinimumSize = new Vector2(360, 0),
        };
        center.AddChild(_panel);

        var margin = new MarginContainer();
        margin.AddThemeConstantOverride("margin_left", 28);
        margin.AddThemeConstantOverride("margin_right", 28);
        margin.AddThemeConstantOverride("margin_top", 20);
        margin.AddThemeConstantOverride("margin_bottom", 20);
        _panel.AddChild(margin);

        _content = new VBoxContainer();
        _content.AddThemeConstantOverride("separation", 10);
        margin.AddChild(_content);

        BuildMain();
    }

    private void BuildMain()
    {
        _showingSlots = false;
        ClearContent();

        _content.AddChild(new Label
        {
            Text = "PAUSED",
            ThemeTypeVariation = "H2",
            HorizontalAlignment = HorizontalAlignment.Center,
        });

        AddSpacer(8);

        var resume = MakeMenuButton("Resume", primary: true);
        resume.Pressed += Close;
        _content.AddChild(resume);

        // Level-up affordance — only when eligible. Disabled in M7 with a
        // tooltip; the actual LevelUpScreen ships with M8.
        var pc = _playScreen.PlayerCharacter();
        if (pc is not null && LevelUpFlow.CanLevelUp(pc))
        {
            var lvlBtn = MakeMenuButton($"★ Level Up ({pc.Level} → {pc.Level + 1})", primary: false);
            lvlBtn.Disabled = true;
            lvlBtn.TooltipText = "Level-up screen ships with M8.";
            _content.AddChild(lvlBtn);
        }

        var saveBtn = MakeMenuButton("Save Game", primary: false);
        saveBtn.Pressed += BuildSlotPicker;
        _content.AddChild(saveBtn);

        var quickSave = MakeMenuButton("Quicksave (autosave slot)", primary: false);
        quickSave.Pressed += OnQuicksave;
        _content.AddChild(quickSave);

        var quitBtn = MakeMenuButton("Quit to Title", primary: false);
        quitBtn.Pressed += OnQuitToTitle;
        _content.AddChild(quitBtn);

        AddSpacer(6);

        _statusLabel = new Label
        {
            Text = " ",
            ThemeTypeVariation = "Eyebrow",
            HorizontalAlignment = HorizontalAlignment.Center,
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
        };
        _content.AddChild(_statusLabel);
    }

    private void BuildSlotPicker()
    {
        _showingSlots = true;
        ClearContent();

        _content.AddChild(new Label
        {
            Text = "SAVE TO SLOT",
            ThemeTypeVariation = "Eyebrow",
            HorizontalAlignment = HorizontalAlignment.Center,
        });
        AddSpacer(4);

        for (int i = 1; i <= C.SAVE_SLOT_COUNT; i++)
        {
            int slotNum = i;
            string path = SavePaths.SlotPath(slotNum);
            string prefix = $"Slot {slotNum:D2}";
            string label = prefix;
            if (File.Exists(path))
            {
                try
                {
                    var bytes = File.ReadAllBytes(path);
                    var header = SaveCodec.DeserializeHeaderOnly(bytes);
                    label = SaveSlotFormat.FormatRow(prefix, header);
                }
                catch { label += " — <unreadable>"; }
            }
            else
            {
                label += " — <empty>";
            }

            var btn = new Button
            {
                Text = label,
                CustomMinimumSize = new Vector2(0, 36),
                SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
                Alignment = HorizontalAlignment.Left,
            };
            btn.Pressed += () => OnSaveToSlot(slotNum, path);
            _content.AddChild(btn);
        }

        AddSpacer(6);
        var back = MakeMenuButton("← Back", primary: false);
        back.Pressed += BuildMain;
        _content.AddChild(back);

        _statusLabel = new Label
        {
            Text = " ",
            ThemeTypeVariation = "Eyebrow",
            HorizontalAlignment = HorizontalAlignment.Center,
            AutowrapMode = TextServer.AutowrapMode.WordSmart,
        };
        _content.AddChild(_statusLabel);
    }

    private void OnQuicksave()
    {
        bool ok = _playScreen.SaveTo(SavePaths.AutosavePath());
        ShowStatus(ok ? "Quicksaved." : "Quicksave failed.");
    }

    private void OnSaveToSlot(int slotNum, string path)
    {
        bool ok = _playScreen.SaveTo(path);
        if (ok)
        {
            BuildMain();
            ShowStatus($"Saved to slot {slotNum:D2}.");
        }
        else
        {
            ShowStatus("Save failed.");
        }
    }

    private void OnQuitToTitle()
    {
        // Autosave on quit-to-title, matching MonoGame behaviour. A failed
        // autosave doesn't block the quit — better to let the user leave
        // than trap them with an angry dialog.
        _playScreen.SaveTo(SavePaths.AutosavePath());

        GetTree().Paused = false;
        var playParent = _playScreen.GetParent();
        if (playParent is not null)
        {
            foreach (Node child in playParent.GetChildren())
                child.QueueFree();
            playParent.AddChild(new TitleScreen());
        }
        QueueFree();
    }

    private void Close()
    {
        GetTree().Paused = false;
        QueueFree();
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is not InputEventKey { Pressed: true, Keycode: Key.Escape } key) return;
        if (key.Echo) return;

        // The same physical Esc press that *opened* this overlay produces
        // exactly one event. We're routed via _UnhandledInput on the layer,
        // which dispatches AFTER PlayScreen.Input has run + consumed the
        // event with SetInputAsHandled — so this branch fires on the NEXT
        // Esc press. The _consumedOpeningEsc guard is a belt-and-braces
        // for the case where input routing skips the consumed flag.
        if (!_consumedOpeningEsc)
        {
            _consumedOpeningEsc = true;
            return;
        }

        GetViewport().SetInputAsHandled();
        if (_showingSlots) BuildMain();
        else               Close();
    }

    // ──────────────────────────────────────────────────────────────────────
    // Layout helpers

    private void ClearContent()
    {
        foreach (Node child in _content.GetChildren()) child.QueueFree();
    }

    private void AddSpacer(int height)
        => _content.AddChild(new Control { CustomMinimumSize = new Vector2(0, height) });

    private static Button MakeMenuButton(string text, bool primary)
    {
        var btn = new Button
        {
            Text = text,
            FocusMode = Control.FocusModeEnum.None,
            SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
            CustomMinimumSize = new Vector2(0, 40),
        };
        if (primary) btn.ThemeTypeVariation = "PrimaryButton";
        return btn;
    }

    private void ShowStatus(string text)
    {
        if (_statusLabel is not null) _statusLabel.Text = text;
    }
}
