using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using LightlessSync.API.Data;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Style;
using LightlessSync.UI.Tags;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.PixelFormats;
using System.Numerics;
using LightlessSync.Services.Profiles;

namespace LightlessSync.UI;

public partial class EditProfileUi : WindowMediatorSubscriberBase
{
    private readonly ApiController _apiController;
    private readonly FileDialogManager _fileDialogManager;
    private readonly LightlessProfileManager _lightlessProfileManager;
    private readonly UiSharedService _uiSharedService;
    private readonly ProfileTagService _profileTagService;
    private const string LoadingProfileDescription = "Loading Profile Data from server...";
    private const string DescriptionMacroTooltip =
        "Supported SeString markup:\n" +
        "<br> - insert a line break (Enter already emits these).\n" +
        "<-> - optional soft hyphen / word break.\n" +
        "<icon(12345)> / <icon2(12345)> - show game icons by numeric id;" +
        "<color=#RRGGBB>text</color> or <color(0xAARRGGBB)> - tint text; reset with </color> or <color(stackcolor)>.\n" +
        "<colortype(n)> / <edgecolortype(n)> - use UI palette colours (0 restores defaults).\n" +
        "<edgecolor(0xAARRGGBB)> / <edgecolor(stackcolor)> - change outline colour.\n" +
        "<bold(1|0)> <italic(1|0)> <shadow(1|0)> <edge(1|0)> - toggle style flags.\n" +
        "<link(0x0E,...)> - create clickable links.";

    private static readonly HashSet<string> _supportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
    {
        "png",
        "jpg",
        "jpeg",
        "webp",
        "bmp"
    };
    private const string _imageFileDialogFilter = "Images{.png,.jpg,.jpeg,.webp,.bmp}";
    private readonly List<int> _tagEditorSelection;
    private int[] _profileTagIds = [];
    private readonly List<SeStringUtils.SeStringSegment> _tagPreviewSegments = new();
    private enum ProfileEditorMode
    {
        User,
        Group
    }

    private ProfileEditorMode _mode = ProfileEditorMode.User;
    private GroupFullInfoDto? _groupInfo;
    private LightlessGroupProfileData? _groupProfileData;
    private bool _groupIsNsfw;
    private bool _groupIsDisabled;
    private bool _groupServerIsNsfw;
    private bool _groupServerIsDisabled;
    private bool _groupVisibilityInitialized;
    private byte[]? _queuedProfileImage;
    private byte[]? _queuedBannerImage;
    private readonly Vector4 _tagBackgroundColor = new(0.18f, 0.18f, 0.18f, 0.95f);
    private readonly Vector4 _tagBorderColor = new(0.35f, 0.35f, 0.35f, 0.4f);
    private const int _maxProfileTags = 12;
    private const int _availableTagsPerPage = 6;
    private int _availableTagPage;
    private UserData? _selfProfileUserData;
    private string _descriptionText = string.Empty;
    private IDalamudTextureWrap? _pfpTextureWrap;
    private IDalamudTextureWrap? _bannerTextureWrap;
    private string _profileDescription = string.Empty;
    private byte[] _profileImage = [];
    private byte[] _bannerImage = [];
    private bool _showProfileImageError = false;
    private bool _showBannerImageError = false;
    private bool _wasOpen;
    private bool _userServerIsNsfw;
    private bool _isNsfwInitialized;
    private bool _isNsfw;

    private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
    private bool _textEnabled;
    private bool _glowEnabled;
    private Vector4 _textColor;
    private Vector4 _glowColor;

    private sealed record VanityState(bool TextEnabled, bool GlowEnabled, Vector4 TextColor, Vector4 GlowColor);
    private VanityState? _savedVanity;

    public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator,
        ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
        LightlessProfileManager lightlessProfileManager, ProfileTagService profileTagService, PerformanceCollectorService performanceCollectorService)
        : base(logger, mediator, "Lightless Sync Edit Profile###LightlessSyncEditProfileUI", performanceCollectorService)
    {
        IsOpen = false;
        var scale = ImGuiHelpers.GlobalScale;
        var editorSize = ProfileEditorLayoutCoordinator.GetEditorSize(scale);
        Size = editorSize;
        SizeCondition = ImGuiCond.FirstUseEver;
        SizeConstraints = new()
        {
            MinimumSize = editorSize,
            MaximumSize = editorSize
        };
        Flags |= ImGuiWindowFlags.NoResize;
        _apiController = apiController;
        _uiSharedService = uiSharedService;
        _fileDialogManager = fileDialogManager;
        _lightlessProfileManager = lightlessProfileManager;
        _profileTagService = profileTagService;
        _tagEditorSelection = new List<int>(_maxProfileTags);

        Mediator.Subscribe<GposeStartMessage>(this, (_) => { _wasOpen = IsOpen; IsOpen = false; });
        Mediator.Subscribe<GposeEndMessage>(this, (_) => IsOpen = _wasOpen);
        Mediator.Subscribe<DisconnectedMessage>(this, (_) =>
        {
            IsOpen = false;
            _selfProfileUserData = null;
        });
        Mediator.Subscribe<ClearProfileUserDataMessage>(this, (msg) =>
        {
            if (msg.UserData == null || string.Equals(msg.UserData.UID, _apiController.UID, StringComparison.Ordinal))
            {
                _pfpTextureWrap?.Dispose();
                _pfpTextureWrap = null;
            }
        });
        Mediator.Subscribe<ConnectedMessage>(this, msg =>
        {
            _selfProfileUserData = msg.Connection.User with
            {
                IsAdmin = msg.Connection.IsAdmin,
                IsModerator = msg.Connection.IsModerator,
                HasVanity = msg.Connection.HasVanity,
                TextColorHex = msg.Connection.TextColorHex,
                TextGlowColorHex = msg.Connection.TextGlowColorHex
            };
            LoadVanity();
        });
        Mediator.Subscribe<OpenGroupProfileEditorMessage>(this, msg => OpenGroupEditor(msg.Group));
    }

    private void LoadVanity()
    {
        _textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex);
        _glowEnabled = !string.IsNullOrEmpty(_apiController.TextGlowColorHex);

        _textColor = _textEnabled ? UIColors.HexToRgba(_apiController.TextColorHex!) : Vector4.One;
        _glowColor = _glowEnabled ? UIColors.HexToRgba(_apiController.TextGlowColorHex!) : Vector4.Zero;

        _savedVanity = new VanityState(_textEnabled, _glowEnabled, _textColor, _glowColor);
    }

    public override async void OnOpen()
    {
        if (_mode == ProfileEditorMode.Group)
        {
            if (_groupInfo is not null)
            {
                var scale = ImGuiHelpers.GlobalScale;
                var viewport = ImGui.GetMainViewport();
                ProfileEditorLayoutCoordinator.EnsureAnchor(viewport.WorkPos, scale);
            }
            return;
        }

        _isNsfwInitialized = false;

        var user = await EnsureSelfProfileUserDataAsync().ConfigureAwait(false);
        if (user is not null)
        {
            ProfileEditorLayoutCoordinator.Enable(user.UID);
            var scale = ImGuiHelpers.GlobalScale;
            var viewport = ImGui.GetMainViewport();
            ProfileEditorLayoutCoordinator.EnsureAnchor(viewport.WorkPos, scale);
            Mediator.Publish(new OpenSelfProfilePreviewMessage(user));
        }
    }

    public override void OnClose()
    {
        if (_mode == ProfileEditorMode.Group)
        {
            if (_groupInfo is not null)
            {
                ProfileEditorLayoutCoordinator.Disable(_groupInfo.Group.GID);
                Mediator.Publish(new CloseGroupProfilePreviewMessage(_groupInfo));
            }

            ResetGroupEditorState();
            _mode = ProfileEditorMode.User;
            return;
        }

        if (_selfProfileUserData is not null)
        {
            ProfileEditorLayoutCoordinator.Disable(_selfProfileUserData.UID);
            Mediator.Publish(new CloseSelfProfilePreviewMessage(_selfProfileUserData));
        }
    }

    private async Task<UserData?> EnsureSelfProfileUserDataAsync()
    {
        if (_selfProfileUserData is not null)
            return _selfProfileUserData;

        try
        {
            var connection = await _apiController.GetConnectionDtoAsync(publishConnected: false).ConfigureAwait(false);
            _selfProfileUserData = connection.User with
            {
                IsAdmin = connection.IsAdmin,
                IsModerator = connection.IsModerator,
                HasVanity = connection.HasVanity,
                TextColorHex = connection.TextColorHex,
                TextGlowColorHex = connection.TextGlowColorHex
            };
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to acquire connection information for profile preview.");
        }

        return _selfProfileUserData;
    }

    protected override void DrawInternal()
    {
        var scale = ImGuiHelpers.GlobalScale;

        if (_mode == ProfileEditorMode.Group)
        {
            DrawGroupEditor(scale);
            return;
        }

        var viewport = ImGui.GetMainViewport();
        var linked = _selfProfileUserData is not null && ProfileEditorLayoutCoordinator.IsActive(_selfProfileUserData.UID);

        if (linked)
        {
            ProfileEditorLayoutCoordinator.EnsureAnchor(viewport.WorkPos, scale);

            var desiredSize = ProfileEditorLayoutCoordinator.GetEditorSize(scale);
            if (!ProfileEditorLayoutCoordinator.NearlyEquals(ImGui.GetWindowSize(), desiredSize))
                ImGui.SetWindowSize(desiredSize, ImGuiCond.Always);

            var currentPos = ImGui.GetWindowPos();
            if (IsWindowBeingDragged())
                ProfileEditorLayoutCoordinator.UpdateAnchorFromEditor(currentPos, scale);

            var desiredPos = ProfileEditorLayoutCoordinator.GetEditorPosition(scale);
            if (!ProfileEditorLayoutCoordinator.NearlyEquals(currentPos, desiredPos))
                ImGui.SetWindowPos(desiredPos, ImGuiCond.Always);
        }
        else
        {
            var defaultProfilePos = viewport.WorkPos + new Vector2(50f, 70f) * scale;
            var defaultEditorPos = defaultProfilePos + ProfileEditorLayoutCoordinator.GetEditorOffset(scale);
            ImGui.SetWindowPos(defaultEditorPos, ImGuiCond.FirstUseEver);
        }

        var profile = _lightlessProfileManager.GetLightlessUserProfile(new UserData(_apiController.UID));

        var accent = UIColors.Get("LightlessPurple");
        var accentBg = new Vector4(accent.X, accent.Y, accent.Z, 0.015f);
        var accentBorder = new Vector4(accent.X, accent.Y, accent.Z, 0.07f);

        using var panelBg = ImRaii.PushColor(ImGuiCol.ChildBg, accentBg);
        using var panelBorder = ImRaii.PushColor(ImGuiCol.ChildBg, accentBorder);
        ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);

        if (ImGui.BeginChild("##ProfileEditorCanvas", -Vector2.One, true))
        {
            DrawGuidelinesSection(scale);
            ImGui.Dummy(new Vector2(0f, 4f * scale));
            DrawTabInterface(profile, scale);
        }
        ImGui.EndChild();
        ImGui.PopStyleVar();
    }

    private void DrawGuidelinesSection(float scale)
    {
        DrawSection("Guidelines", scale, () =>
        {
            ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, 1));

            _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile pictures, tags and description.");
            _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules.");
            _uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)");
            _uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive");
            _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.");
            _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.");
            _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in visibility settings.");

            ImGui.PopStyleVar();
        });
    }

    private void DrawTabInterface(LightlessUserProfileData profile, float scale)
    {
        ImGui.PushStyleVar(ImGuiStyleVar.TabRounding, 4f * scale);
        ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8f, 4f) * scale);

        if (ImGui.BeginTabBar("##ProfileEditorTabs", ImGuiTabBarFlags.NoCloseWithMiddleMouseButton | ImGuiTabBarFlags.FittingPolicyResizeDown))
        {
            if (ImGui.BeginTabItem("Profile"))
            {
                DrawProfileTabContent(profile, scale);
                ImGui.EndTabItem();
            }

            if (ImGui.BeginTabItem("Vanity"))
            {
                DrawVanityTabContent(scale);
                ImGui.EndTabItem();
            }

            ImGui.EndTabBar();
        }
        ImGui.PopStyleVar(2);
    }

    private void DrawProfileTabContent(LightlessUserProfileData profile, float scale)
    {
        if (profile.IsFlagged)
        {
            DrawSection("Moderation Status", scale, () =>
            {
                UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed);
            });
            return;
        }

        SyncProfileState(profile);
        DrawSection("Profile Preview", scale, () => DrawProfileSnapshot(profile, scale));
        DrawSection("Profile Image", scale, () => DrawProfileImageControls(profile));
        DrawSection("Profile Banner", scale, () => DrawProfileBannerControls(profile));
        DrawSection("Profile Description", scale, () => DrawProfileDescriptionEditor(profile, scale));
        DrawSection("Profile Tags", scale, () => DrawProfileTagsEditor(profile, scale));
        DrawSection("Visibility", scale, () => DrawProfileVisibilityControls());
    }

    private void DrawProfileSnapshot(LightlessUserProfileData profile, float scale)
    {
        var bannerHeight = 140f * scale;
        ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
        if (ImGui.BeginChild("##ProfileBannerPreview", new Vector2(-1f, bannerHeight), true))
        {
            if (_bannerTextureWrap != null)
            {
                var childSize = ImGui.GetWindowSize();
                var padding = ImGui.GetStyle().WindowPadding;
                var contentSize = new Vector2(
                    MathF.Max(childSize.X - padding.X * 2f, 1f),
                    MathF.Max(childSize.Y - padding.Y * 2f, 1f));

                var imageSize = ImGuiHelpers.ScaledVector2(_bannerTextureWrap.Width, _bannerTextureWrap.Height);
                if (imageSize.X > contentSize.X || imageSize.Y > contentSize.Y)
                {
                    var ratio = MathF.Min(contentSize.X / MathF.Max(imageSize.X, 1f), contentSize.Y / MathF.Max(imageSize.Y, 1f));
                    imageSize *= ratio;
                }

                var offset = new Vector2(
                    MathF.Max((contentSize.X - imageSize.X) * 0.5f, 0f),
                    MathF.Max((contentSize.Y - imageSize.Y) * 0.5f, 0f));
                ImGui.SetCursorPos(padding + offset);
                ImGui.Image(_bannerTextureWrap.Handle, imageSize);
            }
            else
            {
                ImGui.TextColored(UIColors.Get("LightlessPurple"), "No Profile Banner");
            }
        }
        ImGui.EndChild();
        ImGui.PopStyleVar();

        ImGui.Dummy(new Vector2(0f, 6f * scale));

        if (_pfpTextureWrap != null)
        {
            var size = ImGuiHelpers.ScaledVector2(_pfpTextureWrap.Width, _pfpTextureWrap.Height);
            var maxEdge = 150f * scale;
            if (size.X > maxEdge || size.Y > maxEdge)
            {
                var ratio = MathF.Min(maxEdge / MathF.Max(size.X, 1f), maxEdge / MathF.Max(size.Y, 1f));
                size *= ratio;
            }

            ImGui.Image(_pfpTextureWrap.Handle, size);
        }
        else
        {
            ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
            if (ImGui.BeginChild("##ProfileImagePlaceholder", new Vector2(150f * scale, 150f * scale), true))
                ImGui.TextColored(UIColors.Get("LightlessPurple"), "No Profile Picture");
            ImGui.EndChild();
            ImGui.PopStyleVar();
        }

        ImGui.Dummy(new Vector2(0f, 4f * scale));
        using (_uiSharedService.GameFont.Push())
        {
            ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
            if (ImGui.BeginChild("##CurrentProfileDescription", new Vector2(-1f, 120f * scale), true))
            {
                ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X);
                if (string.IsNullOrWhiteSpace(profile.Description))
                {
                    ImGui.TextDisabled("-- No description --");
                }
                else if (!SeStringUtils.TryRenderSeStringMarkupAtCursor(profile.Description!))
                {
                    UiSharedService.TextWrapped(profile.Description);
                }
                ImGui.PopTextWrapPos();
            }
            ImGui.EndChild();
            ImGui.PopStyleVar();
        }

    }

    private void DrawProfileImageControls(LightlessUserProfileData profile)
    {
        _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "Profile pictures must be 512x512 and under 2 MiB.");

        if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
        {
            var existingBanner = GetCurrentProfileBannerBase64(profile);
            _fileDialogManager.OpenFileDialog("Select new Profile picture", _imageFileDialogFilter, (success, file) =>
            {
                if (!success) return;
                _ = Task.Run(async () =>
                {
                    var fileContent = File.ReadAllBytes(file);
                    using MemoryStream ms = new(fileContent);
                    var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false);
                    if (!IsSupportedImageFormat(format))
                    {
                        _showProfileImageError = true;
                        return;
                    }

                    using var image = Image.Load<Rgba32>(fileContent);
                    if (image.Width > 512 || image.Height > 512 || fileContent.Length > 2000 * 1024)
                    {
                        _showProfileImageError = true;
                        return;
                    }

                    _showProfileImageError = false;
                    var currentTags = GetServerTagPayload();
                    _queuedProfileImage = fileContent;
                    await _apiController.UserSetProfile(new UserProfileDto(
                        new UserData(_apiController.UID),
                        Disabled: false,
                        IsNSFW: null,
                        ProfilePictureBase64: Convert.ToBase64String(fileContent),
                        BannerPictureBase64: existingBanner,
                        Description: null,
                        Tags: currentTags)).ConfigureAwait(false);
                });
            });
        }
        UiSharedService.AttachToolTip("Select an image up to 512x512 pixels and <= 2 MiB (PNG/JPG/JPEG/WEBP/BMP).");

        ImGui.SameLine();
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear profile picture"))
        {
            _ = _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: null,
                ProfilePictureBase64: string.Empty,
                BannerPictureBase64: GetCurrentProfileBannerBase64(profile),
                Description: null,
                Tags: GetServerTagPayload()));
        }
        UiSharedService.AttachToolTip("Remove your current profile picture.");

        if (_showProfileImageError)
        {
            UiSharedService.ColorTextWrapped("Your profile picture must be no larger than 512x512 pixels and under 2 MiB.", ImGuiColors.DalamudRed);
        }
    }

    private void DrawProfileBannerControls(LightlessUserProfileData profile)
    {
        _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "Profile banners must be 840x260 and under 2 MiB.");

        if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile banner"))
        {
            var existingProfile = GetCurrentProfilePictureBase64(profile);
            _fileDialogManager.OpenFileDialog("Select new Profile banner", _imageFileDialogFilter, (success, file) =>
            {
                if (!success) return;
                _ = Task.Run(async () =>
                {
                    var fileContent = File.ReadAllBytes(file);
                    using MemoryStream ms = new(fileContent);
                    var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false);
                    if (!IsSupportedImageFormat(format))
                    {
                        _showBannerImageError = true;
                        return;
                    }

                    using var image = Image.Load<Rgba32>(fileContent);
                    if (image.Width > 840 || image.Height > 260 || fileContent.Length > 2000 * 1024)
                    {
                        _showBannerImageError = true;
                        return;
                    }

                    _showBannerImageError = false;
                    var currentTags = GetServerTagPayload();
                    await _apiController.UserSetProfile(new UserProfileDto(
                        new UserData(_apiController.UID),
                        Disabled: false,
                        IsNSFW: null,
                        ProfilePictureBase64: existingProfile,
                        BannerPictureBase64: Convert.ToBase64String(fileContent),
                        Description: null,
                        Tags: currentTags)).ConfigureAwait(false);
                });
            });
        }
        UiSharedService.AttachToolTip("Select an image up to 840x260 pixels and <= 2 MiB (PNG/JPG/JPEG/WEBP/BMP).");

        ImGui.SameLine();
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear profile banner"))
        {
            _ = _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: null,
                ProfilePictureBase64: GetCurrentProfilePictureBase64(profile),
                BannerPictureBase64: string.Empty,
                Description: null,
                Tags: GetServerTagPayload()));
        }
        UiSharedService.AttachToolTip("Remove your current profile banner.");

        if (_showBannerImageError)
        {
            UiSharedService.ColorTextWrapped("Your banner image must be no larger than 840x260 pixels and under 2 MiB.", ImGuiColors.DalamudRed);
        }
    }

    private void DrawProfileDescriptionEditor(LightlessUserProfileData profile, float scale)
    {
        ImGui.TextUnformatted($"Description {_descriptionText.Length}/1500");
        ImGui.SameLine();
        ImGuiComponents.HelpMarker(DescriptionMacroTooltip);
        using (_uiSharedService.GameFont.Push())
        {
            var inputSize = new Vector2(-1f, 160f * scale);
            ImGui.InputTextMultiline("##profileDescriptionInput", ref _descriptionText, 1500, inputSize);
        }

        ImGui.Dummy(new Vector2(0f, 3f * scale));
        ImGui.TextColored(UIColors.Get("LightlessBlue"), "Preview");
        using (_uiSharedService.GameFont.Push())
        {
            ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 4f * scale);
            if (ImGui.BeginChild("##profileDescriptionPreview", new Vector2(-1f, 140f * scale), true))
            {
                ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X);
                if (string.IsNullOrWhiteSpace(_descriptionText))
                {
                    ImGui.TextDisabled("-- Description preview --");
                }
                else if (!SeStringUtils.TryRenderSeStringMarkupAtCursor(_descriptionText))
                {
                    UiSharedService.TextWrapped(_descriptionText);
                }
                ImGui.PopTextWrapPos();
            }
            ImGui.EndChild();
            ImGui.PopStyleVar();
        }

        ImGui.Dummy(new Vector2(0f, 4f * scale));
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Description"))
        {
            _ = _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: null,
                ProfilePictureBase64: GetCurrentProfilePictureBase64(profile),
                BannerPictureBase64: GetCurrentProfileBannerBase64(profile),
                _descriptionText,
                Tags: GetServerTagPayload()));
        }
        UiSharedService.AttachToolTip("Apply the text above to your profile.");

        ImGui.SameLine();
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Clear Description"))
        {
            _descriptionText = string.Empty;
            _ = _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: null,
                ProfilePictureBase64: GetCurrentProfilePictureBase64(profile),
                BannerPictureBase64: GetCurrentProfileBannerBase64(profile),
                Description: string.Empty,
                Tags: GetServerTagPayload()));
        }
        UiSharedService.AttachToolTip("Remove the description from your profile.");
    }

    private void DrawProfileTagsEditor(LightlessUserProfileData profile, float scale)
    {
        DrawTagEditor(
            scale,
            contextPrefix: "user",
            saveTooltip: "Apply the selected tags to your profile.",
            submitAction: payload => _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: null,
                ProfilePictureBase64: GetCurrentProfilePictureBase64(profile),
                BannerPictureBase64: GetCurrentProfileBannerBase64(profile),
                Description: null,
                Tags: payload)),
            allowReorder: true,
            sortPayloadBeforeSubmit: false);
    }

    private void DrawTagEditor(
        float scale,
        string contextPrefix,
        string saveTooltip,
        Func<int[], Task> submitAction,
        bool allowReorder,
        bool sortPayloadBeforeSubmit,
        Action<int[]>? onPayloadPrepared = null)
    {
        var tagLibrary = ProfileTagService.GetTagLibrary();
        if (tagLibrary.Count == 0)
        {
            ImGui.TextDisabled("No profile tags are available.");
            return;
        }

        var style = ImGui.GetStyle();
        var defaultTextColorU32 = ImGui.GetColorU32(ImGuiCol.Text);

        var selectedCount = _tagEditorSelection.Count;
        ImGui.TextColored(UIColors.Get("LightlessBlue"), $"Selected Tags ({selectedCount}/{_maxProfileTags})");

        int? tagToRemove = null;
        int? moveUpRequest = null;
        int? moveDownRequest = null;

        if (selectedCount == 0)
        {
            ImGui.TextDisabled("-- No tags selected --");
        }
        else
        {
            var selectedFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.SizingStretchSame;
            var selectedTableId = $"##{contextPrefix}SelectedTagsTable";
            var columnCount = allowReorder ? 3 : 2;

            if (ImGui.BeginTable(selectedTableId, columnCount, selectedFlags))
            {
                ImGui.TableSetupColumn("Preview", ImGuiTableColumnFlags.WidthStretch, allowReorder ? 0.55f : 0.75f);
                if (allowReorder)
                    ImGui.TableSetupColumn("##order", ImGuiTableColumnFlags.WidthStretch, 0.1f);
                ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 90f);
                ImGui.TableHeadersRow();

                for (int i = 0; i < _tagEditorSelection.Count; i++)
                {
                    var tagId = _tagEditorSelection[i];
                    if (!tagLibrary.TryGetValue(tagId, out var definition) || !definition.HasContent)
                        continue;

                    var displayName = GetTagDisplayName(definition, tagId);
                    var idLabel = $"ID {tagId}";
                    var previewSize = ProfileTagRenderer.MeasureTag(definition, scale, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _tagPreviewSegments, ResolveIconWrap, _logger);
                    var textHeight = ImGui.CalcTextSize(displayName).Y + style.ItemSpacing.Y + ImGui.CalcTextSize(idLabel).Y;
                    var rowHeight = MathF.Max(previewSize.Y + style.CellPadding.Y * 2f, textHeight + style.CellPadding.Y * 2f);

                    ImGui.TableNextRow(ImGuiTableRowFlags.None, rowHeight);
                    ImGui.TableNextColumn();
                    using (ImRaii.PushId($"{contextPrefix}-selected-tag-{tagId}-{i}"))
                        DrawCenteredTagCell(definition, scale, previewSize, rowHeight, defaultTextColorU32);
                    if (ImGui.IsItemHovered())
                    {
                        ImGui.BeginTooltip();
                        ImGui.TextUnformatted(displayName);
                        ImGui.TextDisabled(idLabel);
                        ImGui.EndTooltip();
                    }

                    if (allowReorder)
                    {
                        ImGui.TableNextColumn();
                        DrawReorderCell(contextPrefix, tagId, i, _tagEditorSelection.Count, rowHeight, scale, ref moveUpRequest, ref moveDownRequest);
                    }

                    ImGui.TableNextColumn();
                    DrawFullCellButton("Remove", UIColors.Get("DimRed"), ref tagToRemove, tagId, false, scale, rowHeight, $"{contextPrefix}-remove-{tagId}-{i}");
                }

                ImGui.EndTable();
            }
        }

        if (allowReorder)
        {
            if (moveUpRequest.HasValue && moveUpRequest.Value > 0)
            {
                var idx = moveUpRequest.Value;
                (_tagEditorSelection[idx - 1], _tagEditorSelection[idx]) = (_tagEditorSelection[idx], _tagEditorSelection[idx - 1]);
            }

            if (moveDownRequest.HasValue && moveDownRequest.Value < _tagEditorSelection.Count - 1)
            {
                var idx = moveDownRequest.Value;
                (_tagEditorSelection[idx], _tagEditorSelection[idx + 1]) = (_tagEditorSelection[idx + 1], _tagEditorSelection[idx]);
            }
        }

        if (tagToRemove.HasValue)
            _tagEditorSelection.Remove(tagToRemove.Value);

        bool limitReached = _tagEditorSelection.Count >= _maxProfileTags;
        if (limitReached)
            UiSharedService.ColorTextWrapped($"You have reached the maximum of {_maxProfileTags} tags. Remove one before adding more.", UIColors.Get("DimRed"));

        ImGui.Dummy(new Vector2(0f, 6f * scale));
        ImGui.TextColored(UIColors.Get("LightlessPurple"), "Available Tags");

        var availableIds = new List<int>(tagLibrary.Count);
        var seenDefinitions = new HashSet<ProfileTagDefinition>();
        foreach (var kvp in tagLibrary)
        {
            var definition = kvp.Value;
            if (!definition.HasContent)
                continue;

            if (!seenDefinitions.Add(definition))
                continue;

            if (_tagEditorSelection.Contains(kvp.Key))
                continue;

            availableIds.Add(kvp.Key);
        }

        availableIds.Sort();
        int totalAvailable = availableIds.Count;
        if (totalAvailable == 0)
        {
            ImGui.TextDisabled("-- No additional tags available --");
        }
        else
        {
            int pageCount = Math.Max(1, (totalAvailable + _availableTagsPerPage - 1) / _availableTagsPerPage);
            _availableTagPage = Math.Clamp(_availableTagPage, 0, pageCount - 1);
            int start = _availableTagPage * _availableTagsPerPage;
            int end = Math.Min(totalAvailable, start + _availableTagsPerPage);

            ImGui.SameLine();
            ImGui.TextDisabled($"Page {_availableTagPage + 1}/{pageCount}");
            ImGui.SameLine();
            ImGui.BeginDisabled(_availableTagPage == 0);
            if (ImGui.SmallButton($"<##{contextPrefix}TagPagePrev"))
                _availableTagPage--;
            ImGui.EndDisabled();
            ImGui.SameLine();
            ImGui.BeginDisabled(_availableTagPage >= pageCount - 1);
            if (ImGui.SmallButton($">##{contextPrefix}TagPageNext"))
                _availableTagPage++;
            ImGui.EndDisabled();

            var availableFlags = ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.SizingStretchSame;
            int? tagToAdd = null;

            if (ImGui.BeginTable($"##{contextPrefix}AvailableTagsTable", 2, availableFlags))
            {
                ImGui.TableSetupColumn("Preview", ImGuiTableColumnFlags.WidthStretch, 0.75f);
                ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.WidthFixed, 90f);
                ImGui.TableHeadersRow();

                for (int idx = start; idx < end; idx++)
                {
                    var tagId = availableIds[idx];
                    if (!tagLibrary.TryGetValue(tagId, out var definition) || !definition.HasContent)
                        continue;

                    var previewSize = ProfileTagRenderer.MeasureTag(definition, scale, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _tagPreviewSegments, ResolveIconWrap, _logger);
                    var rowHeight = previewSize.Y + style.CellPadding.Y * 2f;

                    ImGui.TableNextRow(ImGuiTableRowFlags.None, rowHeight);
                    ImGui.TableNextColumn();
                    using (ImRaii.PushId($"{contextPrefix}-available-tag-{tagId}"))
                        DrawCenteredTagCell(definition, scale, previewSize, rowHeight, defaultTextColorU32);
                    if (ImGui.IsItemHovered())
                    {
                        var name = GetTagDisplayName(definition, tagId);
                        ImGui.BeginTooltip();
                        ImGui.TextUnformatted(name);
                        ImGui.TextDisabled($"ID {tagId}");
                        ImGui.EndTooltip();
                    }

                    ImGui.TableNextColumn();
                    DrawFullCellButton("Add", UIColors.Get("LightlessGreen"), ref tagToAdd, tagId, limitReached, scale, rowHeight, $"{contextPrefix}-add-{tagId}");
                }

                ImGui.EndTable();
            }

            if (tagToAdd.HasValue)
            {
                _tagEditorSelection.Add(tagToAdd.Value);
                if (_availableTagPage > 0 && (totalAvailable - 1) <= start)
                    _availableTagPage = Math.Max(0, _availableTagPage - 1);
            }
        }

        bool hasChanges = !TagsEqual(_tagEditorSelection, _profileTagIds);
        ImGui.Dummy(new Vector2(0f, 6f * scale));
        if (!hasChanges)
            ImGui.BeginDisabled();

        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, $"Save Tags##{contextPrefix}"))
        {
            var payload = _tagEditorSelection.Count == 0 ? Array.Empty<int>() : _tagEditorSelection.ToArray();
            if (sortPayloadBeforeSubmit && payload.Length > 1)
                Array.Sort(payload);
            onPayloadPrepared?.Invoke(payload);
            _ = submitAction(payload);
        }

        if (!hasChanges)
            ImGui.EndDisabled();

        UiSharedService.AttachToolTip(saveTooltip);
    }

    private void DrawProfileVisibilityControls()
    {
        if (!_isNsfwInitialized)
            ImGui.BeginDisabled();

        bool changed = DrawCheckboxRow("Mark profile as NSFW", _isNsfw, out var newValue, "Enable when your profile could be considered NSFW.");

        if (changed)
            _isNsfw = newValue;

        bool visibilityChanged = _isNsfwInitialized && (_isNsfw != _userServerIsNsfw);

        if (!_isNsfwInitialized)
            ImGui.EndDisabled();

        if (!_isNsfwInitialized || !visibilityChanged)
            ImGui.BeginDisabled();

        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Apply Visibility Changes"))
        {
            _userServerIsNsfw = _isNsfw;

            _ = _apiController.UserSetProfile(new UserProfileDto(
                new UserData(_apiController.UID),
                Disabled: false,
                IsNSFW: _isNsfw,
                ProfilePictureBase64: null,
                BannerPictureBase64: null,
                Description: null,
                Tags: null));
        }

        UiSharedService.AttachToolTip("Apply the visibility toggles above.");

        if (!_isNsfwInitialized || !visibilityChanged)
            ImGui.EndDisabled();

        ImGui.SameLine();
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.SyncAlt, "Reset") && _isNsfwInitialized)
            _isNsfw = _userServerIsNsfw;
    }

    private string? GetCurrentProfilePictureBase64(LightlessUserProfileData profile)
    {
        if (_queuedProfileImage is { Length: > 0 })
            return Convert.ToBase64String(_queuedProfileImage);

        if (!string.IsNullOrWhiteSpace(profile.Base64ProfilePicture) && !string.Equals(profile.Description, LoadingProfileDescription, StringComparison.Ordinal))
            return profile.Base64ProfilePicture;

        return _profileImage.Length > 0 ? Convert.ToBase64String(_profileImage) : null;
    }

    private string? GetCurrentProfileBannerBase64(LightlessUserProfileData profile)
    {
        if (!string.IsNullOrWhiteSpace(profile.Base64BannerPicture) && !string.Equals(profile.Description, LoadingProfileDescription, StringComparison.Ordinal))
            return profile.Base64BannerPicture;

        return _bannerImage.Length > 0 ? Convert.ToBase64String(_bannerImage) : null;
    }


    private static bool IsSupportedImageFormat(IImageFormat? format)
    {
        if (format is null)
            return false;

        foreach (var ext in format.FileExtensions)
        {
            if (_supportedImageExtensions.Contains(ext))
                return true;
        }

        return false;
    }

    private void DrawCenteredTagCell(ProfileTagDefinition tag, float scale, Vector2 tagSize, float rowHeight, uint defaultTextColorU32)
    {
        var style = ImGui.GetStyle();
        var cellStart = ImGui.GetCursorPos();
        var available = ImGui.GetContentRegionAvail();
        var innerHeight = MathF.Max(0f, rowHeight - style.CellPadding.Y * 2f);
        var offsetX = MathF.Max(0f, (available.X - tagSize.X) * 0.5f);
        var offsetY = MathF.Max(0f, innerHeight - tagSize.Y) * 0.5f;

        ImGui.SetCursorPos(new Vector2(cellStart.X + offsetX, cellStart.Y + style.CellPadding.Y + offsetY));
        ImGui.InvisibleButton("##tagPreview", tagSize);
        var rectMin = ImGui.GetItemRectMin();
        var drawList = ImGui.GetWindowDrawList();
        ProfileTagRenderer.RenderTag(tag, rectMin, scale, drawList, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _tagPreviewSegments, ResolveIconWrap, _logger);
    }

    private void DrawReorderCell(
        string contextPrefix,
        int tagId,
        int index,
        int count,
        float rowHeight,
        float scale,
        ref int? moveUpTarget,
        ref int? moveDownTarget)
    {
        var style = ImGui.GetStyle();
        var cellStart = ImGui.GetCursorPos();
        var availableWidth = ImGui.GetContentRegionAvail().X;
        var innerHeight = MathF.Max(0f, rowHeight - style.CellPadding.Y * 2f);
        var spacing = MathF.Min(style.ItemSpacing.Y * 0.5f, innerHeight * 0.15f);
        var buttonHeight = MathF.Max(1f, (innerHeight - spacing) * 0.5f);
        var width = MathF.Max(1f, availableWidth);

        var upColor = UIColors.Get("LightlessBlue");
        using (ImRaii.PushId($"{contextPrefix}-order-{tagId}-{index}"))
        {
            ImGui.SetCursorPos(new Vector2(cellStart.X, cellStart.Y + style.CellPadding.Y));
            if (ColoredButton("\u2191##tagMoveUp", upColor, new Vector2(width, buttonHeight), scale, index == 0))
                moveUpTarget = index;

            ImGui.SetCursorPos(new Vector2(cellStart.X, cellStart.Y + style.CellPadding.Y + buttonHeight + spacing));
            if (ColoredButton("\u2193##tagMoveDown", upColor, new Vector2(width, buttonHeight), scale, index >= count - 1))
                moveDownTarget = index;
        }
    }

    private void DrawFullCellButton(string label, Vector4 baseColor, ref int? target, int tagId, bool disabled, float scale, float rowHeight, string idSuffix)
    {
        var style = ImGui.GetStyle();
        var cellStart = ImGui.GetCursorPos();
        var available = ImGui.GetContentRegionAvail();
        var buttonHeight = MathF.Max(1f, rowHeight - style.CellPadding.Y * 2f);

        ImGui.SetCursorPos(new Vector2(cellStart.X, cellStart.Y + style.CellPadding.Y));
        using (ImRaii.PushId(idSuffix))
        {
            if (ColoredButton(label, baseColor, new Vector2(MathF.Max(available.X, 1f), buttonHeight), scale, disabled))
                target = tagId;
        }
    }

    private static bool ColoredButton(string label, Vector4 baseColor, Vector2 size, float scale, bool disabled)
    {
        var style = ImGui.GetStyle();
        var hovered = BlendTowardsWhite(baseColor, 0.15f);
        var active = BlendTowardsWhite(baseColor, 0.3f);

        ImGui.PushStyleColor(ImGuiCol.Button, baseColor);
        ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hovered);
        ImGui.PushStyleColor(ImGuiCol.ButtonActive, active);
        ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, style.FrameRounding);
        ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(MathF.Max(2f, style.FramePadding.X), MathF.Max(1f, style.FramePadding.Y * 0.3f)) * scale);

        ImGui.BeginDisabled(disabled);
        bool clicked = ImGui.Button(label, size);
        ImGui.EndDisabled();

        ImGui.PopStyleVar(2);
        ImGui.PopStyleColor(3);

        return clicked;
    }

    private static float Clamp01(float value)
        => value < 0f ? 0f : value > 1f ? 1f : value;

    private static Vector4 BlendTowardsWhite(Vector4 color, float amount)
    {
        var result = Vector4.Lerp(color, Vector4.One, Clamp01(amount));
        result.W = color.W;
        return result;
    }

    private static string GetTagDisplayName(ProfileTagDefinition tag, int tagId)
    {
        if (!string.IsNullOrWhiteSpace(tag.Text))
            return tag.Text!;

        if (!string.IsNullOrWhiteSpace(tag.SeStringPayload))
        {
            var stripped = SeStringUtils.StripMarkup(tag.SeStringPayload!);
            if (!string.IsNullOrWhiteSpace(stripped))
                return stripped;
        }

        return $"Tag {tagId}";
    }

    private IDalamudTextureWrap? ResolveIconWrap(uint iconId)
    {
        if (_uiSharedService.TryGetIcon(iconId, out var wrap) && wrap != null)
            return wrap;
        return null;
    }

    private int[] GetServerTagPayload()
    {
        if (_profileTagIds.Length == 0)
            return Array.Empty<int>();

        var copy = new int[_profileTagIds.Length];
        Array.Copy(_profileTagIds, copy, _profileTagIds.Length);
        return copy;
    }

    private static bool TagsEqual(IReadOnlyList<int>? current, IReadOnlyList<int>? reference)
    {
        if (ReferenceEquals(current, reference))
            return true;
        if (current is null || reference is null)
            return false;
        if (current.Count != reference.Count)
            return false;

        for (int i = 0; i < current.Count; i++)
        {
            if (current[i] != reference[i])
                return false;
        }

        return true;
    }

    private void DrawVanityTabContent(float scale)
    {
        DrawSection("Colored UID", scale, () =>
        {
            var hasVanity = _apiController.HasVanity;
            if (!hasVanity)
            {
                UiSharedService.ColorTextWrapped("You do not currently have vanity access. Become a supporter to unlock these features. (If you already are, interact with the bot to update)", UIColors.Get("DimRed"));
            }

            var monoFont = UiBuilder.MonoFont;
            using (ImRaii.PushFont(monoFont))
            {
                var previewTextColor = _textEnabled ? _textColor : Vector4.One;
                var previewGlowColor = _glowEnabled ? _glowColor : Vector4.Zero;
                var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.DisplayName, previewTextColor, previewGlowColor);

                var drawList = ImGui.GetWindowDrawList();
                var textSize = ImGui.CalcTextSize(seString.TextValue);
                float minWidth = 160f * ImGuiHelpers.GlobalScale;
                float paddingY = 5f * ImGuiHelpers.GlobalScale;
                float paddingX = 10f * ImGuiHelpers.GlobalScale;
                float bgWidth = Math.Max(textSize.X + paddingX * 2f, minWidth);

                var style = ImGui.GetStyle();
                var fontHeight = monoFont.FontSize > 0f ? monoFont.FontSize : ImGui.GetFontSize();
                float frameHeight = fontHeight + style.FramePadding.Y * 2f;
                float textBlockHeight = MathF.Max(frameHeight, textSize.Y);

                var cursor = ImGui.GetCursorScreenPos();
                var rectMin = cursor;
                var rectMax = rectMin + new Vector2(bgWidth, textBlockHeight + paddingY * 2f);

                float boost = Luminance.ComputeHighlight(previewTextColor, previewGlowColor);

                var baseBg = new Vector4(0.15f + boost, 0.15f + boost, 0.15f + boost, 1f);
                var bgColor = Luminance.BackgroundContrast(previewTextColor, previewGlowColor, baseBg, ref _currentBg);
                var borderColor = UIColors.Get("LightlessPurple");

                drawList.AddRectFilled(rectMin, rectMax, ImGui.GetColorU32(bgColor), 5f);
                drawList.AddRect(rectMin, rectMax, ImGui.GetColorU32(borderColor), 5f, ImDrawFlags.None, 1.2f);

                var textOrigin = new Vector2(rectMin.X + (bgWidth - textSize.X) * 0.5f, rectMin.Y + paddingY);
                SeStringUtils.RenderSeStringWithHitbox(seString, textOrigin, monoFont);

                ImGui.Dummy(new Vector2(0f, 1.5f));
            }

            ImGui.TextColored(UIColors.Get("LightlessPurple"), "Colors");
            if (!hasVanity)
                ImGui.BeginDisabled();

            if (DrawCheckboxRow("Enable custom text color", _textEnabled, out var newTextEnabled))
                _textEnabled = newTextEnabled;

            ImGui.SameLine();
            ImGui.BeginDisabled(!_textEnabled);
            ImGui.ColorEdit4("Text Color##vanityTextColor", ref _textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
            ImGui.EndDisabled();

            if (DrawCheckboxRow("Enable glow color", _glowEnabled, out var newGlowEnabled))
                _glowEnabled = newGlowEnabled;

            ImGui.SameLine();
            ImGui.BeginDisabled(!_glowEnabled);
            ImGui.ColorEdit4("Glow Color##vanityGlowColor", ref _glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
            ImGui.EndDisabled();

            bool changed = !Equals(_savedVanity, new VanityState(_textEnabled, _glowEnabled, _textColor, _glowColor));
            if (!changed)
                ImGui.BeginDisabled();

            if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Vanity Changes"))
            {
                string? newText = _textEnabled ? UIColors.RgbaToHex(_textColor) : string.Empty;
                string? newGlow = _glowEnabled ? UIColors.RgbaToHex(_glowColor) : string.Empty;

                _ = _apiController.UserUpdateVanityColors(new UserVanityColorsDto(newText, newGlow));
                _savedVanity = new VanityState(_textEnabled, _glowEnabled, _textColor, _glowColor);
            }

            if (!changed)
                ImGui.EndDisabled();

            if (!hasVanity)
                ImGui.EndDisabled();
        });
    }

    private static void DrawSection(string title, float scale, Action body)
    {
        ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(6f, 4f) * scale);

        var flags = ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Framed | ImGuiTreeNodeFlags.AllowItemOverlap | ImGuiTreeNodeFlags.DefaultOpen;
        var open = ImGui.CollapsingHeader(title, flags);
        ImGui.PopStyleVar();

        if (open)
        {
            ImGui.Dummy(new Vector2(0f, 3f * scale));
            body();
            ImGui.Dummy(new Vector2(0f, 2f * scale));
        }
    }

    private static bool DrawCheckboxRow(string label, bool currentValue, out bool newValue, string? tooltip = null)
    {
        bool value = currentValue;
        bool changed = UiSharedService.CheckboxWithBorder(label, ref value, UIColors.Get("LightlessPurple"), 1.5f);
        if (!string.IsNullOrEmpty(tooltip))
            UiSharedService.AttachToolTip(tooltip);

        newValue = value;
        return changed;
    }

    private void SyncProfileState(LightlessUserProfileData profile)
    {
        if (string.Equals(profile.Description, LoadingProfileDescription, StringComparison.Ordinal))
        {
            _isNsfwInitialized = false;
            return;
        }

        if (!_isNsfwInitialized)
        {
            _userServerIsNsfw = profile.IsNSFW;
            _isNsfw = profile.IsNSFW;
            _isNsfwInitialized = true;
        }

        var profileBytes = profile.ImageData.Value;
        if (_pfpTextureWrap == null || !_profileImage.SequenceEqual(profileBytes))
        {
            _profileImage = profileBytes;
            _pfpTextureWrap?.Dispose();
            _pfpTextureWrap = _profileImage.Length > 0 ? _uiSharedService.LoadImage(_profileImage) : null;
            _queuedProfileImage = null;
        }

        var bannerBytes = profile.BannerImageData.Value;
        if (_bannerTextureWrap == null || !_bannerImage.SequenceEqual(bannerBytes))
        {
            _bannerImage = bannerBytes;
            _bannerTextureWrap?.Dispose();
            _bannerTextureWrap = _bannerImage.Length > 0 ? _uiSharedService.LoadImage(_bannerImage) : null;
        }

        if (!string.Equals(_profileDescription, profile.Description, StringComparison.Ordinal))
        {
            _profileDescription = profile.Description;
            _descriptionText = _profileDescription;
        }

        var serverTags = profile.Tags ?? [];
        if (!TagsEqual(serverTags, _profileTagIds))
        {
            var previous = _profileTagIds;
            _profileTagIds = serverTags.Count == 0 ? [] : [.. serverTags];

            if (TagsEqual(_tagEditorSelection, previous))
            {
                _tagEditorSelection.Clear();
                if (_profileTagIds.Length > 0)
                    _tagEditorSelection.AddRange(_profileTagIds);
            }
        }
    }

    private static bool IsWindowBeingDragged()
    {
        return ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows) && ImGui.GetIO().MouseDown[0];
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _pfpTextureWrap?.Dispose();
        _bannerTextureWrap?.Dispose();
    }
}