using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Interface.GeneralElements;
using Dalamud.Bindings.ImGui;
using Lumina.Excel;
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Component.GUI;

namespace HSUI.Helpers
{
    public enum GradientDirection
    {
        None,
        Right,
        Left,
        Up,
        Down,
        CenteredHorizonal
    }

    public static class DrawHelper
    {
        private static uint[] ColorArray(PluginConfigColor color, GradientDirection gradientDirection)
        {
            return gradientDirection switch
            {
                GradientDirection.None => new[] { color.Base, color.Base, color.Base, color.Base },
                GradientDirection.Right => new[] { color.TopGradient, color.BottomGradient, color.BottomGradient, color.TopGradient },
                GradientDirection.Left => new[] { color.BottomGradient, color.TopGradient, color.TopGradient, color.BottomGradient },
                GradientDirection.Up => new[] { color.BottomGradient, color.BottomGradient, color.TopGradient, color.TopGradient },
                _ => new[] { color.TopGradient, color.TopGradient, color.BottomGradient, color.BottomGradient }
            };
        }

        private static Vector2 GetBarTextureUV1Vector(Vector2 size, int textureWidth, int textureHeight, BarTextureDrawMode drawMode)
        {
            if (drawMode == BarTextureDrawMode.Stretch) { return new Vector2(1); }

            float x = drawMode == BarTextureDrawMode.RepeatVertical ? 1 : (float)size.X / textureWidth;
            float y = drawMode == BarTextureDrawMode.RepeatHorizontal ? 1 : (float)size.Y / textureHeight;

            return new Vector2(x, y);
        }

        public static void DrawBarTexture(Vector2 position, Vector2 size, PluginConfigColor color, string? name, BarTextureDrawMode drawMode, ImDrawListPtr drawList)
        {
            IDalamudTextureWrap? texture = BarTexturesManager.Instance?.GetBarTexture(name);
            if (texture == null)
            {
                DrawGradientFilledRect(position, size, color, drawList);
                return;
            }

            Vector2 uv0 = new Vector2(0);
            Vector2 uv1 = GetBarTextureUV1Vector(size, texture.Width, texture.Height, drawMode);

            drawList.AddImage(texture.Handle, position, position + size, uv0, uv1, color.Base);
        }

        public static void DrawGradientFilledRect(Vector2 position, Vector2 size, PluginConfigColor color, ImDrawListPtr drawList)
        {
            GradientDirection gradientDirection = ConfigurationManager.Instance.GradientDirection;
            DrawGradientFilledRect(position, size, color, drawList, gradientDirection);
        }

        public static void DrawGradientFilledRect(Vector2 position, Vector2 size, PluginConfigColor color, ImDrawListPtr drawList, GradientDirection gradientDirection = GradientDirection.Down)
        {
            uint[]? colorArray = ColorArray(color, gradientDirection);

            if (gradientDirection == GradientDirection.CenteredHorizonal)
            {
                Vector2 halfSize = new(size.X, size.Y / 2f);
                drawList.AddRectFilledMultiColor(
                    position, position + halfSize,
                    colorArray[0], colorArray[1], colorArray[2], colorArray[3]
                );

                Vector2 pos = position + new Vector2(0, halfSize.Y);
                drawList.AddRectFilledMultiColor(
                    pos, pos + halfSize,
                    colorArray[3], colorArray[2], colorArray[1], colorArray[0]
                );
            }
            else
            {
                drawList.AddRectFilledMultiColor(
                    position, position + size,
                    colorArray[0], colorArray[1], colorArray[2], colorArray[3]
                );
            }
        }

        public static void DrawOutlinedText(string text, Vector2 pos, ImDrawListPtr drawList, int thickness = 1)
        {
            DrawOutlinedText(text, pos, 0xFFFFFFFF, 0xFF000000, drawList, thickness);
        }

        public static void DrawOutlinedText(string text, Vector2 pos, uint color, uint outlineColor, ImDrawListPtr drawList, int thickness = 1)
        {
            // outline
            for (int i = 1; i < thickness + 1; i++)
            {
                drawList.AddText(new Vector2(pos.X - i, pos.Y + i), outlineColor, text);
                drawList.AddText(new Vector2(pos.X, pos.Y + i), outlineColor, text);
                drawList.AddText(new Vector2(pos.X + i, pos.Y + i), outlineColor, text);
                drawList.AddText(new Vector2(pos.X - i, pos.Y), outlineColor, text);
                drawList.AddText(new Vector2(pos.X + i, pos.Y), outlineColor, text);
                drawList.AddText(new Vector2(pos.X - i, pos.Y - i), outlineColor, text);
                drawList.AddText(new Vector2(pos.X, pos.Y - i), outlineColor, text);
                drawList.AddText(new Vector2(pos.X + i, pos.Y - i), outlineColor, text);
            }

            // text
            drawList.AddText(new Vector2(pos.X, pos.Y), color, text);
        }

        public static void DrawShadowText(string text, Vector2 pos, uint color, uint shadowColor, ImDrawListPtr drawList, int offset = 1, int thickness = 1)
        {
            // TODO: Add parameter to allow to choose a direction

            // Shadow
            for (int i = 0; i < thickness; i++)
            {
                drawList.AddText(new Vector2(pos.X + i + offset, pos.Y + i + offset), shadowColor, text);
            }

            // Text
            drawList.AddText(new Vector2(pos.X, pos.Y), color, text);
        }

        public static void DrawIcon<T>(dynamic row, Vector2 position, Vector2 size, bool drawBorder, bool cropIcon, int stackCount = 1) where T : struct, IExcelRow<T>
        {
            IDalamudTextureWrap texture = TexturesHelper.GetTexture<T>(row, (uint)Math.Max(0, stackCount - 1));
            if (texture == null) { return; }

            (Vector2 uv0, Vector2 uv1) = GetTexCoordinates(texture, size, cropIcon);

            ImGui.SetCursorPos(position);
            ImGui.Image(texture.Handle, size, uv0, uv1);

            if (drawBorder)
            {
                ImDrawListPtr drawList = ImGui.GetWindowDrawList();
                drawList.AddRect(position, position + size, 0xFF000000);
            }
        }

        public static void DrawIcon<T>(ImDrawListPtr drawList, dynamic row, Vector2 position, Vector2 size, bool drawBorder, bool cropIcon, int stackCount = 1) where T : struct, IExcelRow<T>
        {
            IDalamudTextureWrap texture = TexturesHelper.GetTexture<T>(row, (uint)Math.Max(0, stackCount - 1));
            if (texture == null) { return; }

            (Vector2 uv0, Vector2 uv1) = GetTexCoordinates(texture, size, cropIcon);

            drawList.AddImage(texture.Handle, position, position + size, uv0, uv1);

            if (drawBorder)
            {
                drawList.AddRect(position, position + size, 0xFF000000);
            }
        }

        public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, ImDrawListPtr drawList)
        {
            DrawIcon(iconId, position, size, drawBorder, 0xFFFFFFFF, drawList);
        }

        public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, float alpha, ImDrawListPtr drawList)
        {
            uint a = (uint)(alpha * 255);
            uint color = 0xFFFFFF + (a << 24);
            DrawIcon(iconId, position, size, drawBorder, color, drawList);
        }


        /// <summary>Draw a rect outline with optional glow and line style (solid, dashed, dotted).</summary>
        public static void DrawComboHighlightRect(ImDrawListPtr drawList, Vector2 min, Vector2 max, uint color, float thickness, bool showGlow, int lineStyle)
        {
            const float inset = 2f;
            Vector2 innerMin = min + new Vector2(inset, inset);
            Vector2 innerMax = max - new Vector2(inset, inset);
            if (innerMax.X <= innerMin.X || innerMax.Y <= innerMin.Y) return;

            if (showGlow)
            {
                for (int g = 5; g >= 1; g--)
                {
                    float o = g;
                    uint alpha = (uint)(byte)(70 * (6 - g)) << 24;
                    drawList.AddRect(innerMin - new Vector2(o, o), innerMax + new Vector2(o, o),
                        alpha | (color & 0x00FFFFFF), 0, ImDrawFlags.None, 2f);
                }
            }

            if (lineStyle == 0) // Solid
            {
                drawList.AddRect(innerMin, innerMax, color, 0, ImDrawFlags.None, thickness);
                return;
            }

            // Dashed or Dotted: draw each side as segments
            float dashLen = lineStyle == 1 ? 4f : 2f;
            float gapLen = lineStyle == 1 ? 3f : 3f;
            float step = dashLen + gapLen;

            void DrawSegmentedLine(Vector2 a, Vector2 b)
            {
                Vector2 d = b - a;
                float len = d.Length();
                if (len < 0.001f) return;
                Vector2 u = d / len;
                float t = 0;
                while (t < len)
                {
                    float tEnd = Math.Min(t + dashLen, len);
                    drawList.AddLine(a + u * t, a + u * tEnd, color, thickness);
                    t += step;
                }
            }

            DrawSegmentedLine(innerMin, new Vector2(innerMax.X, innerMin.Y)); // top
            DrawSegmentedLine(new Vector2(innerMax.X, innerMin.Y), innerMax); // right
            DrawSegmentedLine(innerMax, new Vector2(innerMin.X, innerMax.Y)); // bottom
            DrawSegmentedLine(new Vector2(innerMin.X, innerMax.Y), innerMin); // left
        }

        public static void DrawIcon(uint iconId, Vector2 position, Vector2 size, bool drawBorder, uint color, ImDrawListPtr drawList)
        {
            IDalamudTextureWrap? texture = TexturesHelper.GetTextureFromIconId(iconId);
            if (texture == null) { return; }

            drawList.AddImage(texture.Handle, position, position + size, Vector2.Zero, Vector2.One, color);

            if (drawBorder)
            {
                drawList.AddRect(position, position + size, 0xFF000000);
            }
        }

        public static (Vector2, Vector2) GetTexCoordinates(IDalamudTextureWrap texture, Vector2 size, bool cropIcon = true)
        {
            if (texture == null)
            {
                return (Vector2.Zero, Vector2.Zero);
            }

            // Status = 24x32, show from 2,7 until 22,26
            //show from 0,0 until 24,32 for uncropped status icon

            float uv0x = cropIcon ? 4f : 1f;
            float uv0y = cropIcon ? 14f : 1f;

            float uv1x = cropIcon ? 4f : 1f;
            float uv1y = cropIcon ? 12f : 1f;

            Vector2 uv0 = new(uv0x / texture.Width, uv0y / texture.Height);
            Vector2 uv1 = new(1f - uv1x / texture.Width, 1f - uv1y / texture.Height);

            return (uv0, uv1);
        }

        public static void DrawIconCooldown(Vector2 position, Vector2 size, float elapsed, float total, ImDrawListPtr drawList)
        {
            // completion = how much of icon to grey out (remaining cooldown). Starts full, shrinks as it cools down.
            float completion = total > 0 ? 1f - (elapsed / total) : 0f;
            int segments = (int)Math.Ceiling(completion * 4);

            Vector2 center = position + size / 2;

            //Define vertices for top, left, bottom, and right points relative to the center.
            Vector2[] vertices =
            [
                center with {Y = center.Y - size.Y}, // Top
                center with {X = center.X - size.X}, // Left
                center with {Y = center.Y + size.Y}, // Bottom
                center with {X = center.X + size.X}  // Right
            ];

            ImGui.PushClipRect(position, position + size, false);
            for (int i = 0; i < segments; i++)
            {
                Vector2 v2 = vertices[i % 4];
                Vector2 v3 = vertices[(i + 1) % 4];


                if (i == segments - 1)
                {   // If drawing the last segment, adjust the second vertex based on the cooldown.
                    float angle = 2 * MathF.PI * (1 - completion);
                    float cos = MathF.Cos(angle);
                    float sin = MathF.Sin(angle);

                    v3 = center + Vector2.Multiply(new Vector2(sin, -cos), size);
                }

                drawList.AddTriangleFilled(center, v3, v2, 0xCC000000);
            }
            ImGui.PopClipRect();
        }

        public static void DrawOvershield(float shield, Vector2 cursorPos, Vector2 barSize, float height, bool useRatioForHeight, PluginConfigColor color, ImDrawListPtr drawList)
        {
            if (shield == 0) { return; }

            float h = useRatioForHeight ? barSize.Y / 100 * height : height;

            DrawGradientFilledRect(cursorPos, new Vector2(Math.Max(1, barSize.X * shield), h), color, drawList);
        }

        public static void DrawShield(float shield, float hp, Vector2 cursorPos, Vector2 barSize, float height, bool useRatioForHeight, PluginConfigColor color, ImDrawListPtr drawList)
        {
            if (shield == 0) { return; }

            // on full hp just draw overshield
            if (hp == 1)
            {
                DrawOvershield(shield, cursorPos, barSize, height, useRatioForHeight, color, drawList);
                return;
            }

            // hp portion
            float h = useRatioForHeight ? barSize.Y / 100 * Math.Min(100, height) : height;
            float missingHPRatio = 1 - hp;
            float s = Math.Min(shield, missingHPRatio);
            Vector2 shieldStartPos = cursorPos + new Vector2(Math.Max(1, barSize.X * hp), 0);
            DrawGradientFilledRect(shieldStartPos, new Vector2(Math.Max(1, barSize.X * s), barSize.Y), color, drawList);

            // overshield
            shield -= s;
            if (shield <= 0) { return; }

            DrawGradientFilledRect(cursorPos, new Vector2(Math.Max(1, barSize.X * shield), h), color, drawList);
        }

        public static void DrawInWindow(string name, Vector2 pos, Vector2 size, bool needsInput, Action<ImDrawListPtr> drawAction)
        {
            const ImGuiWindowFlags windowFlags = ImGuiWindowFlags.NoTitleBar |
                                                 ImGuiWindowFlags.NoScrollbar |
                                                 ImGuiWindowFlags.NoBackground |
                                                 ImGuiWindowFlags.NoMove |
                                                 ImGuiWindowFlags.NoResize;

            bool inputs = InputsHelper.Instance?.IsProxyEnabled == true ? false : needsInput;

            DrawInWindow(name, pos, size, inputs, false, windowFlags, drawAction);
        }

        public static void DrawInWindow(
            string name,
            Vector2 pos,
            Vector2 size,
            bool needsInput,
            bool needsWindow,
            ImGuiWindowFlags windowFlags,
            Action<ImDrawListPtr> drawAction)
        {

            if (!ClipRectsHelper.Instance.Enabled || ClipRectsHelper.Instance.Mode == WindowClippingMode.Performance)
            {
                drawAction(ImGui.GetWindowDrawList());
                return;
            }

            windowFlags |= ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBringToFrontOnFocus;

            if (!needsInput)
            {
                windowFlags |= ImGuiWindowFlags.NoInputs;
            }

            ClipRect? clipRect = ClipRectsHelper.Instance.GetClipRectForArea(pos, size);

            // no clipping needed
            if (!ClipRectsHelper.Instance.Enabled || !clipRect.HasValue)
            {
                ImDrawListPtr drawList = ImGui.GetWindowDrawList();

                if (!needsInput && !needsWindow)
                {
                    drawAction(drawList);
                    return;
                }

                ImGui.SetNextWindowPos(pos);
                ImGui.SetNextWindowSize(size);

                bool begin = ImGui.Begin(name, windowFlags);
                if (!begin)
                {
                    ImGui.End();
                    return;
                }

                drawAction(drawList);

                ImGui.End();
            }

            // clip around game's window
            else
            {
                // hide instead of clip?
                if (ClipRectsHelper.Instance.Mode == WindowClippingMode.Hide) { return; }

                ImGuiWindowFlags flags = windowFlags;
                if (needsInput && clipRect.Value.Contains(ImGui.GetMousePos()))
                {
                    flags |= ImGuiWindowFlags.NoInputs;
                }

                ClipRect[] invertedClipRects = ClipRectsHelper.GetInvertedClipRects(clipRect.Value);
                for (int i = 0; i < invertedClipRects.Length; i++)
                {
                    ImGui.SetNextWindowPos(pos);
                    ImGui.SetNextWindowSize(size);
                    ImGuiHelpers.ForceNextWindowMainViewport();

                    bool begin = ImGui.Begin(name + "_" + i, flags);
                    if (!begin)
                    {
                        ImGui.End();
                        continue;
                    }

                    ImGui.PushClipRect(invertedClipRects[i].Min, invertedClipRects[i].Max, false);
                    drawAction(ImGui.GetWindowDrawList());
                    ImGui.PopClipRect();

                    ImGui.End();
                }
            }
        }
    }
}
