using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Myra.Graphics2D.UI;
using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;

namespace Theriapolis.Game.Screens;

/// <summary>
/// Phase 5 M2: pre-game character creation. Player picks clade, species,
/// class, background, stat method, skills, and name; the screen builds a
/// <see cref="Character"/> and threads it through worldgen into the play
/// session.
///
/// This is intentionally minimal-but-complete: every selector is visible at
/// once with sensible defaults, so a player can confirm immediately. M3+ may
/// expand stat-array manual assignment, multi-step wizard polish, and the
/// portrait/preview pane.
/// </summary>
public sealed class CharacterCreationScreen : IScreen
{
    private readonly ulong _seed;
    private Game1 _game = null!;
    private Desktop _desktop = null!;

    // Loaded content
    private ContentResolver _content = null!;
    private CladeDef[]      _clades      = null!;
    private SpeciesDef[]    _allSpecies  = null!;
    private ClassDef[]      _classes     = null!;
    private BackgroundDef[] _backgrounds = null!;

    // Live selections
    private CladeDef?      _clade;
    private SpeciesDef?    _species;
    private ClassDef?      _class;
    private BackgroundDef? _background;
    private string         _name = "Wanderer";
    private bool           _useRoll = false;
    private AbilityScores  _baseAbilities = new(15, 14, 13, 12, 10, 8);  // Standard Array, default order
    private readonly HashSet<SkillId> _chosenSkills = new();

    // Stat-roll seeding: ms-since-game-start at the moment the screen opened
    // (so successive rerolls advance ms). M2 dev override: hold SHIFT while
    // pressing Reroll to use a fixed override (helpful for screenshots).
    private readonly long _gameStartMs;
    private long _msAtScreenOpen;

    public CharacterCreationScreen(ulong seed)
    {
        _seed         = seed;
        _gameStartMs  = Environment.TickCount64;
    }

    public void Initialize(Game1 game)
    {
        _game = game;
        _msAtScreenOpen = Environment.TickCount64 - _gameStartMs;

        var loader = new ContentLoader(_game.ContentDataDirectory);
        _content     = new ContentResolver(loader);
        _clades      = _content.Clades.Values.OrderBy(c => c.Id).ToArray();
        _allSpecies  = _content.Species.Values.OrderBy(s => s.Id).ToArray();
        _classes     = _content.Classes.Values.OrderBy(c => c.Id).ToArray();
        _backgrounds = _content.Backgrounds.Values.OrderBy(b => b.Id).ToArray();

        // Sensible defaults so the player can press Confirm immediately.
        _clade      = _clades.FirstOrDefault(c => c.Id == "canidae")     ?? _clades[0];
        _species    = _allSpecies.FirstOrDefault(s => s.CladeId == _clade.Id);
        _class      = _classes.FirstOrDefault(c => c.Id == "fangsworn")  ?? _classes[0];
        _background = _backgrounds.FirstOrDefault(b => b.Id == "pack_raised") ?? _backgrounds[0];

        AssignStandardArrayByClassPriority();
        AutoPickSkills();

        BuildUI();
    }

    private void BuildUI()
    {
        var root = new VerticalStackPanel
        {
            Spacing             = 6,
            HorizontalAlignment = HorizontalAlignment.Center,
            VerticalAlignment   = VerticalAlignment.Top,
            Margin              = new Myra.Graphics2D.Thickness(20),
        };
        root.Widgets.Add(new Label { Text = "CHARACTER CREATION", HorizontalAlignment = HorizontalAlignment.Center });
        root.Widgets.Add(new Label { Text = $"Seed: 0x{_seed:X}",  HorizontalAlignment = HorizontalAlignment.Center });
        root.Widgets.Add(new Label { Text = " " });

        // Clade row
        root.Widgets.Add(new Label { Text = "Clade:" });
        root.Widgets.Add(BuildSelectorRow(
            _clades,
            c => c.Name,
            c => c == _clade,
            c => { _clade = c; _species = _allSpecies.FirstOrDefault(s => s.CladeId == c.Id); RebuildUI(); }));

        // Species row (filtered to selected clade)
        root.Widgets.Add(new Label { Text = "Species:" });
        var filteredSpecies = _allSpecies.Where(s => _clade is null || s.CladeId == _clade.Id).ToArray();
        root.Widgets.Add(BuildSelectorRow(
            filteredSpecies,
            s => s.Name + " (" + s.Size + ")",
            s => s == _species,
            s => { _species = s; RebuildUI(); }));

        // Class row
        root.Widgets.Add(new Label { Text = "Class:" });
        root.Widgets.Add(BuildSelectorRow(
            _classes,
            c => c.Name + " (d" + c.HitDie + ")",
            c => c == _class,
            c => { _class = c; AutoPickSkills(); AssignStandardArrayByClassPriority(); RebuildUI(); }));

        // Background row
        root.Widgets.Add(new Label { Text = "Background:" });
        root.Widgets.Add(BuildSelectorRow(
            _backgrounds,
            b => b.Name,
            b => b == _background,
            b => { _background = b; RebuildUI(); }));

        // Stat method
        root.Widgets.Add(new Label { Text = " " });
        var statRow = new HorizontalStackPanel { Spacing = 8 };
        statRow.Widgets.Add(MakeButton("Standard Array", !_useRoll,
            () => { _useRoll = false; AssignStandardArrayByClassPriority(); RebuildUI(); }));
        statRow.Widgets.Add(MakeButton("Roll 4d6 drop lowest", _useRoll,
            () => { _useRoll = true; RollAndAssign(); RebuildUI(); }));
        if (_useRoll)
            statRow.Widgets.Add(MakeButton("Reroll", false, () => { RollAndAssign(); RebuildUI(); }));
        root.Widgets.Add(statRow);

        // Stat readout (post clade+species mods)
        root.Widgets.Add(new Label { Text = FormatStats() });

        // Skills (only show if class is picked)
        if (_class is not null)
        {
            root.Widgets.Add(new Label { Text = $"Skills (pick {_class.SkillsChoose}):" });
            var skillRow = new HorizontalStackPanel { Spacing = 4 };
            foreach (var raw in _class.SkillOptions)
            {
                SkillId s;
                try { s = SkillIdExtensions.FromJson(raw); } catch { continue; }
                bool picked = _chosenSkills.Contains(s);
                skillRow.Widgets.Add(MakeButton(raw + (picked ? " ✓" : ""), picked, () =>
                {
                    if (picked) _chosenSkills.Remove(s);
                    else        _chosenSkills.Add(s);
                    RebuildUI();
                }));
            }
            root.Widgets.Add(skillRow);
        }

        // Name
        root.Widgets.Add(new Label { Text = " " });
        var nameRow = new HorizontalStackPanel { Spacing = 8 };
        nameRow.Widgets.Add(new Label { Text = "Name:", VerticalAlignment = VerticalAlignment.Center });
        var nameInput = new TextBox { Text = _name, Width = 240 };
        nameInput.TextChanged += (_, _) => _name = nameInput.Text ?? "Wanderer";
        nameRow.Widgets.Add(nameInput);
        root.Widgets.Add(nameRow);

        // Validation status
        var status = new Label { Text = "", HorizontalAlignment = HorizontalAlignment.Center };
        if (TryBuildPreview(out var error))
            status.Text = "Ready.";
        else
            status.Text = $"Cannot confirm: {error}";
        root.Widgets.Add(status);

        // Confirm + Back
        root.Widgets.Add(new Label { Text = " " });
        var btnRow = new HorizontalStackPanel { Spacing = 16, HorizontalAlignment = HorizontalAlignment.Center };
        var backBtn = new TextButton { Text = "Back", Width = 120 };
        backBtn.Click += (_, _) => _game.Screens.Pop();
        btnRow.Widgets.Add(backBtn);

        var confirmBtn = new TextButton { Text = "Confirm", Width = 200 };
        confirmBtn.Click += (_, _) => OnConfirm();
        confirmBtn.Enabled = TryBuildPreview(out _);
        btnRow.Widgets.Add(confirmBtn);
        root.Widgets.Add(btnRow);

        _desktop = new Desktop { Root = root };
    }

    private void RebuildUI() => BuildUI();

    private static TextButton MakeButton(string text, bool selected, Action onClick)
    {
        var btn = new TextButton
        {
            Text = (selected ? "→ " : "  ") + text,
            Padding = new Myra.Graphics2D.Thickness(6, 2, 6, 2),
        };
        btn.Click += (_, _) => onClick();
        return btn;
    }

    private static HorizontalStackPanel BuildSelectorRow<T>(
        IEnumerable<T> items,
        Func<T, string> label,
        Func<T, bool> isSelected,
        Action<T> onSelect)
    {
        var row = new HorizontalStackPanel { Spacing = 4 };
        foreach (var item in items)
            row.Widgets.Add(MakeButton(label(item), isSelected(item), () => onSelect(item)));
        return row;
    }

    /// <summary>Validates current selections + builds a preview character. Returns false on the first error.</summary>
    private bool TryBuildPreview(out string error)
    {
        error = "";
        if (_clade is null || _species is null || _class is null || _background is null)
        { error = "Pick clade, species, class, and background."; return false; }
        if (_chosenSkills.Count != _class.SkillsChoose)
        { error = $"Pick exactly {_class.SkillsChoose} skill(s)."; return false; }
        if (string.IsNullOrWhiteSpace(_name))
        { error = "Enter a name."; return false; }

        var b = new CharacterBuilder
        {
            Clade        = _clade,
            Species      = _species,
            ClassDef     = _class,
            Background   = _background,
            BaseAbilities = _baseAbilities,
            Name         = _name,
        };
        foreach (var s in _chosenSkills) b.ChooseSkill(s);
        return b.Validate(out error);
    }

    private void OnConfirm()
    {
        if (!TryBuildPreview(out var err)) return;
        var b = new CharacterBuilder
        {
            Clade        = _clade,
            Species      = _species,
            ClassDef     = _class,
            Background   = _background,
            BaseAbilities = _baseAbilities,
            Name         = _name,
        };
        foreach (var s in _chosenSkills) b.ChooseSkill(s);
        // Apply the class's starting kit so the new character is equipped on
        // arrival — pass the loaded items table from the resolver.
        var character = b.Build(_content.Items);

        // Pop ourselves and push worldgen with the pending character. PlayScreen
        // attaches it to the spawned PlayerActor on Initialize.
        _game.Screens.Pop();
        _game.Screens.Push(new WorldGenProgressScreen(_seed, pendingCharacter: character, pendingName: _name));
    }

    // ── Stats helpers ────────────────────────────────────────────────────

    /// <summary>
    /// Standard Array (15/14/13/12/10/8) assigned in class-priority order so
    /// the default character is functional. M3 may add manual swapping.
    /// </summary>
    private void AssignStandardArrayByClassPriority()
    {
        var values = (int[])AbilityScores.StandardArray.Clone();
        AssignByClassPriority(values);
    }

    private void RollAndAssign()
    {
        ulong msNow = (ulong)(Environment.TickCount64 - _gameStartMs);
        var rng = SeededRng.ForSubsystem(_seed, C.RNG_STAT_ROLL ^ msNow);
        int[] values = new int[6];
        for (int i = 0; i < 6; i++) values[i] = CharacterBuilder.Roll4d6DropLowest(rng);
        AssignByClassPriority(values);
    }

    private void AssignByClassPriority(int[] values)
    {
        // Sort values descending so highest goes to primary ability.
        Array.Sort(values, (a, b) => b - a);

        var primary = _class?.PrimaryAbility ?? Array.Empty<string>();
        var allAbilities = new[] { "STR", "DEX", "CON", "INT", "WIS", "CHA" };
        var order = new List<string>();
        foreach (var p in primary) order.Add(p.ToUpperInvariant());
        // After primaries, prefer CON for survivability, then physical/mental in default order.
        foreach (var a in new[] { "CON", "DEX", "STR", "WIS", "INT", "CHA" })
            if (!order.Contains(a)) order.Add(a);

        var assigned = new Dictionary<string, int>();
        for (int i = 0; i < 6; i++) assigned[order[i]] = values[i];

        _baseAbilities = new AbilityScores(
            assigned["STR"], assigned["DEX"], assigned["CON"],
            assigned["INT"], assigned["WIS"], assigned["CHA"]);
    }

    private void AutoPickSkills()
    {
        _chosenSkills.Clear();
        if (_class is null) return;
        int n = _class.SkillsChoose;
        foreach (var raw in _class.SkillOptions)
        {
            if (_chosenSkills.Count >= n) break;
            try { _chosenSkills.Add(SkillIdExtensions.FromJson(raw)); }
            catch { /* ignore unknown */ }
        }
    }

    private string FormatStats()
    {
        if (_clade is null || _species is null) return "";
        // Apply mods to base for display
        var mods = new Dictionary<AbilityId, int>();
        void Add(string raw, int v)
        {
            if (TryParseAbility(raw, out var id))
                mods[id] = (mods.TryGetValue(id, out var x) ? x : 0) + v;
        }
        foreach (var kv in _clade.AbilityMods)   Add(kv.Key, kv.Value);
        foreach (var kv in _species.AbilityMods) Add(kv.Key, kv.Value);

        var final = _baseAbilities.Plus(mods);
        return $"STR {final.STR} ({Sign(AbilityScores.Mod(final.STR))})  " +
               $"DEX {final.DEX} ({Sign(AbilityScores.Mod(final.DEX))})  " +
               $"CON {final.CON} ({Sign(AbilityScores.Mod(final.CON))})\n" +
               $"INT {final.INT} ({Sign(AbilityScores.Mod(final.INT))})  " +
               $"WIS {final.WIS} ({Sign(AbilityScores.Mod(final.WIS))})  " +
               $"CHA {final.CHA} ({Sign(AbilityScores.Mod(final.CHA))})";
    }

    private static string Sign(int n) => n >= 0 ? $"+{n}" : n.ToString();

    private static bool TryParseAbility(string raw, out AbilityId id)
    {
        switch (raw.ToUpperInvariant())
        {
            case "STR": id = AbilityId.STR; return true;
            case "DEX": id = AbilityId.DEX; return true;
            case "CON": id = AbilityId.CON; return true;
            case "INT": id = AbilityId.INT; return true;
            case "WIS": id = AbilityId.WIS; return true;
            case "CHA": id = AbilityId.CHA; return true;
            default:    id = AbilityId.STR; return false;
        }
    }

    public void Update(GameTime gt)
    {
        if (Keyboard.GetState().IsKeyDown(Keys.Escape)) _game.Screens.Pop();
    }

    public void Draw(GameTime gt, SpriteBatch sb)
    {
        _game.GraphicsDevice.Clear(new Color(15, 15, 25));
        _desktop.Render();
    }

    public void Deactivate() { }
    public void Reactivate() { }
}
