using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Style;
using LightlessSync.Utils;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;

namespace LightlessSync.UI.Handlers;

public class IdDisplayHandler
{
    private readonly LightlessConfigService _lightlessConfigService;
    private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
    private readonly LightlessMediator _mediator;
    private readonly ServerConfigurationManager _serverManager;
    private readonly Dictionary<string, bool> _showIdForEntry = new(StringComparer.Ordinal);
    private string _editComment = string.Empty;
    private string _editEntry = string.Empty;
    private bool _editIsUid = false;
    private string _lastMouseOverUid = string.Empty;
    private bool _popupShown = false;
    private DateTime? _popupTime;

    private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
    private float _highlightBoost;

    public IdDisplayHandler(
        LightlessMediator mediator,
        ServerConfigurationManager serverManager,
        LightlessConfigService lightlessConfigService,
        PlayerPerformanceConfigService playerPerformanceConfigService)
    {
        _mediator = mediator;
        _serverManager = serverManager;
        _lightlessConfigService = lightlessConfigService;
        _playerPerformanceConfigService = playerPerformanceConfigService;
    }

    public void DrawGroupText(string id, GroupFullInfoDto group, float textPosX, Func<float> editBoxWidth)
    {
        ImGui.SameLine(textPosX);
        (bool textIsUid, string playerText) = GetGroupText(group);
        if (!string.Equals(_editEntry, group.GID, StringComparison.Ordinal))
        {
            using (ImRaii.PushFont(UiBuilder.MonoFont, textIsUid))
            {
                ImGui.AlignTextToFramePadding();
                ImGui.TextUnformatted(playerText);
            }

            if (ImGui.IsItemHovered())
            {
                ImGui.SetTooltip("Left click to switch between ID display and alias"
                    + Environment.NewLine + "Right click to edit notes for this syncshell"
                    + Environment.NewLine + "Middle Mouse Button to open syncshell profile in a separate window");
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
            {
                var prevState = textIsUid;
                if (_showIdForEntry.TryGetValue(group.GID, out bool value))
                {
                    prevState = value;
                }
                _showIdForEntry[group.GID] = !prevState;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
            {
                if (_editIsUid)
                {
                    _serverManager.SetNoteForUid(_editEntry, _editComment, save: true);
                }
                else
                {
                    _serverManager.SetNoteForGid(_editEntry, _editComment, save: true);
                }

                _editComment = _serverManager.GetNoteForGid(group.GID) ?? string.Empty;
                _editEntry = group.GID;
                _editIsUid = false;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
            {
                _mediator.Publish(new GroupProfileOpenStandaloneMessage(group.Group));
            }
        }
        else
        {
            ImGui.AlignTextToFramePadding();

            ImGui.SetNextItemWidth(editBoxWidth.Invoke());
            if (ImGui.InputTextWithHint("", "Name/Notes", ref _editComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
            {
                _serverManager.SetNoteForGid(group.GID, _editComment, save: true);
                _editEntry = string.Empty;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
            {
                _editEntry = string.Empty;
            }
            UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel");
        }
    }

    public void DrawPairText(string id, Pair pair, float textPosX, Func<float> editBoxWidth)
    {
        ImGui.SameLine(textPosX);
        (bool textIsUid, string playerText) = GetPlayerText(pair);
        var compactPerformanceText = BuildCompactPerformanceUsageText(pair);

        if (!string.Equals(_editEntry, pair.UserData.UID, StringComparison.Ordinal))
        {
            var targetFontSize = ImGui.GetFontSize();
            var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
            var rowWidth = MathF.Max(editBoxWidth.Invoke(), 0f);
            float rowRightLimit = 0f;
            Vector2 nameRectMin = Vector2.Zero;
            Vector2 nameRectMax = Vector2.Zero;
            float rowTopForStats = 0f;
            float frameHeightForStats = 0f;

            using (ImRaii.PushFont(font, textIsUid))
            {
                ImGui.AlignTextToFramePadding();
                var rowStart = ImGui.GetCursorScreenPos();
                rowRightLimit = rowStart.X + rowWidth;

                Vector4? textColor = null;
                Vector4? glowColor = null;

                if (pair.UserData.HasVanity)
                {
                    if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
                    {
                        textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
                    }

                    if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
                    {
                        glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
                    }
                }

                var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null);
                var seString = useVanityColors
                    ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)
                    : SeStringUtils.BuildPlain(playerText);

                var drawList = ImGui.GetWindowDrawList();
                bool useHighlight = false;
                float highlightPadX = 0f;
                float highlightPadY = 0f;

                if (useVanityColors)
                {
                    float boost = Luminance.ComputeHighlight(textColor, glowColor);

                    if (boost > 0f)
                    {
                        var style = ImGui.GetStyle();
                        useHighlight = true;
                        highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale);
                        highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale);
                        drawList.ChannelsSplit(2);
                        drawList.ChannelsSetCurrent(1);

                        _highlightBoost = boost;
                    }
                    else
                    {
                        _highlightBoost = 0f;
                    }
                }

                SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, targetFontSize, font, pair.UserData.UID);
                nameRectMin = ImGui.GetItemRectMin();
                nameRectMax = ImGui.GetItemRectMax();

                if (useHighlight)
                {
                    var style = ImGui.GetStyle();
                    var frameHeight = ImGui.GetFrameHeight();
                    var rowTop = rowStart.Y - style.FramePadding.Y;
                    var rowBottom = rowTop + frameHeight;

                    var highlightMin = new Vector2(nameRectMin.X - highlightPadX, rowTop - highlightPadY);
                    var highlightMax = new Vector2(nameRectMax.X + highlightPadX, rowBottom + highlightPadY);

                    var windowPos = ImGui.GetWindowPos();
                    var contentMin = windowPos + ImGui.GetWindowContentRegionMin();
                    var contentMax = windowPos + ImGui.GetWindowContentRegionMax();
                    highlightMin.X = MathF.Max(highlightMin.X, contentMin.X);
                    highlightMax.X = MathF.Min(highlightMax.X, contentMax.X);
                    highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y);
                    highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y);

                    var highlightColor = new Vector4(
                        0.25f + _highlightBoost,
                        0.25f + _highlightBoost,
                        0.25f + _highlightBoost,
                        1f
                    );

                    highlightColor = Luminance.BackgroundContrast(textColor, glowColor, highlightColor, ref _currentBg);

                    float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale;
                    drawList.ChannelsSetCurrent(0);
                    drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding);

                    var borderColor = style.Colors[(int)ImGuiCol.Border];
                    borderColor.W *= 0.25f;
                    drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding);
                    drawList.ChannelsMerge();
                }
            }
            {
                var style = ImGui.GetStyle();
                frameHeightForStats = ImGui.GetFrameHeight();
                rowTopForStats = nameRectMin.Y - style.FramePadding.Y;
            }
            if (ImGui.IsItemHovered())
            {
                if (!string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal))
                {
                    _popupTime = DateTime.UtcNow.AddSeconds(_lightlessConfigService.Current.ProfileDelay);
                }

                _lastMouseOverUid = id;

                if (_popupTime > DateTime.UtcNow || !_lightlessConfigService.Current.ProfilesShow)
                {
                    ImGui.SetTooltip("Left click to switch between UID display and nick" + Environment.NewLine
                        + "Right click to change nick for " + pair.UserData.AliasOrUID + Environment.NewLine
                        + "Middle Mouse Button to open their profile in a separate window");
                }
                else if (_popupTime < DateTime.UtcNow && !_popupShown)
                {
                    _popupShown = true;
                    _mediator.Publish(new ProfilePopoutToggle(pair));
                }
            }
            else
            {
                if (string.Equals(_lastMouseOverUid, id, StringComparison.Ordinal))
                {
                    _mediator.Publish(new ProfilePopoutToggle(Pair: null));
                    _lastMouseOverUid = string.Empty;
                    _popupShown = false;
                }
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
            {
                var prevState = textIsUid;
                if (_showIdForEntry.ContainsKey(pair.UserData.UID))
                {
                    prevState = _showIdForEntry[pair.UserData.UID];
                }
                _showIdForEntry[pair.UserData.UID] = !prevState;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
            {
                if (_editIsUid)
                {
                    _serverManager.SetNoteForUid(_editEntry, _editComment, save: true);
                }
                else
                {
                    _serverManager.SetNoteForGid(_editEntry, _editComment, save: true);
                }

                _editComment = pair.GetNote() ?? string.Empty;
                _editEntry = pair.UserData.UID;
                _editIsUid = true;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
            {
                _mediator.Publish(new ProfileOpenStandaloneMessage(pair));
            }

            if (!string.IsNullOrEmpty(compactPerformanceText))
            {
                ImGui.SameLine();

                const float compactFontScale = 0.85f;
                ImGui.SetWindowFontScale(compactFontScale);
                var compactHeight = ImGui.GetTextLineHeight();
                var targetPos = ImGui.GetCursorScreenPos();
                var availableWidth = MathF.Max(rowRightLimit - targetPos.X, 0f);
                var centeredY = rowTopForStats + MathF.Max((frameHeightForStats - compactHeight) * 0.5f, 0f);
                ImGui.SetCursorScreenPos(new Vector2(targetPos.X, centeredY));

                var performanceText = string.Empty;
                var wasTruncated = false;
                if (availableWidth > 0f)
                {
                    performanceText = TruncateTextToWidth(compactPerformanceText, availableWidth, out wasTruncated);
                }

                ImGui.TextDisabled(performanceText);
                ImGui.SetWindowFontScale(1f);

                if (wasTruncated && ImGui.IsItemHovered())
                {
                    ImGui.SetTooltip(compactPerformanceText);
                }
            }
        }
        else
        {
            ImGui.AlignTextToFramePadding();

            ImGui.SetNextItemWidth(MathF.Max(editBoxWidth.Invoke(), 0f));
            if (ImGui.InputTextWithHint("##" + pair.UserData.UID, "Nick/Notes", ref _editComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
            {
                _serverManager.SetNoteForUid(pair.UserData.UID, _editComment);
                _serverManager.SaveNotes();
                _editEntry = string.Empty;
            }

            if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
            {
                _editEntry = string.Empty;
            }

            UiSharedService.AttachToolTip("Hit ENTER to save\nRight click to cancel");
        }
    }


    public (bool isGid, string text) GetGroupText(GroupFullInfoDto group)
    {
        var textIsGid = true;
        bool showUidInsteadOfName = ShowGidInsteadOfName(group);
        string? groupText = _serverManager.GetNoteForGid(group.GID);
        if (!showUidInsteadOfName && groupText != null)
        {
            if (string.IsNullOrEmpty(groupText))
            {
                groupText = group.GroupAliasOrGID;
            }
            else
            {
                textIsGid = false;
            }
        }
        else
        {
            groupText = group.GroupAliasOrGID;
        }

        return (textIsGid, groupText!);
    }

    public (bool isUid, string text) GetPlayerText(Pair pair)
    {
        var textIsUid = true;
        bool showUidInsteadOfName = ShowUidInsteadOfName(pair);
        string? playerText = _serverManager.GetNoteForUid(pair.UserData.UID);
        if (!showUidInsteadOfName && playerText != null)
        {
            if (string.IsNullOrEmpty(playerText))
            {
                playerText = pair.UserData.AliasOrUID;
            }
            else
            {
                textIsUid = false;
            }
        }
        else
        {
            playerText = pair.UserData.AliasOrUID;
        }

        if (_lightlessConfigService.Current.ShowCharacterNameInsteadOfNotesForVisible && pair.IsVisible && !showUidInsteadOfName)
        {
            playerText = pair.PlayerName;
            textIsUid = false;
            if (_lightlessConfigService.Current.PreferNotesOverNamesForVisible)
            {
                var note = pair.GetNote();
                if (note != null)
                {
                    playerText = note;
                }
            }
        }

        return (textIsUid, playerText!);
    }

    private string? BuildCompactPerformanceUsageText(Pair pair)
    {
        var config = _playerPerformanceConfigService.Current;
        if (!config.ShowPerformanceIndicator || !config.ShowPerformanceUsageNextToName)
        {
            return null;
        }

        var vramBytes = pair.LastAppliedApproximateEffectiveVRAMBytes >= 0
            ? pair.LastAppliedApproximateEffectiveVRAMBytes
            : pair.LastAppliedApproximateVRAMBytes;
        var triangleCount = pair.LastAppliedDataTris;
        if (vramBytes < 0 && triangleCount < 0)
        {
            return null;
        }

        var segments = new List<string>(2);
        if (vramBytes >= 0)
        {
            segments.Add(UiSharedService.ByteToString(vramBytes));
        }

        if (triangleCount >= 0)
        {
            segments.Add(FormatTriangleCount(triangleCount));
        }

        return segments.Count == 0 ? null : string.Join(" / ", segments);
    }

    private static string FormatTriangleCount(long triangleCount)
    {
        if (triangleCount < 0)
        {
            return string.Empty;
        }

        if (triangleCount >= 1_000_000)
        {
            return FormattableString.Invariant($"{triangleCount / 1_000_000d:0.#}m tris");
        }

        if (triangleCount >= 1_000)
        {
            return FormattableString.Invariant($"{triangleCount / 1_000d:0.#}k tris");
        }

        return $"{triangleCount} tris";
    }

    internal void Clear()
    {
        _editEntry = string.Empty;
        _editComment = string.Empty;
    }

    internal void OpenProfile(Pair entry)
    {
        _mediator.Publish(new ProfileOpenStandaloneMessage(entry));
    }

    private bool ShowGidInsteadOfName(GroupFullInfoDto group)
    {
        _showIdForEntry.TryGetValue(group.GID, out var showidInsteadOfName);

        return showidInsteadOfName;
    }

    private bool ShowUidInsteadOfName(Pair pair)
    {
        _showIdForEntry.TryGetValue(pair.UserData.UID, out var showidInsteadOfName);

        return showidInsteadOfName;
    }

    private static string TruncateTextToWidth(string text, float maxWidth, out bool wasTruncated)
    {
        wasTruncated = false;
        if (string.IsNullOrEmpty(text) || maxWidth <= 0f)
        {
            return string.Empty;
        }

        var fullWidth = ImGui.CalcTextSize(text).X;
        if (fullWidth <= maxWidth)
        {
            return text;
        }

        wasTruncated = true;

        const string Ellipsis = "...";
        var ellipsisWidth = ImGui.CalcTextSize(Ellipsis).X;
        if (ellipsisWidth >= maxWidth)
        {
            return Ellipsis;
        }

        var builder = new StringBuilder(text.Length);
        var remainingWidth = maxWidth - ellipsisWidth;

        foreach (var rune in text.EnumerateRunes())
        {
            var runeText = rune.ToString();
            var runeWidth = ImGui.CalcTextSize(runeText).X;
            if (runeWidth > remainingWidth)
            {
                break;
            }

            builder.Append(runeText);
            remainingWidth -= runeWidth;
        }

        if (builder.Length == 0)
        {
            return Ellipsis;
        }

        builder.Append(Ellipsis);
        return builder.ToString();
    }
}
