using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;
using Dalamud.Interface.Utility.Raii;

namespace HellionChat.Ui;

// Popup picker for chat-input symbol insertion. Two tabs:
//   PUA  — Dalamud's SeIconChar enum (161 server-safe FFXIV glyphs)
//   BMP  — server-verified Unicode symbols (whitelist built 2026-05-16)
//
// Render-only — the Settings-Guard for showing the trigger button lives on
// the caller side (ChatLogWindow). Recent-Used is session state by design.
internal sealed class SymbolPicker
{
    private const string PopupId = "HellionSymbolPicker";
    private const int RecentCapacity = 16;

    private string _search = string.Empty;
    private readonly List<uint> _recentUsed = new(capacity: RecentCapacity);

    // FFXIV server-safe BMP symbols, verified 2026-05-16 via /echo + /say.
    // Filtered ranges: U+2694-26C4 (Misc Symbols Extended), U+2700+ (Dingbats
    // Extended), diagonal arrows, U+2153+ fractions, chess pieces.
    // Full probe log: Cycles/v1.4.10 BMP-Whitelist Notes.md.
    private static readonly (uint Codepoint, string Name)[] BmpWhitelist = new[]
    {
        (0x00A1u, "Inverted Exclamation"),
        (0x00A2u, "Cent Sign"),
        (0x00A3u, "Pound Sign"),
        (0x00A4u, "Currency Sign"),
        (0x00A5u, "Yen Sign"),
        (0x00A7u, "Section Sign"),
        (0x00A9u, "Copyright Sign"),
        (0x00ABu, "Left Angle Quote"),
        (0x00AEu, "Registered Sign"),
        (0x00B0u, "Degree Sign"),
        (0x00B1u, "Plus-Minus Sign"),
        (0x00B6u, "Pilcrow Sign"),
        (0x00BBu, "Right Angle Quote"),
        (0x00BCu, "One Quarter"),
        (0x00BDu, "One Half"),
        (0x00BEu, "Three Quarters"),
        (0x00BFu, "Inverted Question"),
        (0x00D7u, "Multiplication Sign"),
        (0x00F7u, "Division Sign"),
        (0x0393u, "Greek Capital Gamma"),
        (0x0394u, "Greek Capital Delta"),
        (0x0398u, "Greek Capital Theta"),
        (0x039Bu, "Greek Capital Lambda"),
        (0x039Eu, "Greek Capital Xi"),
        (0x03A0u, "Greek Capital Pi"),
        (0x03A3u, "Greek Capital Sigma"),
        (0x03A6u, "Greek Capital Phi"),
        (0x03A8u, "Greek Capital Psi"),
        (0x03A9u, "Greek Capital Omega"),
        (0x03B1u, "Greek Small Alpha"),
        (0x03B2u, "Greek Small Beta"),
        (0x03B3u, "Greek Small Gamma"),
        (0x03B4u, "Greek Small Delta"),
        (0x03B5u, "Greek Small Epsilon"),
        (0x03B6u, "Greek Small Zeta"),
        (0x03B7u, "Greek Small Eta"),
        (0x03B8u, "Greek Small Theta"),
        (0x03B9u, "Greek Small Iota"),
        (0x03BAu, "Greek Small Kappa"),
        (0x03BBu, "Greek Small Lambda"),
        (0x03BCu, "Greek Small Mu"),
        (0x03BDu, "Greek Small Nu"),
        (0x03BEu, "Greek Small Xi"),
        (0x03BFu, "Greek Small Omicron"),
        (0x03C0u, "Greek Small Pi"),
        (0x03C1u, "Greek Small Rho"),
        (0x03C3u, "Greek Small Sigma"),
        (0x03C4u, "Greek Small Tau"),
        (0x03C5u, "Greek Small Upsilon"),
        (0x03C6u, "Greek Small Phi"),
        (0x03C7u, "Greek Small Chi"),
        (0x03C8u, "Greek Small Psi"),
        (0x03C9u, "Greek Small Omega"),
        (0x2013u, "En Dash"),
        (0x2014u, "Em Dash"),
        (0x2020u, "Dagger"),
        (0x2021u, "Double Dagger"),
        (0x2026u, "Horizontal Ellipsis"),
        (0x203Bu, "Reference Mark"),
        (0x20ACu, "Euro Sign"),
        (0x2122u, "Trade Mark Sign"),
        (0x2190u, "Leftwards Arrow"),
        (0x2191u, "Upwards Arrow"),
        (0x2192u, "Rightwards Arrow"),
        (0x2193u, "Downwards Arrow"),
        (0x21D2u, "Rightwards Double Arrow"),
        (0x21D4u, "Left Right Double Arrow"),
        (0x2202u, "Partial Differential"),
        (0x2207u, "Nabla"),
        (0x2211u, "Summation"),
        (0x221Au, "Square Root"),
        (0x221Eu, "Infinity"),
        (0x222Bu, "Integral"),
        (0x2260u, "Not Equal To"),
        (0x25A0u, "Black Square"),
        (0x25A1u, "White Square"),
        (0x25B2u, "Black Up Triangle"),
        (0x25B3u, "White Up Triangle"),
        (0x25BCu, "Black Down Triangle"),
        (0x25C6u, "Black Diamond"),
        (0x25C7u, "White Diamond"),
        (0x25CBu, "White Circle"),
        (0x25CFu, "Black Circle"),
        (0x2600u, "Black Sun With Rays"),
        (0x2601u, "Cloud"),
        (0x2602u, "Umbrella"),
        (0x2603u, "Snowman"),
        (0x2605u, "Black Star"),
        (0x2606u, "White Star"),
        (0x2640u, "Female Sign"),
        (0x2642u, "Male Sign"),
        (0x2660u, "Black Spade Suit"),
        (0x2661u, "White Heart Suit"),
        (0x2663u, "Black Club Suit"),
        (0x2665u, "Black Heart Suit"),
        (0x266Au, "Eighth Note"),
        (0x2713u, "Check Mark"),
    };

    public void OpenPopup() => ImGui.OpenPopup(PopupId);

    // Returns the inserted codepoint as a string fragment if the user clicked
    // one this frame, or null otherwise. Caller splices the fragment into the
    // chat-input buffer at the current cursor position.
    public string? DrawAndConsume()
    {
        // ImRaii.Popup auto-disposes EndPopup, same idiom as other popups in
        // ChatLogWindow.
        using var popup = ImRaii.Popup(PopupId);
        if (!popup)
            return null;

        string? inserted = null;

        // Recent-Used-Row sits above the tabs so both PUA and BMP picks share
        // one fast-access strip. Session-only by design (see TrackRecent).
        if (_recentUsed.Count > 0)
        {
            ImGui.TextDisabled("Recent");
            ImGui.SameLine();
            foreach (var codepoint in _recentUsed)
            {
                var glyph = char.ConvertFromUtf32((int)codepoint);
                if (
                    ImGui.Selectable(
                        glyph,
                        false,
                        ImGuiSelectableFlags.DontClosePopups,
                        new Vector2(20, 20)
                    )
                )
                {
                    inserted = glyph;
                }
                ImGui.SameLine();
            }
            ImGui.NewLine();
            ImGui.Separator();
        }

        using (var tabs = ImRaii.TabBar("##symbolpicker-tabs"))
        {
            if (tabs)
            {
                inserted = DrawPuaTab() ?? inserted;
                inserted = DrawBmpTab() ?? inserted;
            }
        }

        if (inserted is not null)
            TrackRecent(inserted);

        return inserted;
    }

    private string? DrawPuaTab()
    {
        using var tab = ImRaii.TabItem("FFXIV Icons");
        if (!tab)
            return null;

        ImGui.InputTextWithHint(
            "##pua-search",
            "Search by name (e.g. HighQuality)",
            ref _search,
            64
        );

        string? inserted = null;

        if (ImGui.BeginChild("##pua-grid", new Vector2(0, 280), false))
        {
            var query = _search;
            foreach (var icon in Enum.GetValues<SeIconChar>())
            {
                var label = icon.ToString();
                if (
                    query.Length > 0
                    && label.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0
                )
                {
                    continue;
                }

                // ToIconString gives the single-codepoint glyph; tooltip
                // carries the enum name for discoverability.
                if (
                    ImGui.Selectable(
                        icon.ToIconString(),
                        false,
                        ImGuiSelectableFlags.DontClosePopups,
                        new Vector2(24, 24)
                    )
                )
                {
                    inserted = icon.ToIconString();
                }
                if (ImGui.IsItemHovered())
                    ImGui.SetTooltip(label);

                // Manually-wrapping pattern from imgui_demo.cpp;
                // GetWindowContentRegionMax obsolete since ImGui 1.92, use
                // GetContentRegionAvail (see ChatLogWindow.cs:840).
                var style = ImGui.GetStyle();
                var lastItemX2 = ImGui.GetItemRectMax().X;
                var availableRightX =
                    ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
                if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
                    ImGui.SameLine();
            }
        }
        ImGui.EndChild();

        return inserted;
    }

    private string? DrawBmpTab()
    {
        using var tab = ImRaii.TabItem("Symbols");
        if (!tab)
            return null;

        ImGui.InputTextWithHint("##bmp-search", "Search by name (e.g. Heart)", ref _search, 64);

        string? inserted = null;

        if (ImGui.BeginChild("##bmp-grid", new Vector2(0, 280), false))
        {
            var query = _search;
            foreach (var (codepoint, name) in BmpWhitelist)
            {
                if (query.Length > 0 && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
                {
                    continue;
                }

                var glyph = char.ConvertFromUtf32((int)codepoint);
                if (
                    ImGui.Selectable(
                        glyph,
                        false,
                        ImGuiSelectableFlags.DontClosePopups,
                        new Vector2(24, 24)
                    )
                )
                {
                    inserted = glyph;
                }
                if (ImGui.IsItemHovered())
                    ImGui.SetTooltip(name);

                // Same manually-wrapping pattern as DrawPuaTab — modern API
                // since GetWindowContentRegionMax was deprecated in ImGui 1.92.
                var style = ImGui.GetStyle();
                var lastItemX2 = ImGui.GetItemRectMax().X;
                var availableRightX =
                    ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
                if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
                    ImGui.SameLine();
            }
        }
        ImGui.EndChild();

        return inserted;
    }

    private void TrackRecent(string fragment)
    {
        if (string.IsNullOrEmpty(fragment) || fragment.Length > 4)
            return;

        var codepoint = (uint)char.ConvertToUtf32(fragment, 0);

        // Move-to-front so the head stays the freshest pick.
        _recentUsed.RemoveAll(c => c == codepoint);
        _recentUsed.Insert(0, codepoint);

        if (_recentUsed.Count > RecentCapacity)
            _recentUsed.RemoveAt(_recentUsed.Count - 1);
    }
}
