using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using HSUI.Config;
using HSUI.Config.Attributes;
using HSUI.Interface.GeneralElements;
using Dalamud.Bindings.ImGui;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;

namespace HSUI.Helpers
{
    /// <summary>Colored segment for game-style tooltip formatting.</summary>
    public readonly struct TooltipSegment
    {
        public readonly string Text;
        public readonly Vector4 Color;

        public TooltipSegment(string text, Vector4 color)
        {
            Text = text ?? "";
            Color = color;
        }
    }

    /// <summary>When showing an ID in the tooltip title, use Action for action IDs or Status for status effect IDs.</summary>
    public enum TooltipIdKind
    {
        None = 0,
        Action = 1,
        Status = 2,
    }

    public class TooltipsHelper : IDisposable
    {
        #region Singleton
        private TooltipsHelper()
        {
        }

        public static void Initialize() { Instance = new TooltipsHelper(); }

        public static TooltipsHelper Instance { get; private set; } = null!;

        ~TooltipsHelper()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            Instance = null!;
        }
        #endregion

        private float MaxWidth => Math.Max(160, _config.TooltipMaxWidth * ImGuiHelpers.GlobalScale * _config.TooltipScale);
        private float Margin => Math.Max(4, 5 * ImGuiHelpers.GlobalScale * _config.TooltipScale);

        private TooltipsConfig _config => ConfigurationManager.Instance.GetConfigObject<TooltipsConfig>();

        private string? _currentTooltipText = null;
        private Vector2 _textSize;
        private string? _currentTooltipTitle = null;
        private Vector2 _titleSize;
        private string? _previousRawText = null;
        private uint? _currentIconId = null;
        private List<TooltipSegment>? _formattedSegments = null;

        private Vector2 _position;
        private Vector2 _size;

        private bool _dataIsValid = false;

        private const float IconSize = 24f;

        public void ShowTooltipOnCursor(string text, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None, List<TooltipSegment>? formattedText = null)
        {
            ShowTooltip(text, ImGui.GetMousePos(), title, id, name, iconId, idKind, formattedText);
        }

        public void ShowTooltip(string text, Vector2 position, string? title = null, uint id = 0, string name = "", uint? iconId = null, TooltipIdKind idKind = TooltipIdKind.None, List<TooltipSegment>? formattedText = null)
        {
            if (text == null)
            {
                if (_config.DebugTooltips)
                    Plugin.Logger.Information("[HSUI Tooltip DBG] ShowTooltip skipped: text is null");
                return;
            }

            // remove styling tags from text
            if (_previousRawText != text)
            {
                _currentTooltipText = text;
                _previousRawText = text;
            }

            _currentIconId = iconId;
            _formattedSegments = formattedText;

            // calcualte title size
            _titleSize = Vector2.Zero;
            if (title != null)
            {
                _currentTooltipTitle = title;

                if (_config.ShowSourceName && name.Length > 0)
                {
                    _currentTooltipTitle += $" ({name})";
                }

                bool showId = id != 0 && (
                    (idKind == TooltipIdKind.Action && _config.ShowActionIDs) ||
                    (idKind == TooltipIdKind.Status && _config.ShowStatusIDs) ||
                    (idKind == TooltipIdKind.None && _config.ShowStatusIDs));
                if (showId)
                {
                    _currentTooltipTitle += " (ID: " + id + ")";
                }

                string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
                using (FontsManager.Instance.PushFont(fontId))
                {
                    _titleSize = ImGui.CalcTextSize(_currentTooltipTitle, false, MaxWidth);
                    _titleSize.Y += Margin;
                }
            }

            // calculate text size (build combined text from segments for sizing)
            string fontIdForText = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
            using (FontsManager.Instance.PushFont(fontIdForText))
            {
                float lineHeight = ImGui.GetTextLineHeight();
                if (_formattedSegments != null && _formattedSegments.Count > 0)
                {
                    var sb = new StringBuilder();
                    int newlineCount = 0;
                    foreach (var seg in _formattedSegments)
                    {
                        sb.Append(seg.Text);
                        if (seg.Text == "\n") newlineCount++;
                    }
                    _textSize = ImGui.CalcTextSize(sb.ToString(), false, MaxWidth);
                    // Account for Dummy spacing between sections (each \n adds compactSpacing)
                    float compactSpacing = Math.Max(2, lineHeight * _config.TooltipLineSpacing);
                    _textSize.Y += newlineCount * compactSpacing;
                }
                else
                {
                    _textSize = ImGui.CalcTextSize(_currentTooltipText, false, MaxWidth);
                }
            }

            float fontScale = Math.Max(0.7f, _config.TooltipFontScale);
            float contentWidth = Math.Max(_titleSize.X, _textSize.X) * fontScale;
            float contentHeight = (_titleSize.Y + _textSize.Y) * fontScale;
            float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale * _config.TooltipIconScale;
            float iconGap = _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled + Margin : 0;
            // Add buffer to prevent clipping (font/scale changes, rounding, padding)
            float heightBuffer = _config.TooltipHeightBuffer;
            float minContentHeight = contentHeight + heightBuffer;
            _size = new Vector2(
                contentWidth + iconGap + Margin * 2,
                Math.Max(minContentHeight, _currentIconId.HasValue && _currentIconId.Value > 0 ? iconSizeScaled + Margin * 2 : 0) + Margin * 2);

            // position tooltip using the given coordinates as bottom center
            position.X = position.X - _size.X / 2f;
            position.Y = position.Y - _size.Y;

            // correct tooltips off screen
            _position = ConstrainPosition(position, _size);

            _dataIsValid = true;
            if (_config.DebugTooltips)
                Plugin.Logger.Information($"[HSUI Tooltip DBG] ShowTooltip: title='{title}' textLen={text?.Length ?? 0} textPreview='{(text != null && text.Length > 80 ? text[..80] + "..." : text ?? "")}' pos=({_position.X:F0},{_position.Y:F0})");
        }

        private WorldObjectTooltipConfig? _worldTooltipConfig;

        /// <summary>
        /// Shows a tooltip for the game object the mouse is hovering over in the 3D world.
        /// Call this each frame before Draw().
        /// </summary>
        public void ShowWorldObjectTooltip()
        {
            try
            {
                _worldTooltipConfig ??= ConfigurationManager.Instance.GetConfigObject<WorldObjectTooltipConfig>();
                if (_worldTooltipConfig == null || !_worldTooltipConfig.Enabled)
                    return;
            }
            catch (Exception ex)
            {
                // Config not ready yet or not found - silently skip
                if (_config.DebugTooltips)
                    Plugin.Logger.Warning($"[HSUI Tooltip] WorldObjectTooltipConfig not available: {ex.Message}");
                return;
            }

            IGameObject? mouseOverTarget = Plugin.TargetManager.MouseOverTarget;
            if (mouseOverTarget == null)
                return;

            // Don't show tooltip for ourselves
            if (mouseOverTarget.GameObjectId == Plugin.ObjectTable.LocalPlayer?.GameObjectId)
                return;

            string name = mouseOverTarget.Name.ToString();
            if (string.IsNullOrEmpty(name))
                name = "Unknown";

            var sb = new StringBuilder();

            // Free Company tag for players
            if (_worldTooltipConfig.ShowTitle && mouseOverTarget is IPlayerCharacter player)
            {
                string fcTag = player.CompanyTag.ToString();
                if (!string.IsNullOrEmpty(fcTag))
                    sb.AppendLine($"«{fcTag}»");
            }

            // Level
            if (_worldTooltipConfig.ShowLevel && mouseOverTarget is ICharacter charLevel)
            {
                byte level = charLevel.Level;
                if (level > 0)
                    sb.AppendLine($"Level {level}");
            }

            // Job (for players)
            if (_worldTooltipConfig.ShowJob && mouseOverTarget is IPlayerCharacter playerJob)
            {
                uint jobId = playerJob.ClassJob.RowId;
                if (jobId > 0 && JobsHelper.JobNames.TryGetValue(jobId, out string? jobName))
                    sb.AppendLine($"Job: {jobName}");
            }

            // HP
            if (_worldTooltipConfig.ShowHP && mouseOverTarget is ICharacter charHp)
            {
                uint currentHp = charHp.CurrentHp;
                uint maxHp = charHp.MaxHp;
                if (maxHp > 0)
                {
                    float pct = (float)currentHp / maxHp * 100f;
                    sb.AppendLine($"HP: {currentHp:N0} / {maxHp:N0} ({pct:F0}%)");
                }
            }

            // Distance
            if (_worldTooltipConfig.ShowDistance)
            {
                var localPlayer = Plugin.ObjectTable.LocalPlayer;
                if (localPlayer != null)
                {
                    float dist = Vector3.Distance(localPlayer.Position, mouseOverTarget.Position);
                    sb.AppendLine($"Distance: {dist:F1}y");
                }
            }

            // Object ID (debug)
            if (_worldTooltipConfig.ShowObjectId)
            {
                sb.AppendLine($"ID: {mouseOverTarget.GameObjectId}");
            }

            string body = sb.ToString().TrimEnd('\r', '\n');
            if (string.IsNullOrEmpty(body))
                body = "(No info)";

            uint? iconId = _worldTooltipConfig.ShowIcon ? GetWorldObjectIconId(mouseOverTarget) : null;

            if (_worldTooltipConfig.DetachFromCursor)
            {
                ShowTooltip(body, _worldTooltipConfig.Position, name, 0, "", iconId);
            }
            else
            {
                ShowTooltipOnCursor(body, name, 0, "", iconId);
            }
        }

        private static unsafe uint? GetWorldObjectIconId(IGameObject gameObject)
        {
            if (gameObject == null || gameObject.Address == IntPtr.Zero)
                return null;
            var obj = (GameObject*)gameObject.Address;
            if (obj == null)
                return null;
            uint iconId = obj->NamePlateIconId;
            return iconId > 0 ? iconId : null;
        }

        public void RemoveTooltip()
        {
            _dataIsValid = false;
            _formattedSegments = null;
        }

        private void DrawTooltipBody()
        {
            if (_formattedSegments != null && _formattedSegments.Count > 0)
            {
                bool afterNewline = true;
                float lineHeight = ImGui.GetTextLineHeight();
                float compactSpacing = Math.Max(2, lineHeight * _config.TooltipLineSpacing);

                foreach (var seg in _formattedSegments)
                {
                    if (seg.Text == "\n")
                    {
                        ImGui.Dummy(new Vector2(0, compactSpacing));
                        afterNewline = true;
                    }
                    else if (!string.IsNullOrEmpty(seg.Text))
                    {
                        if (!afterNewline)
                            ImGui.SameLine(0, 0);
                        string text = afterNewline ? seg.Text.TrimStart() : seg.Text;
                        if (!string.IsNullOrEmpty(text))
                        {
                            ImGui.TextColored(seg.Color, text);
                            afterNewline = false;
                        }
                    }
                }
            }
            else
            {
                ImGui.TextColored(_config.TextColor.Vector, _currentTooltipText);
            }
        }

        public void Draw()
        {
            if (!_dataIsValid)
                return;
            if (ConfigurationManager.Instance.ShowingModalWindow)
            {
                if (_config.DebugTooltips)
                    Plugin.Logger.Information("[HSUI Tooltip DBG] Draw SKIPPED: ShowingModalWindow=true");
                return;
            }

            // bg
            ImGuiWindowFlags windowFlags =
                  ImGuiWindowFlags.NoTitleBar
                | ImGuiWindowFlags.NoMove
                | ImGuiWindowFlags.NoDecoration
                | ImGuiWindowFlags.NoBackground
                | ImGuiWindowFlags.NoInputs
                | ImGuiWindowFlags.NoSavedSettings
                | ImGuiWindowFlags.NoFocusOnAppearing;

            // imgui clips the left and right borders inside windows for some reason
            // we make the window bigger so the actual drawable size is the expected one
            var windowMargin = new Vector2(4, 0);
            var windowPos = _position - windowMargin;

            ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always);
            ImGui.SetNextWindowSize(_size + windowMargin * 2);

            ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
            ImGui.Begin("DelvUI_tooltip", windowFlags);
            ImGui.SetWindowFontScale(_config.TooltipFontScale);
            var drawList = ImGui.GetWindowDrawList();

            drawList.AddRectFilled(_position, _position + _size, _config.BackgroundColor.Base);

            if (_config.BorderConfig.Enabled)
            {
                drawList.AddRect(_position, _position + _size, _config.BorderConfig.Color.Base, 0, ImDrawFlags.None, _config.BorderConfig.Thickness);
            }

            // no idea why i have to do this
            float globalScaleCorrection = -15 + 15 * ImGuiHelpers.GlobalScale;

            string fontId = _config.FontID ?? FontsConfig.DefaultSmallFontKey;
            float iconSizeScaled = IconSize * ImGuiHelpers.GlobalScale * _config.TooltipScale * _config.TooltipIconScale;
            float iconGap = (_currentIconId.HasValue && _currentIconId.Value > 0) ? iconSizeScaled + Margin : 0;
            float wrapWidth = (_currentIconId.HasValue && _currentIconId.Value > 0)
                ? _size.X - iconGap - Margin * 2
                : Math.Max(_titleSize.X, _textSize.X) + Margin;
            float textBlockX = windowMargin.X + Margin + iconGap;

            if (_currentIconId.HasValue && _currentIconId.Value > 0)
            {
                var iconPos = _position + new Vector2(Margin, Margin);
                DrawHelper.DrawIcon(_currentIconId.Value, iconPos, new Vector2(iconSizeScaled, iconSizeScaled), false, drawList);
            }

            if (_currentTooltipTitle != null)
            {
                // title
                Vector2 cursorPos;
                using (FontsManager.Instance.PushFont(fontId))
                {
                    cursorPos = new Vector2(textBlockX, Margin);
                    ImGui.SetCursorPos(cursorPos);
                    ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
                    ImGui.TextColored(_config.TitleColor.Vector, _currentTooltipTitle);
                    ImGui.PopTextWrapPos();
                }

                // text (formatted or plain)
                using (FontsManager.Instance.PushFont(fontId))
                {
                    cursorPos = new Vector2(textBlockX, Margin + _titleSize.Y);
                    ImGui.SetCursorPos(cursorPos);
                    ImGui.PushTextWrapPos(cursorPos.X + wrapWidth + globalScaleCorrection);
                    DrawTooltipBody();
                    ImGui.PopTextWrapPos();
                }
            }
            else
            {
                // text (formatted or plain)
                using (FontsManager.Instance.PushFont(fontId))
                {
                    var cursorPos = new Vector2(textBlockX, Margin);
                    var textWidth = _size.X - iconGap - Margin * 2;

                    ImGui.SetCursorPos(cursorPos);
                    ImGui.PushTextWrapPos(cursorPos.X + textWidth + globalScaleCorrection);
                    DrawTooltipBody();
                    ImGui.PopTextWrapPos();
                }
            }

            ImGui.SetWindowFontScale(1f);
            ImGui.End();
            ImGui.PopStyleVar();

            if (_config.DebugTooltips)
                Plugin.Logger.Information($"[HSUI Tooltip DBG] Draw rendered tooltip at ({_position.X:F0},{_position.Y:F0})");
            RemoveTooltip();
        }

        /// <summary>
        /// Parses action/ability description text into colored segments for game-style tooltips.
        /// Adds newlines before section headers and colors: section labels (Additional Effect, Duration) green,
        /// status names (e.g. Blood of the Dragon) yellow, secondary labels grey.
        /// </summary>
        public static List<TooltipSegment> FormatActionTooltip(string description, Vector4 textColor, Vector4 sectionLabelColor, Vector4 statusNameColor)
        {
            var result = new List<TooltipSegment>();
            if (string.IsNullOrEmpty(description))
                return result;

            // Pattern: section labels to color (Additional Effect, Duration, X Effect, Maximum Charges, Blood Gauge Cost, Combo, roles)
            var sectionRegex = new Regex(@"\b(Additional Effect:|Duration:|Maximum Charges:|Blood Gauge Cost:|Combo Action:|Combo Potency:|Combo Bonus:|Tank:|Melee:|Ranged:|Caster:|Healer:|[A-Za-z][A-Za-z0-9\s]+ Effect:)", RegexOptions.None);
            // Pattern: "Grants X X Effect:" - captures status name X
            var grantsRegex = new Regex(@"Grants\s+(.+?)\s+\1\s+Effect:", RegexOptions.Singleline);

            int lastEnd = 0;
            foreach (Match m in sectionRegex.Matches(description))
            {
                string chunk = description.Substring(lastEnd, m.Index - lastEnd).TrimEnd();
                if (!string.IsNullOrEmpty(chunk))
                {
                    // Check for "Grants StatusName" in this chunk to color status name yellow
                    var grantsMatch = grantsRegex.Match(chunk);
                    if (grantsMatch.Success)
                    {
                        string statusName = grantsMatch.Groups[1].Value.Trim();
                        string grantsPrefix = "Grants " + statusName;
                        int idx = chunk.IndexOf(grantsPrefix, StringComparison.Ordinal);
                        if (idx >= 0)
                        {
                            if (idx > 0)
                                result.Add(new TooltipSegment(chunk.Substring(0, idx), textColor));
                            result.Add(new TooltipSegment("Grants ", textColor));
                            result.Add(new TooltipSegment(statusName + " ", statusNameColor));
                            int after = idx + grantsPrefix.Length;
                            if (after < chunk.Length)
                                result.Add(new TooltipSegment(chunk.Substring(after), textColor));
                        }
                        else
                            result.Add(new TooltipSegment(chunk, textColor));
                    }
                    else
                        result.Add(new TooltipSegment(chunk, textColor));
                }

                result.Add(new TooltipSegment("\n", textColor));
                result.Add(new TooltipSegment(m.Value + " ", sectionLabelColor));
                // Add newline so description wraps below label like stock game (not floating beside it)
                if (m.Value.IndexOf("Effect:", StringComparison.OrdinalIgnoreCase) >= 0)
                    result.Add(new TooltipSegment("\n", textColor));
                lastEnd = m.Index + m.Length;
            }
            if (lastEnd < description.Length)
            {
                string tail = description.Substring(lastEnd).TrimStart();
                if (!string.IsNullOrEmpty(tail))
                {
                    string valuePart = "";
                    string notePart = "";
                    bool hasValue = false;

                    int newlineIdx = tail.IndexOf('\n');
                    if (newlineIdx >= 0)
                    {
                        // "2\nCannot be executed..." - value on first line, note on next
                        valuePart = tail.Substring(0, newlineIdx).TrimEnd();
                        notePart = tail.Substring(newlineIdx + 1).TrimStart();
                        hasValue = Regex.IsMatch(valuePart, @"^\d+[ms]?$");
                    }
                    else
                    {
                        // "2 Cannot be executed while bound." - value + space + note (no newline)
                        var valueNoteMatch = Regex.Match(tail, @"^(\d+[ms]?)\s+([A-Z].+)$", RegexOptions.Singleline);
                        if (valueNoteMatch.Success)
                        {
                            valuePart = valueNoteMatch.Groups[1].Value;
                            notePart = valueNoteMatch.Groups[2].Value;
                            hasValue = true;
                        }
                        else if (Regex.IsMatch(tail, @"^\d+[ms]?$"))
                        {
                            // Plain value only: "50" (Blood Gauge Cost), "2" (charges) - stay on same line as label
                            valuePart = tail;
                            hasValue = true;
                        }
                    }

                    // Add valuePart when present - section content (e.g. "Restore party HP..." for Healer, "2" for charges)
                    if (!string.IsNullOrEmpty(valuePart))
                    {
                        result.Add(new TooltipSegment(valuePart, textColor));
                    }
                    if (!string.IsNullOrEmpty(notePart))
                    {
                        result.Add(new TooltipSegment("\n", textColor));
                        result.Add(new TooltipSegment(notePart, textColor));
                    }
                    else if (!hasValue)
                    {
                        // Standalone note: "(Duration can be extended...)", "Cannot be executed...", footnotes
                        result.Add(new TooltipSegment("\n", textColor));
                        result.Add(new TooltipSegment(tail, textColor));
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Builds formatted segments from a full action tooltip body (stats + description).
        /// Used when UseGameStyleFormatting is enabled.
        /// Stats are placed first, then exactly one newline, then description below.
        /// </summary>
        public static List<TooltipSegment>? BuildFormattedActionTooltipBody(string body, TooltipsConfig config)
        {
            if (string.IsNullOrEmpty(body) || !config.UseGameStyleFormatting)
                return null;

            var result = new List<TooltipSegment>();
            var textColor = config.TextColor.Vector;
            var sectionColor = config.SectionLabelColor.Vector;
            var statusColor = config.StatusNameColor.Vector;
            var secondaryColor = config.SecondaryLabelColor.Vector;

            var blocks = body.Split(new[] { "\n\n" }, StringSplitOptions.None);
            int descStart = 0;
            bool hasStats = false;

            // Stats block: may contain "Potency: X" and "Cast: ... | Recast: ..." on separate lines
            for (int i = 0; i < blocks.Length; i++)
            {
                string block = blocks[i].Trim();
                var lines = block.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
                bool blockIsStats = false;
                foreach (string line in lines)
                {
                    string part = line.Trim();
                    if (part.StartsWith("Potency:", StringComparison.OrdinalIgnoreCase))
                    {
                        if (hasStats) result.Add(new TooltipSegment("\n", textColor));
                        result.Add(new TooltipSegment("Potency:", secondaryColor));
                        result.Add(new TooltipSegment(" " + part.Substring(8).TrimStart(), textColor));
                        hasStats = true;
                        blockIsStats = true;
                    }
                    else if (part.Contains("Cast:") || part.Contains("Recast:"))
                    {
                        if (hasStats) result.Add(new TooltipSegment("\n", textColor));
                        var statsSegs = FormatActionStats(part, textColor, secondaryColor);
                        result.AddRange(statsSegs);
                        hasStats = true;
                        blockIsStats = true;
                    }
                }
                if (!blockIsStats)
                {
                    descStart = i;
                    break;
                }
                descStart = i + 1;
            }

            // Description: exactly one newline after stats, then description with compact section spacing
            string desc = string.Join("\n", blocks.Skip(descStart)).Trim();
            if (!string.IsNullOrEmpty(desc))
            {
                if (hasStats) result.Add(new TooltipSegment("\n", textColor));
                var descSegs = FormatActionTooltip(desc, textColor, sectionColor, statusColor);
                result.AddRange(descSegs);
            }

            return result.Count > 0 ? result : null;
        }

        /// <summary>
        /// Formats action stats (Cast, Recast, Range, Radius) as colored segments.
        /// </summary>
        public static List<TooltipSegment> FormatActionStats(string statsLine, Vector4 textColor, Vector4 secondaryLabelColor)
        {
            var result = new List<TooltipSegment>();
            if (string.IsNullOrEmpty(statsLine))
                return result;

            // statsLine format: "Cast: Instant | Recast: 60.0s | Range: 15y | Radius: 15y"
            var parts = statsLine.Split(new[] { " | " }, StringSplitOptions.None);
            for (int i = 0; i < parts.Length; i++)
            {
                if (i > 0)
                    result.Add(new TooltipSegment(" | ", textColor));
                int colonIdx = parts[i].IndexOf(':');
                if (colonIdx >= 0)
                {
                    result.Add(new TooltipSegment(parts[i].Substring(0, colonIdx + 1), secondaryLabelColor));
                    result.Add(new TooltipSegment(parts[i].Substring(colonIdx + 1), textColor));
                }
                else
                {
                    result.Add(new TooltipSegment(parts[i], textColor));
                }
            }
            return result;
        }

        private Vector2 ConstrainPosition(Vector2 position, Vector2 size)
        {
            var screenSize = ImGui.GetWindowViewport().Size;

            if (position.X < 0)
            {
                position.X = Margin;
            }
            else if (position.X + size.X > screenSize.X)
            {
                position.X = screenSize.X - size.X - Margin;
            }

            if (position.Y < 0)
            {
                position.Y = Margin;
            }

            return position;
        }
    }

    [Section("Misc")]
    [SubSection("Tooltips", 0)]
    public class TooltipsConfig : PluginConfigObject
    {
        public new static TooltipsConfig DefaultConfig() { return new TooltipsConfig(); }

        [Checkbox("Debug Tooltips")]
        [Order(3)]
        public bool DebugTooltips = false;

        [Font]
        [Order(4)]
        public string? FontID = null;

        [DragFloat("Tooltip Scale", min = 0.5f, max = 2f, help = "Scale the tooltip window size. Useful for large resolutions.")]
        [Order(5)]
        public float TooltipScale = 1f;

        [DragFloat("Tooltip Max Width", min = 160f, max = 600f, help = "Maximum width of the tooltip box in pixels.")]
        [Order(6)]
        public float TooltipMaxWidth = 340f;

        [DragFloat("Line Spacing", min = 0.2f, max = 1.5f, help = "Spacing between lines and sections (1 = normal, lower = compact).")]
        [Order(7)]
        public float TooltipLineSpacing = 0.6f;

        [DragFloat("Font Scale", min = 0.7f, max = 1.5f, help = "Scale factor for tooltip font size.")]
        [Order(8)]
        public float TooltipFontScale = 1f;

        [DragFloat("Icon Scale", min = 0.5f, max = 2f, help = "Scale factor for the ability/status icon in tooltips.")]
        [Order(9)]
        public float TooltipIconScale = 1f;

        [DragFloat("Height Buffer", min = 0f, max = 80f, help = "Extra space at bottom of tooltip to prevent clipping. Adjust if text is cut off.")]
        [Order(10)]
        public float TooltipHeightBuffer = 28f;

        [Checkbox("Show Status Effects IDs")]
        [Order(11)]
        public bool ShowStatusIDs = false;

        [Checkbox("Show Action ID", help = "Show action ID in hotbar and action tooltips.")]
        [Order(12)]
        public bool ShowActionIDs = false;

        [Checkbox("Show Source Name")]
        [Order(13)]
        public bool ShowSourceName = false;

        [Checkbox("Game-style formatting", help = "Color section labels (Additional Effect, Duration) green, status names yellow, and add new lines between sections like the base game tooltips.")]
        [Order(14)]
        public bool UseGameStyleFormatting = true;

        [ColorEdit4("Section Label Color", help = "Color for labels like 'Additional Effect:', 'Duration:'.")]
        [Order(15)]
        public PluginConfigColor SectionLabelColor = new PluginConfigColor(new(0f, 255f / 255f, 0f, 1f));

        [ColorEdit4("Status Name Color", help = "Color for buff/status effect names like 'Blood of the Dragon'.")]
        [Order(16)]
        public PluginConfigColor StatusNameColor = new PluginConfigColor(new(255f / 255f, 255f / 255f, 0f, 1f));

        [ColorEdit4("Secondary Label Color", help = "Color for Cast, Recast, Range, Radius labels.")]
        [Order(17)]
        public PluginConfigColor SecondaryLabelColor = new PluginConfigColor(new(204f / 255f, 204f / 255f, 204f / 255f, 1f));

        [ColorEdit4("Background Color")]
        [Order(15)]
        public PluginConfigColor BackgroundColor = new PluginConfigColor(new(19f / 255f, 19f / 255f, 19f / 255f, 190f / 250f));

        [ColorEdit4("Title Color")]
        [Order(20)]
        public PluginConfigColor TitleColor = new PluginConfigColor(new(255f / 255f, 210f / 255f, 31f / 255f, 100f / 100f));

        [ColorEdit4("Text Color")]
        [Order(35)]
        public PluginConfigColor TextColor = new PluginConfigColor(new(255f / 255f, 255f / 255f, 255f / 255f, 100f / 100f));

        [NestedConfig("Border", 40, separator = false, spacing = true, collapsingHeader = false)]
        public TooltipBorderConfig BorderConfig = new();
    }

    [Exportable(false)]
    public class TooltipBorderConfig : PluginConfigObject
    {
        [ColorEdit4("Color")]
        [Order(5)]
        public PluginConfigColor Color = new(new Vector4(10f / 255f, 10f / 255f, 10f / 255f, 160f / 255f));

        [DragInt("Thickness", min = 1, max = 100)]
        [Order(10)]
        public int Thickness = 4;

        public TooltipBorderConfig()
        {
        }

        public TooltipBorderConfig(PluginConfigColor color, int thickness)
        {
            Color = color;
            Thickness = thickness;
        }
    }

    [Section("Misc")]
    [SubSection("World Tooltip", 0)]
    public class WorldObjectTooltipConfig : PluginConfigObject
    {
        public new static WorldObjectTooltipConfig DefaultConfig() { return new WorldObjectTooltipConfig(); }

        [Checkbox("Detach from Cursor", spacing = true)]
        [Order(1)]
        public bool DetachFromCursor = false;

        [DragFloat2("Position (when detached)", min = -4000f, max = 4000f)]
        [Order(2, collapseWith = nameof(DetachFromCursor))]
        public Vector2 Position = new Vector2(100, 100);

        [Checkbox("Show Icon (quest/NPC icons)", spacing = true)]
        [Order(3)]
        public bool ShowIcon = true;

        [Checkbox("Shift+Click action in Actions & Traits → insert \"You should check out X\" in chat", help = "When chat is focused, inserts directly. Otherwise copies to clipboard.")]
        [Order(4)]
        public bool ActionChatLinkEnabled = true;

        [Checkbox("Show Level", spacing = true)]
        [Order(6)]
        public bool ShowLevel = true;

        [Checkbox("Show HP")]
        [Order(10)]
        public bool ShowHP = true;

        [Checkbox("Show Job (Players)")]
        [Order(15)]
        public bool ShowJob = true;

        [Checkbox("Show FC Tag (Players)")]
        [Order(20)]
        public bool ShowTitle = false;

        [Checkbox("Show Distance")]
        [Order(25)]
        public bool ShowDistance = true;

        [Checkbox("Show Object ID")]
        [Order(30)]
        public bool ShowObjectId = false;
    }
}
