using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Themes;
using HellionChat.Util;

namespace HellionChat.Ui;

// Theme-driven ImGui style override. PushGlobal is pushed once per frame
// in Plugin.Draw and drives every Hellion-rendered window.
internal static class HellionStyle
{
    // Local color stack for the active theme. Use inside a
    // `using var _ = HellionStyle.Push(theme);` block.
    internal static IDisposable Push(Theme theme)
    {
        var a = theme.AbgrCache;
        var stack = new StackHandle();
        stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
        stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
        stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);
        stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
        stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
        stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);
        stack.PushColorAbgr(ImGuiCol.Border, a.Border);
        stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
        stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
        stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);
        stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
        stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
        stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);
        return stack;
    }

    // Global color and style stack pushed once per frame.
    // windowOpacity: window background alpha (0.5-1.0).
    internal static IDisposable PushGlobal(
        Theme theme,
        ThemeRegistry registry,
        float windowOpacity = 1.0f
    )
    {
        var c = theme.Colors;
        var l = theme.Layout;

        // Crossfade: PM-1 reads a lerped snapshot during the 300ms window
        // following a Switch (TryGetActiveCrossfade returns false outside
        // the window or while ReduceMotion is on). Only the ABGR-slot path
        // crossfades -- WindowBg/ChildBg RGBA stays bound to the user's
        // per-window opacity override and must not fade. See
        // feedback_dalamud_pinning_override.
        ThemeAbgrCache a;
        if (!Plugin.Config.ReduceMotion && registry.TryGetActiveCrossfade(out var lerped))
        {
            a = lerped;
        }
        else
        {
            a = theme.AbgrCache;
        }

        var stack = new StackHandle();

        var alphaByte = (uint)Math.Clamp((int)(windowOpacity * 255f), 0x55, 0xFF);
        var windowBgWithAlpha = (c.WindowBg & 0xFFFFFF00u) | alphaByte;

        // ChildBg alpha resolution lives in HellionStyleHelpers so the
        // threshold logic can be covered by a pure-helper test in the
        // build suite.
        var childBgWithAlpha = HellionStyleHelpers.ResolveChildBgAlpha(c.ChildBg, windowOpacity);

        // Layout
        stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
        stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding);
        stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding);
        stack.PushStyleVar(ImGuiStyleVar.FrameRounding, l.FrameRounding);
        stack.PushStyleVar(ImGuiStyleVar.GrabRounding, l.GrabRounding);
        stack.PushStyleVar(ImGuiStyleVar.TabRounding, l.TabRounding);
        stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, l.ScrollbarRounding);
        stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize);
        stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize);

        // Surfaces — WindowBg/ChildBg use opacity-modulated values (RGBA path);
        // everything else reads from the pre-computed ABGR cache.
        stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
        stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
        stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg);
        stack.PushColorAbgr(ImGuiCol.Border, a.Border);
        stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u);

        // Frames
        stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg);
        stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover);
        stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface);

        // Title bars
        stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg);
        stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity);
        stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg);

        // Buttons
        stack.PushColorAbgr(ImGuiCol.Button, a.Primary);
        stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight);
        stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark);

        // Headers / selectables
        stack.PushColorAbgr(ImGuiCol.Header, a.Surface);
        stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover);
        stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity);

        // Tabs
        stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg);
        stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight);
        stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity);
        stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg);
        stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark);

        // Scrollbar
        stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg);
        stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface);
        stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight);
        stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent);

        // Resize grip
        stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg);
        stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight);
        stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent);

        // Check mark + slider grab
        stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary);
        stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary);
        stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight);

        // Separator
        stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
        stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
        stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);

        return stack;
    }

    private sealed class StackHandle : IDisposable
    {
        private readonly List<IDisposable> _items = new(64);

        internal void PushColor(ImGuiCol slot, uint rgba) =>
            _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));

        internal void PushColorAbgr(ImGuiCol slot, uint abgr) =>
            _items.Add(ImRaii.PushColor(slot, abgr));

        internal void PushStyleVar(ImGuiStyleVar var, float value) =>
            _items.Add(ImRaii.PushStyle(var, value));

        public void Dispose()
        {
            for (var i = _items.Count - 1; i >= 0; i--)
                _items[i].Dispose();
            _items.Clear();
        }
    }
}
