using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using LightlessSync.UI.Style;
using LightlessSync.UI.Tags;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging;
using System.Numerics;

namespace LightlessSync.UI;

public class LightFinderUI : WindowMediatorSubscriberBase
{
    #region Services

    private readonly ActorObjectService _actorObjectService;
    private readonly ApiController _apiController;
    private readonly DalamudUtilService _dalamudUtilService;
    private readonly LightFinderScannerService _broadcastScannerService;
    private readonly LightFinderService _broadcastService;
    private readonly LightlessConfigService _configService;
    private readonly LightlessProfileManager _lightlessProfileManager;
    private readonly LightFinderPlateHandler _lightFinderPlateHandler;
    private readonly PairUiService _pairUiService;
    private readonly UiSharedService _uiSharedService;

    #endregion

    #region UI Components

    private readonly AnimatedHeader _animatedHeader = new();
    private readonly List<SeStringUtils.SeStringSegment> _seResolvedSegments = new();
    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);

    #endregion

    #region State

    private IReadOnlyList<GroupFullInfoDto> _allSyncshells = Array.Empty<GroupFullInfoDto>();
    private bool _compactView;
    private List<GroupFullInfoDto> _currentSyncshells = [];
    private GroupJoinDto? _joinDto;
    private GroupJoinInfoDto? _joinInfo;
    private bool _joinModalOpen = true;
    private readonly List<GroupJoinDto> _nearbySyncshells = [];
    private DefaultPermissionsDto _ownPermissions = null!;
    private readonly HashSet<string> _recentlyJoined = new(StringComparer.Ordinal);
    private int _selectedNearbyIndex = -1;
    private readonly List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
    private LightfinderTab _selectedTab = LightfinderTab.NearbySyncshells;
    private string _userUid = string.Empty;
    
    private const float AnimationSpeed = 6f;
    private readonly Dictionary<string, float> _itemAlpha = new(StringComparer.Ordinal);
    private readonly HashSet<string> _currentVisibleItems = new(StringComparer.Ordinal);
    private readonly HashSet<string> _previousVisibleItems = new(StringComparer.Ordinal);
    
    private enum LightfinderTab { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help }
    
#if DEBUG
    private enum LightfinderTabDebug { NearbySyncshells, NearbyPlayers, BroadcastSettings, Help, Debug }
    private LightfinderTabDebug _selectedTabDebug = LightfinderTabDebug.NearbySyncshells;
#endif

#if DEBUG
    private bool _useTestSyncshells;
#endif

    #endregion

    #region Constructor

    public LightFinderUI(
        ILogger<LightFinderUI> logger,
        LightlessMediator mediator,
        PerformanceCollectorService performanceCollectorService,
        LightFinderService broadcastService,
        LightlessConfigService configService,
        UiSharedService uiShared,
        ApiController apiController,
        LightFinderScannerService broadcastScannerService,
        PairUiService pairUiService,
        DalamudUtilService dalamudUtilService,
        LightlessProfileManager lightlessProfileManager,
        ActorObjectService actorObjectService
,
        LightFinderPlateHandler lightFinderPlateHandler) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
    {
        _broadcastService = broadcastService;
        _uiSharedService = uiShared;
        _configService = configService;
        _apiController = apiController;
        _broadcastScannerService = broadcastScannerService;
        _pairUiService = pairUiService;
        _dalamudUtilService = dalamudUtilService;
        _lightlessProfileManager = lightlessProfileManager;
        _actorObjectService = actorObjectService;

        _animatedHeader.Height = 85f;
        _animatedHeader.EnableBottomGradient = true;
        _animatedHeader.GradientHeight = 90f;
        _animatedHeader.EnableParticles = _configService.Current.EnableParticleEffects;

        IsOpen = false;
        WindowBuilder.For(this)
            .SetSizeConstraints(new Vector2(620, 85), new Vector2(700, 2000))
            .Apply();

        Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshNearbySyncshellsAsync().ConfigureAwait(false));
        Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshNearbySyncshellsAsync().ConfigureAwait(false));
        Mediator.Subscribe<UserLeftSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
        Mediator.Subscribe<UserJoinedSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
        _lightFinderPlateHandler = lightFinderPlateHandler;
    }

    #endregion

    #region Lifecycle

    public override void OnOpen()
    {
        _userUid = _apiController.UID;
        _ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
        _ = RefreshSyncshellsAsync();
        _ = RefreshNearbySyncshellsAsync();
    }

    public override void OnClose()
    {
        _animatedHeader.ClearParticles();
        ClearSelection();
        base.OnClose();
    }

    #endregion

    #region Main Drawing

    protected override void DrawInternal()
    {
        var contentWidth = ImGui.GetContentRegionAvail().X;
        _animatedHeader.Draw(contentWidth, (_, _) => { });

        if (!_broadcastService.IsLightFinderAvailable)
        {
            ImGui.TextColored(UIColors.Get("LightlessYellow"), "This server doesn't support Lightfinder.");
            ImGuiHelpers.ScaledDummy(2f);
        }

        DrawStatusPanel();
        ImGuiHelpers.ScaledDummy(4f);

#if DEBUG
        var debugTabOptions = new List<UiSharedService.TabOption<LightfinderTabDebug>>
        {
            new("Nearby Syncshells", LightfinderTabDebug.NearbySyncshells),
            new("Nearby Players", LightfinderTabDebug.NearbyPlayers),
            new("Broadcast", LightfinderTabDebug.BroadcastSettings),
            new("Help", LightfinderTabDebug.Help),
            new("Debug", LightfinderTabDebug.Debug)
        };
        UiSharedService.Tab("LightfinderTabs", debugTabOptions, ref _selectedTabDebug);

        ImGuiHelpers.ScaledDummy(4f);

        switch (_selectedTabDebug)
        {
            case LightfinderTabDebug.NearbySyncshells:
                DrawNearbySyncshellsTab();
                break;
            case LightfinderTabDebug.NearbyPlayers:
                DrawNearbyPlayersTab();
                break;
            case LightfinderTabDebug.BroadcastSettings:
                DrawBroadcastSettingsTab();
                break;
            case LightfinderTabDebug.Help:
                DrawHelpTab();
                break;
            case LightfinderTabDebug.Debug:
                DrawDebugTab();
                break;
        }
#else
        var tabOptions = new List<UiSharedService.TabOption<LightfinderTab>>
        {
            new("Nearby Syncshells", LightfinderTab.NearbySyncshells),
            new("Nearby Players", LightfinderTab.NearbyPlayers),
            new("Broadcast", LightfinderTab.BroadcastSettings),
            new("Help", LightfinderTab.Help)
        };
        UiSharedService.Tab("LightfinderTabs", tabOptions, ref _selectedTab);
        
        ImGuiHelpers.ScaledDummy(4f);
        
        switch (_selectedTab)
        {
            case LightfinderTab.NearbySyncshells:
                DrawNearbySyncshellsTab();
                break;
            case LightfinderTab.NearbyPlayers:
                DrawNearbyPlayersTab();
                break;
            case LightfinderTab.BroadcastSettings:
                DrawBroadcastSettingsTab();
                break;
            case LightfinderTab.Help:
                DrawHelpTab();
                break;
        }
#endif

        if (_joinDto != null && _joinInfo != null && _joinInfo.Success)
            DrawJoinConfirmation();
    }

    private void DrawStatusPanel()
    {
        var scale = ImGuiHelpers.GlobalScale;
        var isBroadcasting = _broadcastService.IsBroadcasting;
        var cooldown = _broadcastService.RemainingCooldown;
        var isOnCooldown = cooldown.HasValue && cooldown.Value.TotalSeconds > 0;

        var accent = isBroadcasting ? UIColors.Get("LightlessGreen") : UIColors.Get("LightlessPurple");
        var accentBg = new Vector4(accent.X, accent.Y, accent.Z, 0.16f);
        var accentBorder = new Vector4(accent.X, accent.Y, accent.Z, 0.32f);
        var infoColor = ImGuiColors.DalamudGrey;

        var summaryHeight = MathF.Max(ImGui.GetTextLineHeightWithSpacing() * 2.4f, 46f * scale);
        float buttonWidth = 130 * scale;

        using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 6f * scale))
        using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, MathF.Max(1f, ImGui.GetStyle().ChildBorderSize)))
        using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(12f * scale, 6f * scale)))
        using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(18f * scale, 4f * scale)))
        using (ImRaii.PushColor(ImGuiCol.ChildBg, UiSharedService.Color(accentBg)))
        using (ImRaii.PushColor(ImGuiCol.Border, UiSharedService.Color(accentBorder)))
        using (var child = ImRaii.Child("StatusPanel", new Vector2(-1f, summaryHeight), true, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse))
        {
            if (child)
            {
                using (ImRaii.PushStyle(ImGuiStyleVar.CellPadding, new Vector2(8f * scale, 4f * scale)))
                {
                    if (ImGui.BeginTable("StatusPanelTable", 6, ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.NoBordersInBody))
                    {
                        ImGui.TableSetupColumn("Status", ImGuiTableColumnFlags.WidthStretch, 1f);
                        ImGui.TableSetupColumn("Time", ImGuiTableColumnFlags.WidthStretch, 1f);
                        ImGui.TableSetupColumn("NearbySyncshells", ImGuiTableColumnFlags.WidthStretch, 1f);
                        ImGui.TableSetupColumn("NearbyPlayers", ImGuiTableColumnFlags.WidthStretch, 1f);
                        ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch, 1f);
                        ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed, buttonWidth + 16f * scale);

                        ImGui.TableNextRow();

                        // Status cell
                        var statusColor = isBroadcasting ? UIColors.Get("LightlessGreen") : UIColors.Get("DimRed");
                        var statusText = isBroadcasting ? "Active" : "Inactive";
                        var statusIcon = isBroadcasting ? FontAwesomeIcon.CheckCircle : FontAwesomeIcon.TimesCircle;
                        DrawStatusCell(statusIcon, statusColor, statusText, "Status", infoColor, scale);

                        // Time remaining Cooldown cell
                        string timeValue;
                        string timeSub;
                        Vector4 timeColor;
                        if (isOnCooldown)
                        {
                            timeValue = $"{Math.Ceiling(cooldown!.Value.TotalSeconds)}s";
                            timeSub = "Cooldown";
                            timeColor = UIColors.Get("DimRed");
                        }
                        else if (isBroadcasting && _broadcastService.RemainingTtl is { } remaining && remaining > TimeSpan.Zero)
                        {
                            timeValue = $"{remaining:hh\\:mm\\:ss}";
                            timeSub = "Time left";
                            timeColor = UIColors.Get("LightlessYellow");
                        }
                        else
                        {
                            timeValue = "--:--:--";
                            timeSub = "Time left";
                            timeColor = infoColor;
                        }
                        DrawStatusCell(FontAwesomeIcon.Clock, timeColor, timeValue, timeSub, infoColor, scale);

                        // Nearby syncshells cell
                        var nearbySyncshellCount = _nearbySyncshells.Count;
                        var nearbySyncshellColor = nearbySyncshellCount > 0 ? UIColors.Get("LightlessPurple") : infoColor;
                        DrawStatusCell(FontAwesomeIcon.Compass, nearbySyncshellColor, nearbySyncshellCount.ToString(), "Syncshells", infoColor, scale);

                        // Nearby players cell (exclude self)
                        string? myHashedCidForCount = null;
                        try { myHashedCidForCount = _dalamudUtilService.GetCID().ToString().GetHash256(); } catch { }
                        var nearbyPlayerCount = _broadcastScannerService.CountActiveBroadcasts(myHashedCidForCount);
                        var nearbyPlayerColor = nearbyPlayerCount > 0 ? UIColors.Get("LightlessBlue") : infoColor;
                        DrawStatusCell(FontAwesomeIcon.Users, nearbyPlayerColor, nearbyPlayerCount.ToString(), "Players", infoColor, scale);

                        // Broadcasting syncshell cell
                        var isBroadcastingSyncshell = _configService.Current.SyncshellFinderEnabled && isBroadcasting;
                        var broadcastSyncshellColor = isBroadcastingSyncshell ? UIColors.Get("LightlessGreen") : infoColor;
                        var broadcastSyncshellText = isBroadcastingSyncshell ? "Yes" : "No";
                        var broadcastSyncshellIcon = FontAwesomeIcon.Wifi;
                        DrawStatusCell(broadcastSyncshellIcon, broadcastSyncshellColor, broadcastSyncshellText, "Broadcasting", infoColor, scale);

                        // Enable/Disable button cell - right aligned
                        ImGui.TableNextColumn();

                        float cellWidth = ImGui.GetContentRegionAvail().X;
                        float offsetX = cellWidth - buttonWidth;
                        if (offsetX > 0)
                            ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offsetX);

                        using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 6f * scale))
                        {
                            Vector4 buttonColor;
                            if (isOnCooldown)
                                buttonColor = UIColors.Get("DimRed");
                            else if (isBroadcasting)
                                buttonColor = UIColors.Get("LightlessGreen");
                            else
                                buttonColor = UIColors.Get("LightlessPurple");

                            using (ImRaii.PushColor(ImGuiCol.Button, buttonColor))
                            using (ImRaii.PushColor(ImGuiCol.ButtonHovered, buttonColor.WithAlpha(0.85f)))
                            using (ImRaii.PushColor(ImGuiCol.ButtonActive, buttonColor.WithAlpha(0.75f)))
                            using (ImRaii.Disabled(isOnCooldown || !_broadcastService.IsLightFinderAvailable))
                            {
                                string buttonText = isBroadcasting ? "Disable" : "Enable";
                                if (ImGui.Button(buttonText, new Vector2(buttonWidth, 0)))
                                    _broadcastService.ToggleBroadcast();
                            }
                        }

                        ImGui.EndTable();
                    }
                }
            }
        }
    }

    private void DrawStatusCell(FontAwesomeIcon icon, Vector4 iconColor, string mainText, string subText, Vector4 subColor, float scale)
    {
        ImGui.TableNextColumn();
        using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(6f * scale, 2f * scale)))
        using (ImRaii.Group())
        {
            _uiSharedService.IconText(icon, iconColor);
            ImGui.SameLine(0f, 6f * scale);
            using (ImRaii.PushColor(ImGuiCol.Text, iconColor))
            {
                ImGui.TextUnformatted(mainText);
            }
            using (ImRaii.PushColor(ImGuiCol.Text, subColor))
            {
                ImGui.TextUnformatted(subText);
            }
        }
    }

    #endregion

    #region Animation Helpers

    private void UpdateItemAnimations(IEnumerable<string> visibleItemIds)
    {
        var deltaTime = ImGui.GetIO().DeltaTime;

        _previousVisibleItems.Clear();
        foreach (var id in _currentVisibleItems)
            _previousVisibleItems.Add(id);

        _currentVisibleItems.Clear();
        foreach (var id in visibleItemIds)
            _currentVisibleItems.Add(id);

        // Fade in new items
        foreach (var id in _currentVisibleItems)
        {
            if (!_itemAlpha.ContainsKey(id))
                _itemAlpha[id] = 0f;

            _itemAlpha[id] = Math.Min(1f, _itemAlpha[id] + deltaTime * AnimationSpeed);
        }

        // Fade out removed items
        var toRemove = new List<string>();
        foreach (var (id, alpha) in _itemAlpha)
        {
            if (!_currentVisibleItems.Contains(id))
            {
                _itemAlpha[id] = Math.Max(0f, alpha - deltaTime * AnimationSpeed);
                if (_itemAlpha[id] <= 0.01f)
                    toRemove.Add(id);
            }
        }

        foreach (var id in toRemove)
            _itemAlpha.Remove(id);
    }

    private float GetItemAlpha(string itemId)
    {
        return _itemAlpha.TryGetValue(itemId, out var alpha) ? alpha : 1f;
    }

    #endregion

    #region Nearby Syncshells Tab

    private void DrawNearbySyncshellsTab()
    {
        ImGui.BeginGroup();

#if DEBUG
        if (ImGui.SmallButton("Test Data"))
        {
            _useTestSyncshells = !_useTestSyncshells;
            _ = Task.Run(async () => await RefreshNearbySyncshellsAsync().ConfigureAwait(false));
        }
        ImGui.SameLine();
#endif

        string checkboxLabel = "Compact";
        float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight() + 8f;
        float availWidth = ImGui.GetContentRegionAvail().X;
        ImGui.SetCursorPosX(ImGui.GetCursorPosX() + availWidth - checkboxWidth);
        ImGui.Checkbox(checkboxLabel, ref _compactView);
        ImGui.EndGroup();

        ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));

        if (_nearbySyncshells.Count == 0)
        {
            DrawNoSyncshellsMessage();
            return;
        }

        var cardData = BuildSyncshellCardData();
        if (cardData.Count == 0)
        {
            UpdateItemAnimations([]);
            ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby syncshells found.");
            return;
        }

        // Update animations for syncshell items
        UpdateItemAnimations(cardData.Select(c => $"shell_{c.Shell.Group.GID}"));

        if (_compactView)
            DrawSyncshellGrid(cardData);
        else
            DrawSyncshellList(cardData);
    }

    private void DrawNoSyncshellsMessage()
    {
        ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby syncshells are being broadcasted.");

        if (!_broadcastService.IsBroadcasting)
        {
            ImGuiHelpers.ScaledDummy(4f);
            UiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"));
            ImGuiHelpers.ScaledDummy(2f);

            ImGui.TextColored(UIColors.Get("LightlessYellow"), "Lightfinder must be active to find nearby syncshells.");
        }
    }

    private List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> BuildSyncshellCardData()
    {
        string? myHashedCid = null;
        try
        {
            var cid = _dalamudUtilService.GetCID();
            myHashedCid = cid.ToString().GetHash256();
        }
        catch { }

        var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts().ToList();

        var cardData = new List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)>();

        foreach (var shell in _nearbySyncshells)
        {
            if (shell?.Group == null || string.IsNullOrEmpty(shell.Group.GID))
                continue;

#if DEBUG
            if (_useTestSyncshells)
            {
                var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
                cardData.Add((shell, $"{displayName} (Test World)", false));
                continue;
            }
#endif

            var broadcast = broadcasts.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal));
            if (broadcast == null)
                continue;

            var isOwnBroadcast = !string.IsNullOrEmpty(myHashedCid) && string.Equals(broadcast.HashedCID, myHashedCid, StringComparison.Ordinal);

            string broadcasterName;
            if (isOwnBroadcast)
            {
                broadcasterName = "You";
            }
            else
            {
                var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID);
                if (string.IsNullOrEmpty(name))
                    continue;

                var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address);
                broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{name} ({worldName})" : name;
            }

            cardData.Add((shell, broadcasterName, isOwnBroadcast));
        }

        return cardData;
    }

    private void DrawSyncshellList(List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> listData)
    {
        ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f);
        ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);

        if (ImGui.BeginChild("SyncshellListScroll", new Vector2(-1, -1), border: false))
        {
            foreach (var (shell, broadcasterName, isOwnBroadcast) in listData)
            {
                DrawSyncshellListItem(shell, broadcasterName, isOwnBroadcast);
                ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));
            }
        }
        ImGui.EndChild();

        ImGui.PopStyleVar(2);
    }

    private void DrawSyncshellListItem(GroupJoinDto shell, string broadcasterName, bool isOwnBroadcast)
    {
        var itemId = $"shell_{shell.Group.GID}";
        var alpha = GetItemAlpha(itemId);
        if (alpha <= 0.01f)
            return;

        ImGui.PushID(shell.Group.GID);
        using var alphaStyle = ImRaii.PushStyle(ImGuiStyleVar.Alpha, alpha);
        float rowHeight = 74f * ImGuiHelpers.GlobalScale;

        ImGui.BeginChild($"ShellRow##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true);

        var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
        var style = ImGui.GetStyle();
        float startX = ImGui.GetCursorPosX();
        float regionW = ImGui.GetContentRegionAvail().X;
        float rightTxtW = ImGui.CalcTextSize(broadcasterName).X;

        _uiSharedService.MediumText(displayName, UIColors.Get("LightlessPurple"));
        if (ImGui.IsItemHovered())
            ImGui.SetTooltip("Click to open profile.");
        if (ImGui.IsItemClicked())
            Mediator.Publish(new GroupProfileOpenStandaloneMessage(shell.Group));

        float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X;
        ImGui.SameLine();
        ImGui.SetCursorPosX(rightX);
        if (isOwnBroadcast)
            ImGui.TextColored(UIColors.Get("LightlessGreen"), broadcasterName);
        else
            ImGui.TextUnformatted(broadcasterName);
        if (ImGui.IsItemHovered())
            ImGui.SetTooltip(isOwnBroadcast ? "Your broadcast" : "Broadcaster");

        UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));

        var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group);
        IReadOnlyList<ProfileTagDefinition> groupTags = groupProfile?.Tags.Count > 0
            ? ProfileTagService.ResolveTags(groupProfile.Tags)
            : [];

        var limitedTags = groupTags.Count > 3 ? [.. groupTags.Take(3)] : groupTags;
        float tagScale = ImGuiHelpers.GlobalScale * 0.9f;

        Vector2 rowStartLocal = ImGui.GetCursorPos();
        float tagsWidth = 0f;

        if (limitedTags.Count > 0)
            (tagsWidth, _) = RenderProfileTagsSingleRow(limitedTags, tagScale);
        else
        {
            ImGui.SetCursorPosX(startX);
            ImGui.TextDisabled("No tags");
            ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
        }

        float joinX = rowStartLocal.X + (tagsWidth > 0 ? tagsWidth + style.ItemSpacing.X : 0f);
        ImGui.SetCursorPos(new Vector2(joinX, rowStartLocal.Y));
        DrawJoinButton(shell, false);

        ImGui.EndChild();
        ImGui.PopID();
    }

    private void DrawSyncshellGrid(List<(GroupJoinDto Shell, string BroadcasterName, bool IsOwnBroadcast)> cardData)
    {
        ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f);
        ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);

        foreach (var (shell, _, isOwnBroadcast) in cardData)
        {
            DrawSyncshellCompactItem(shell, isOwnBroadcast);
            ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));
        }

        ImGui.PopStyleVar(2);
    }

    private void DrawSyncshellCompactItem(GroupJoinDto shell, bool isOwnBroadcast)
    {
        var itemId = $"shell_{shell.Group.GID}";
        var alpha = GetItemAlpha(itemId);
        if (alpha <= 0.01f)
            return;

        ImGui.PushID(shell.Group.GID);
        using var alphaStyle = ImRaii.PushStyle(ImGuiStyleVar.Alpha, alpha);
        float rowHeight = 36f * ImGuiHelpers.GlobalScale;

        ImGui.BeginChild($"ShellCompact##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true);

        var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
        if (isOwnBroadcast)
            displayName += " (You)";
        var style = ImGui.GetStyle();
        float availW = ImGui.GetContentRegionAvail().X;

        ImGui.AlignTextToFramePadding();
        _uiSharedService.MediumText(displayName, isOwnBroadcast ? UIColors.Get("LightlessGreen") : UIColors.Get("LightlessPurple"));
        if (ImGui.IsItemHovered())
            ImGui.SetTooltip("Click to open profile.");
        if (ImGui.IsItemClicked())
            Mediator.Publish(new GroupProfileOpenStandaloneMessage(shell.Group));

        ImGui.SameLine();
        DrawJoinButton(shell, false);

        ImGui.EndChild();
        ImGui.PopID();
    }

    private void DrawJoinButton(GroupJoinDto shell, bool fullWidth)
    {
        const string visibleLabel = "Join";
        var label = $"{visibleLabel}##{shell.Group.GID}";

        var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.Group.GID, StringComparison.Ordinal));
        var isRecentlyJoined = _recentlyJoined.Contains(shell.Group.GID);
        var isOwnBroadcast = _configService.Current.SyncshellFinderEnabled
            && _broadcastService.IsBroadcasting
            && string.Equals(_configService.Current.SelectedFinderSyncshell, shell.Group.GID, StringComparison.Ordinal);

        Vector2 buttonSize;
        if (fullWidth)
        {
            buttonSize = new Vector2(-1, 0);
        }
        else
        {
            var textSize = ImGui.CalcTextSize(visibleLabel);
            var width = textSize.X + ImGui.GetStyle().FramePadding.X * 20f;
            buttonSize = new Vector2(width, 30f);

            float availX = ImGui.GetContentRegionAvail().X;
            float curX = ImGui.GetCursorPosX();
            ImGui.SetCursorPosX(curX + availX - buttonSize.X);
        }

        if (isOwnBroadcast)
        {
            ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessPurple"));
            ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple").WithAlpha(0.85f));
            ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurple").WithAlpha(0.75f));

            using (ImRaii.Disabled())
                ImGui.Button(label, buttonSize);

            UiSharedService.AttachToolTip("You can't join your own Syncshell...");
        }
        else if (!isAlreadyMember && !isRecentlyJoined)
        {
            ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"));
            ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
            ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));

            if (ImGui.Button(label, buttonSize))
            {
                _ = Task.Run(async () =>
                {
                    try
                    {
                        var info = await _apiController.GroupJoinHashed(new GroupJoinHashedDto(
                            shell.Group, shell.Password, shell.GroupUserPreferredPermissions
                        )).ConfigureAwait(false);

                        if (info?.Success == true)
                        {
                            _joinDto = new GroupJoinDto(shell.Group, shell.Password, shell.GroupUserPreferredPermissions);
                            _joinInfo = info;
                            _joinModalOpen = true;
                            _ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Join failed for {GID}", shell.Group.GID);
                    }
                });
            }
        }
        else
        {
            ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("DimRed"));
            ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("DimRed").WithAlpha(0.85f));
            ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("DimRed").WithAlpha(0.75f));

            using (ImRaii.Disabled())
                ImGui.Button(label, buttonSize);

            UiSharedService.AttachToolTip("Already a member of this Syncshell.");
        }

        ImGui.PopStyleColor(3);
    }


    private void DrawJoinConfirmation()
    {
        if (_joinDto == null || _joinInfo == null) return;

        var scale = ImGuiHelpers.GlobalScale;

        // if not already open
        if (!ImGui.IsPopupOpen("JoinSyncshellModal"))
            ImGui.OpenPopup("JoinSyncshellModal");

        Vector2 windowPos = ImGui.GetWindowPos();
        Vector2 windowSize = ImGui.GetWindowSize();
        float modalWidth = Math.Min(420f * scale, windowSize.X - 40f * scale);
        float modalHeight = 295f * scale;
        ImGui.SetNextWindowPos(new Vector2(
            windowPos.X + (windowSize.X - modalWidth) * 0.5f,
            windowPos.Y + (windowSize.Y - modalHeight) * 0.5f
        ), ImGuiCond.Always);
        ImGui.SetNextWindowSize(new Vector2(modalWidth, modalHeight));
        ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 6f * ImGuiHelpers.GlobalScale);
        ImGui.PushStyleColor(ImGuiCol.Border, Vector4.Zero);

        using ImRaii.Color modalBorder = ImRaii.PushColor(ImGuiCol.Border, UIColors.Get("LightlessPurple").WithAlpha(0.5f));
        using ImRaii.Style rounding = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 8f * scale);
        using ImRaii.Style borderSize = ImRaii.PushStyle(ImGuiStyleVar.WindowBorderSize, 2f * scale);
        using ImRaii.Style padding = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(16f * scale, 16f * scale));

        ImGuiWindowFlags flags = ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar;
        if (ImGui.BeginPopupModal("JoinSyncshellModal", ref _joinModalOpen, flags))
        {
            float contentWidth = ImGui.GetContentRegionAvail().X;

            // Header
            _uiSharedService.MediumText("Join Syncshell", UIColors.Get("LightlessPurple"));
            ImGuiHelpers.ScaledDummy(2f);
            ImGui.TextColored(ImGuiColors.DalamudGrey, $"{_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}");

            ImGuiHelpers.ScaledDummy(8f);
            UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault").WithAlpha(0.4f));
            ImGuiHelpers.ScaledDummy(8f);

            // Permissions section
            ImGui.TextColored(ImGuiColors.DalamudWhite, "Permissions");
            ImGuiHelpers.ScaledDummy(6f);

            DrawPermissionToggleRow("Sounds", FontAwesomeIcon.VolumeUp,
                _joinInfo.GroupPermissions.IsPreferDisableSounds(),
                _ownPermissions.DisableGroupSounds,
                v => _ownPermissions.DisableGroupSounds = v,
                contentWidth);

            DrawPermissionToggleRow("Animations", FontAwesomeIcon.Running,
                _joinInfo.GroupPermissions.IsPreferDisableAnimations(),
                _ownPermissions.DisableGroupAnimations,
                v => _ownPermissions.DisableGroupAnimations = v,
                contentWidth);

            DrawPermissionToggleRow("VFX", FontAwesomeIcon.Magic,
                _joinInfo.GroupPermissions.IsPreferDisableVFX(),
                _ownPermissions.DisableGroupVFX,
                v => _ownPermissions.DisableGroupVFX = v,
                contentWidth);

            ImGuiHelpers.ScaledDummy(12f);

            // Buttons
            float buttonHeight = 32f * scale;
            float buttonSpacing = 8f * scale;
            float joinButtonWidth = (contentWidth - buttonSpacing) * 0.65f;
            float cancelButtonWidth = (contentWidth - buttonSpacing) * 0.35f;

            using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 6f * scale))
            {
                // Join button
                using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessGreen")))
                using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)))
                using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)))
                {
                    if (ImGui.Button($"Join Syncshell##{_joinDto.Group.GID}", new Vector2(joinButtonWidth, buttonHeight)))
                    {
                        var finalPermissions = GroupUserPreferredPermissions.NoneSet;
                        finalPermissions.SetDisableSounds(_ownPermissions.DisableGroupSounds);
                        finalPermissions.SetDisableAnimations(_ownPermissions.DisableGroupAnimations);
                        finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);

                        _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
                        _recentlyJoined.Add(_joinDto.Group.GID);

                        _joinDto = null;
                        _joinInfo = null;
                        ImGui.CloseCurrentPopup();
                    }
                }

                ImGui.SameLine(0f, buttonSpacing);

                // Cancel button
                using (ImRaii.PushColor(ImGuiCol.Button, new Vector4(0.3f, 0.3f, 0.3f, 1f)))
                using (ImRaii.PushColor(ImGuiCol.ButtonHovered, new Vector4(0.4f, 0.4f, 0.4f, 1f)))
                using (ImRaii.PushColor(ImGuiCol.ButtonActive, new Vector4(0.25f, 0.25f, 0.25f, 1f)))
                {
                    if (ImGui.Button("Cancel", new Vector2(cancelButtonWidth, buttonHeight)))
                    {
                        _joinDto = null;
                        _joinInfo = null;
                        ImGui.CloseCurrentPopup();
                    }
                }
            }

            // Handle modal close via the bool ref
            if (!_joinModalOpen)
            {
                _joinDto = null;
                _joinInfo = null;
            }

            ImGui.EndPopup();
        }
    }

    private void DrawPermissionToggleRow(string label, FontAwesomeIcon icon, bool suggested, bool current, Action<bool> apply, float contentWidth)
    {
        var scale = ImGuiHelpers.GlobalScale;
        float rowHeight = 28f * scale;
        bool isDifferent = current != suggested;

        using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 4f * scale))
        using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 4f * scale))
        using (ImRaii.PushColor(ImGuiCol.ChildBg, new Vector4(0.18f, 0.15f, 0.22f, 0.6f)))
        {
            ImGui.BeginChild($"PermRow_{label}", new Vector2(contentWidth, rowHeight), false, ImGuiWindowFlags.NoScrollbar);

            float innerPadding = 8f * scale;
            ImGui.SetCursorPos(new Vector2(innerPadding, (rowHeight - ImGui.GetTextLineHeight()) * 0.5f));

            // Icon and label
            var enabledColor = UIColors.Get("LightlessGreen");
            var disabledColor = UIColors.Get("DimRed");
            var currentColor = !current ? enabledColor : disabledColor;

            _uiSharedService.IconText(icon, currentColor);
            ImGui.SameLine(0f, 6f * scale);
            ImGui.TextUnformatted(label);

            // Current status
            ImGui.SameLine();
            float statusX = contentWidth * 0.38f;
            ImGui.SetCursorPosX(statusX);
            ImGui.TextColored(ImGuiColors.DalamudGrey, "Current:");
            ImGui.SameLine(0f, 4f * scale);
            _uiSharedService.BooleanToColoredIcon(!current, false);

            // Suggested status
            ImGui.SameLine();
            float suggestedX = contentWidth * 0.60f;
            ImGui.SetCursorPosX(suggestedX);
            ImGui.TextColored(ImGuiColors.DalamudGrey, "Suggested:");
            ImGui.SameLine(0f, 4f * scale);
            _uiSharedService.BooleanToColoredIcon(!suggested, false);

            // Apply checkmark button if different
            if (isDifferent)
            {
                ImGui.SameLine();
                float applyX = contentWidth - 26f * scale;
                ImGui.SetCursorPosX(applyX);
                ImGui.SetCursorPosY((rowHeight - ImGui.GetFrameHeight()) * 0.5f);

                using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessGreen").WithAlpha(0.6f)))
                using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen")))
                using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreenDefault")))
                {
                    if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
                        apply(suggested);
                }
                if (ImGui.IsItemHovered())
                    ImGui.SetTooltip("Apply suggested");
            }

            ImGui.EndChild();
        }
        ImGui.Dummy(new Vector2(0, 2f * scale));
    }


    #endregion

    #region Nearby Players Tab

    private void DrawNearbyPlayersTab()
    {
        ImGui.BeginGroup();
        string checkboxLabel = "Compact";
        float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight() + 8f;
        float availWidth = ImGui.GetContentRegionAvail().X;
        ImGui.SetCursorPosX(ImGui.GetCursorPosX() + availWidth - checkboxWidth);
        ImGui.Checkbox(checkboxLabel, ref _compactView);
        ImGui.EndGroup();

        ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));

        if (!_broadcastService.IsBroadcasting)
        {
            ImGui.TextColored(UIColors.Get("LightlessYellow"), "Lightfinder must be active to see nearby players.");
            return;
        }

        string? myHashedCid = null;
        try
        {
            var cid = _dalamudUtilService.GetCID();
            myHashedCid = cid.ToString().GetHash256();
        }
        catch { }

        var activeBroadcasts = _broadcastScannerService.GetActiveBroadcasts(myHashedCid);

        if (activeBroadcasts.Count == 0)
        {
            ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby Lightfinder players found.");
            return;
        }

        var playerData = BuildNearbyPlayerData(activeBroadcasts);
        if (playerData.Count == 0)
        {
            UpdateItemAnimations([]);
            ImGui.TextColored(ImGuiColors.DalamudGrey, "No nearby Lightfinder players found.");
            return;
        }

        // Update animations for player items
        UpdateItemAnimations(playerData.Select(p => $"player_{p.HashedCid}"));

        ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f);
        ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);

        if (ImGui.BeginChild("NearbyPlayersScroll", new Vector2(-1, -1), border: false))
        {
            foreach (var data in playerData)
            {
                if (_compactView)
                    DrawNearbyPlayerCompactRow(data);
                else
                    DrawNearbyPlayerRow(data);
                ImGui.Dummy(new Vector2(0, 2 * ImGuiHelpers.GlobalScale));
            }
        }
        ImGui.EndChild();

        ImGui.PopStyleVar(2);
    }

    private List<NearbyPlayerData> BuildNearbyPlayerData(List<KeyValuePair<string, LightFinderScannerService.BroadcastEntry>> activeBroadcasts)
    {
        var snapshot = _pairUiService.GetSnapshot();
        var playerData = new List<NearbyPlayerData>();

        foreach (var broadcast in activeBroadcasts)
        {
            var (name, address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.Key);
            if (string.IsNullOrEmpty(name) || address == nint.Zero)
                continue;

            var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(address);
            var pair = snapshot.PairsByUid.Values.FirstOrDefault(p =>
                p.IsVisible &&
                !string.IsNullOrEmpty(p.GetPlayerNameHash()) &&
                string.Equals(p.GetPlayerNameHash(), broadcast.Key, StringComparison.Ordinal));

            var isDirectlyPaired = pair?.IsDirectlyPaired ?? false;
            var sharedGroups = pair?.UserPair?.Groups ?? [];
            var sharedGroupNames = sharedGroups
                .Select(gid => snapshot.GroupsByGid.TryGetValue(gid, out var g) ? g.GroupAliasOrGID : gid)
                .ToList();

            playerData.Add(new NearbyPlayerData(broadcast.Key, name, worldName, address, pair, isDirectlyPaired, sharedGroupNames));
        }

        return playerData;
    }

    private readonly record struct NearbyPlayerData(
        string HashedCid,
        string Name,
        string? World,
        nint Address,
        Pair? Pair,
        bool IsDirectlyPaired,
        List<string> SharedSyncshells);

    private void DrawNearbyPlayerRow(NearbyPlayerData data)
    {
        var itemId = $"player_{data.HashedCid}";
        var alpha = GetItemAlpha(itemId);
        if (alpha <= 0.01f)
            return;

        ImGui.PushID(data.HashedCid);
        using var alphaStyle = ImRaii.PushStyle(ImGuiStyleVar.Alpha, alpha);
        float rowHeight = 74f * ImGuiHelpers.GlobalScale;

        ImGui.BeginChild($"PlayerRow##{data.HashedCid}", new Vector2(-1, rowHeight), border: true);

        var serverName = !string.IsNullOrEmpty(data.World) ? data.World : "Unknown";
        var style = ImGui.GetStyle();
        float startX = ImGui.GetCursorPosX();
        float regionW = ImGui.GetContentRegionAvail().X;
        float rightTxtW = ImGui.CalcTextSize(serverName).X;

        _uiSharedService.MediumText(data.Name, UIColors.Get("LightlessPurple"));
        if (data.Pair != null)
        {
            if (ImGui.IsItemHovered())
                ImGui.SetTooltip("Click to open profile.");
            if (ImGui.IsItemClicked())
                Mediator.Publish(new ProfileOpenStandaloneMessage(data.Pair));
        }

        float rightX = startX + regionW - rightTxtW - style.ItemSpacing.X;
        ImGui.SameLine();
        ImGui.SetCursorPosX(rightX);
        ImGui.TextUnformatted(serverName);
        if (ImGui.IsItemHovered())
            ImGui.SetTooltip("Home World");

        UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));

        Vector2 rowStartLocal = ImGui.GetCursorPos();

        if (data.IsDirectlyPaired)
        {
            ImGui.SetCursorPosX(startX);
            _uiSharedService.IconText(FontAwesomeIcon.UserCheck, UIColors.Get("LightlessGreen"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
            ImGui.TextColored(UIColors.Get("LightlessGreen"), "Direct Pair");
        }
        else if (data.SharedSyncshells.Count > 0)
        {
            ImGui.SetCursorPosX(startX);
            _uiSharedService.IconText(FontAwesomeIcon.Users, UIColors.Get("LightlessPurple"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
            var shellText = data.SharedSyncshells.Count == 1
                ? data.SharedSyncshells[0]
                : $"{data.SharedSyncshells.Count} shared shells";
            ImGui.TextColored(UIColors.Get("LightlessPurple"), shellText);
            if (data.SharedSyncshells.Count > 1 && ImGui.IsItemHovered())
                ImGui.SetTooltip(string.Join("\n", data.SharedSyncshells));
        }
        else
        {
            ImGui.SetCursorPosX(startX);
            _uiSharedService.IconText(FontAwesomeIcon.Wifi, UIColors.Get("LightlessBlue"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
            ImGui.TextColored(ImGuiColors.DalamudGrey, "Lightfinder user");
        }

        ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));

        DrawPlayerActionButtons(data, startX, regionW, rowStartLocal.Y, style);

        ImGui.EndChild();
        ImGui.PopID();
    }

    private void DrawNearbyPlayerCompactRow(NearbyPlayerData data)
    {
        var itemId = $"player_{data.HashedCid}";
        var alpha = GetItemAlpha(itemId);
        if (alpha <= 0.01f)
            return;

        ImGui.PushID(data.HashedCid);
        using var alphaStyle = ImRaii.PushStyle(ImGuiStyleVar.Alpha, alpha);
        float rowHeight = 36f * ImGuiHelpers.GlobalScale;

        ImGui.BeginChild($"PlayerCompact##{data.HashedCid}", new Vector2(-1, rowHeight), border: true);

        ImGui.AlignTextToFramePadding();

        if (data.IsDirectlyPaired)
        {
            _uiSharedService.IconText(FontAwesomeIcon.UserCheck, UIColors.Get("LightlessGreen"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
        }
        else if (data.SharedSyncshells.Count > 0)
        {
            _uiSharedService.IconText(FontAwesomeIcon.Users, UIColors.Get("LightlessPurple"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
        }
        else
        {
            _uiSharedService.IconText(FontAwesomeIcon.Wifi, UIColors.Get("LightlessBlue"));
            ImGui.SameLine(0f, 4f * ImGuiHelpers.GlobalScale);
        }

        var displayText = !string.IsNullOrEmpty(data.World) ? $"{data.Name} ({data.World})" : data.Name;
        _uiSharedService.MediumText(displayText, UIColors.Get("LightlessPurple"));
        if (data.Pair != null)
        {
            if (ImGui.IsItemHovered())
                ImGui.SetTooltip("Click to open profile.");
            if (ImGui.IsItemClicked())
                Mediator.Publish(new ProfileOpenStandaloneMessage(data.Pair));
        }

        ImGui.SameLine();
        DrawPlayerActionButtons(data, 0, ImGui.GetContentRegionAvail().X, ImGui.GetCursorPosY(), ImGui.GetStyle(), compact: true);

        ImGui.EndChild();
        ImGui.PopID();
    }

    private void DrawPlayerActionButtons(NearbyPlayerData data, float startX, float regionW, float rowY, ImGuiStylePtr style, bool compact = false)
    {
        float buttonWidth = compact ? 60f * ImGuiHelpers.GlobalScale : 80f * ImGuiHelpers.GlobalScale;
        float buttonHeight = compact ? 0 : 30f;
        float totalButtonsWidth = buttonWidth * 2 + style.ItemSpacing.X;

        if (compact)
        {
            float availX = ImGui.GetContentRegionAvail().X;
            float curX = ImGui.GetCursorPosX();
            ImGui.SetCursorPosX(curX + availX - totalButtonsWidth);
        }
        else
        {
            ImGui.SetCursorPos(new Vector2(startX + regionW - totalButtonsWidth - style.ItemSpacing.X, rowY));
        }

        using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessGreen")))
        using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f)))
        using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f)))
        using (ImRaii.Disabled(data.IsDirectlyPaired))
        {
            if (ImGui.Button($"Pair##{data.HashedCid}", new Vector2(buttonWidth, buttonHeight)))
            {
                _ = SendPairRequestAsync(data.HashedCid);
            }
        }
        if (data.IsDirectlyPaired)
            UiSharedService.AttachToolTip("Already directly paired with this player.");

        ImGui.SameLine();

        using (ImRaii.PushColor(ImGuiCol.Button, UIColors.Get("LightlessPurple")))
        using (ImRaii.PushColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple").WithAlpha(0.85f)))
        using (ImRaii.PushColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurple").WithAlpha(0.75f)))
        {
            if (ImGui.Button($"Target##{data.HashedCid}", new Vector2(buttonWidth, buttonHeight)))
            {
                TargetPlayerByAddress(data.Address);
            }
        }
    }

    private async Task SendPairRequestAsync(string hashedCid)
    {
        if (string.IsNullOrWhiteSpace(hashedCid))
            return;

        try
        {
            await _apiController.TryPairWithContentId(hashedCid).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send pair request to {HashedCid}", hashedCid);
        }
    }

    private void TargetPlayerByAddress(nint address)
    {
        if (address == nint.Zero)
            return;

        _dalamudUtilService.TargetPlayerByAddress(address);
    }

    #endregion

    #region Broadcast Settings Tab

    private void DrawBroadcastSettingsTab()
    {
        _uiSharedService.MediumText("Syncshell Broadcasting", UIColors.Get("PairBlue"));
        ImGuiHelpers.ScaledDummy(2f);

        ImGui.PushTextWrapPos();
        ImGui.TextColored(ImGuiColors.DalamudGrey, "Broadcast your Syncshell to nearby Lightfinder users. They can then join directly from the Nearby Syncshells tab.");
        ImGui.PopTextWrapPos();

        ImGuiHelpers.ScaledDummy(4f);
        UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
        ImGuiHelpers.ScaledDummy(4f);

        bool isBroadcasting = _broadcastService.IsBroadcasting;

        if (isBroadcasting)
        {
            _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"),
                new SeStringUtils.RichTextEntry("Settings can only be changed while Lightfinder is disabled.", UIColors.Get("LightlessYellow")));
            ImGuiHelpers.ScaledDummy(4f);
        }

        if (isBroadcasting)
            ImGui.BeginDisabled();

        bool shellFinderEnabled = _configService.Current.SyncshellFinderEnabled;
        if (ImGui.Checkbox("Enable Syncshell Broadcasting", ref shellFinderEnabled))
        {
            _configService.Current.SyncshellFinderEnabled = shellFinderEnabled;
            _configService.Save();
        }
        UiSharedService.AttachToolTip("When enabled and Lightfinder is active, your selected Syncshell will be visible to nearby users.");

        ImGuiHelpers.ScaledDummy(4f);

        ImGui.Text("Select Syncshell to broadcast:");

        var selectedGid = _configService.Current.SelectedFinderSyncshell;
        var currentOption = _syncshellOptions.FirstOrDefault(o => string.Equals(o.GID, selectedGid, StringComparison.Ordinal));
        var preview = currentOption.Label ?? "Select a Syncshell...";

        ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
        if (ImGui.BeginCombo("##SyncshellDropdown", preview))
        {
            foreach (var (label, gid, available) in _syncshellOptions)
            {
                bool isSelected = string.Equals(gid, selectedGid, StringComparison.Ordinal);

                if (!available)
                    ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));

                if (ImGui.Selectable(label, isSelected))
                {
                    _configService.Current.SelectedFinderSyncshell = gid;
                    _configService.Save();
                    _ = RefreshSyncshellsAsync();
                }

                if (!available && ImGui.IsItemHovered())
                    ImGui.SetTooltip("This Syncshell is not available on the current service.");

                if (!available)
                    ImGui.PopStyleColor();

                if (isSelected)
                    ImGui.SetItemDefaultFocus();
            }

            ImGui.EndCombo();
        }

        if (isBroadcasting)
            ImGui.EndDisabled();

        ImGui.SameLine();
        if (_uiSharedService.IconButton(FontAwesomeIcon.Sync))
            _ = RefreshSyncshellsAsync();
        UiSharedService.AttachToolTip("Refresh Syncshell list");

        ImGuiHelpers.ScaledDummy(8f);

        ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 8f);
        if (_uiSharedService.IconTextButton(FontAwesomeIcon.Cog, "Advanced Settings"))
            Mediator.Publish(new OpenLightfinderSettingsMessage());
        ImGui.PopStyleVar();
        UiSharedService.AttachToolTip("Open Lightfinder settings in the Settings window.");
    }

    #endregion

    #region Help Tab

    private void DrawHelpTab()
    {
        ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(4, 4));

        _uiSharedService.MediumText("What is Lightfinder?", UIColors.Get("PairBlue"));
        ImGui.PushTextWrapPos();
        ImGui.TextColored(ImGuiColors.DalamudGrey, "Lightfinder lets other Lightless users know you use Lightless. While enabled, you and others can see each other via a nameplate label.");
        ImGui.PopTextWrapPos();

        ImGuiHelpers.ScaledDummy(6f);

        _uiSharedService.MediumText("Pairing", UIColors.Get("PairBlue"));
        ImGui.PushTextWrapPos();
        ImGui.TextColored(ImGuiColors.DalamudGrey, "Pairing can be initiated via the right-click context menu on another player. The process requires mutual confirmation from both users.");
        ImGui.PopTextWrapPos();

        ImGuiHelpers.ScaledDummy(2f);

        _uiSharedService.DrawNoteLine("", UIColors.Get("LightlessGreen"),
            new SeStringUtils.RichTextEntry("If Lightfinder is "),
            new SeStringUtils.RichTextEntry("ENABLED", UIColors.Get("LightlessGreen"), true),
            new SeStringUtils.RichTextEntry(", the receiving user will get notified about pair requests."));

        _uiSharedService.DrawNoteLine("", UIColors.Get("DimRed"),
            new SeStringUtils.RichTextEntry("If Lightfinder is "),
            new SeStringUtils.RichTextEntry("DISABLED", UIColors.Get("DimRed"), true),
            new SeStringUtils.RichTextEntry(", pair requests will NOT be visible to the recipient."));

        ImGuiHelpers.ScaledDummy(6f);

        _uiSharedService.MediumText("Privacy", UIColors.Get("PairBlue"));

        _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"),
            new SeStringUtils.RichTextEntry("Lightfinder is entirely "),
            new SeStringUtils.RichTextEntry("opt-in", UIColors.Get("LightlessYellow"), true),
            new SeStringUtils.RichTextEntry(" and does not share personal data with other users."));

        ImGui.PushTextWrapPos();
        ImGui.TextColored(ImGuiColors.DalamudGrey, "All identifying information remains private to the server. Use Lightfinder when you're okay with being visible to other users.");
        ImGui.PopTextWrapPos();

        ImGuiHelpers.ScaledDummy(6f);

        _uiSharedService.MediumText("Syncshell Broadcasting", UIColors.Get("PairBlue"));
        ImGui.PushTextWrapPos();
        ImGui.TextColored(ImGuiColors.DalamudGrey, "You can broadcast a Syncshell you own or moderate to nearby Lightfinder users. Configure this in the Broadcast Settings tab.");
        ImGui.PopTextWrapPos();

        ImGui.PopStyleVar();
    }

    #endregion

    private void DrawDebugTab()
    {
#if DEBUG
            if (ImGui.CollapsingHeader("LightFinder Plates", ImGuiTreeNodeFlags.DefaultOpen))
            {
                var h = _lightFinderPlateHandler;

                var enabled = h.DebugEnabled;
                if (ImGui.Checkbox("Enable LightFinder debug", ref enabled))
                    h.DebugEnabled = enabled;

                if (h.DebugEnabled)
                {
                    ImGui.Indent();

                    var disableOcc = h.DebugDisableOcclusion;
                    if (ImGui.Checkbox("Disable occlusion (force draw)", ref disableOcc))
                        h.DebugDisableOcclusion = disableOcc;

                    var drawUiRects = h.DebugDrawUiRects;
                    if (ImGui.Checkbox("Draw UI rects", ref drawUiRects))
                        h.DebugDrawUiRects = drawUiRects;

                    var drawLabelRects = h.DebugDrawLabelRects;
                    if (ImGui.Checkbox("Draw label rects", ref drawLabelRects))
                        h.DebugDrawLabelRects = drawLabelRects;

                    ImGui.Separator();
                    ImGui.TextUnformatted($"Labels last frame: {h.DebugLabelCountLastFrame}");
                    ImGui.TextUnformatted($"UI rects last frame: {h.DebugUiRectCountLastFrame}");
                    ImGui.TextUnformatted($"Occluded last frame: {h.DebugOccludedCountLastFrame}");
                    ImGui.TextUnformatted($"Last NamePlate frame: {h.DebugLastNameplateFrame}");

                    ImGui.Unindent();
                }
            }

            ImGui.Separator();

            ImGui.Text("Broadcast Cache");

            if (ImGui.BeginTable("##BroadcastCacheTable", 4,
                ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY,
                new Vector2(-1, 225f)))
            {
                ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
                ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch);
                ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
                ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
                ImGui.TableHeadersRow();

                var now = DateTime.UtcNow;

                foreach (var (cid, entry) in _broadcastScannerService.BroadcastCache)
                {
                    ImGui.TableNextRow();

                    ImGui.TableNextColumn();
                    ImGui.TextUnformatted(cid.Truncate(12));
                    if (ImGui.IsItemHovered())
                        ImGui.SetTooltip(cid);

                    ImGui.TableNextColumn();
                    var colorBroadcast = entry.IsBroadcasting ? UIColors.Get("LightlessGreen") : UIColors.Get("DimRed");
                    ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(colorBroadcast));
                    ImGui.TextUnformatted(entry.IsBroadcasting.ToString());

                    ImGui.TableNextColumn();
                    var remaining = entry.ExpiryTime - now;
                    var colorTtl = remaining <= TimeSpan.Zero ? UIColors.Get("DimRed")
                        : remaining < TimeSpan.FromSeconds(10) ? UIColors.Get("LightlessYellow")
                        : (Vector4?)null;

                    if (colorTtl != null)
                        ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(colorTtl.Value));

                    ImGui.TextUnformatted(remaining > TimeSpan.Zero ? remaining.ToString("hh\\:mm\\:ss") : "Expired");

                    ImGui.TableNextColumn();
                    ImGui.TextUnformatted(entry.GID ?? "-");
                }

                ImGui.EndTable();
            }
#endif
    }

    #region Data Refresh

    private async Task RefreshSyncshellsAsync()
    {
        if (!_apiController.IsConnected)
        {
            _allSyncshells = [];
            RebuildSyncshellDropdownOptions();
            return;
        }

        try
        {
            _allSyncshells = await _apiController.GroupsGetAll().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to fetch Syncshells.");
            _allSyncshells = [];
        }

        RebuildSyncshellDropdownOptions();
    }

    private void RebuildSyncshellDropdownOptions()
    {
        var selectedGid = _configService.Current.SelectedFinderSyncshell;
        var allSyncshells = _allSyncshells ?? [];
        var filteredSyncshells = allSyncshells
            .Where(g => string.Equals(g.OwnerUID, _userUid, StringComparison.Ordinal) || g.GroupUserInfo.IsModerator())
            .ToList();

        _syncshellOptions.Clear();
        _syncshellOptions.Add(("None", null, true));

        var addedGids = new HashSet<string>(StringComparer.Ordinal);

        foreach (var shell in filteredSyncshells)
        {
            var label = shell.GroupAliasOrGID ?? shell.GID;
            _syncshellOptions.Add((label, shell.GID, true));
            addedGids.Add(shell.GID);
        }

        if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
        {
            var matching = allSyncshells.FirstOrDefault(g => string.Equals(g.GID, selectedGid, StringComparison.Ordinal));
            if (matching != null)
            {
                var label = matching.GroupAliasOrGID ?? matching.GID;
                _syncshellOptions.Add((label, matching.GID, true));
                addedGids.Add(matching.GID);
            }
        }

        if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
            _syncshellOptions.Add(($"[Unavailable] {selectedGid}", selectedGid, false));
    }

    private async Task RefreshNearbySyncshellsAsync(string? gid = null)
    {
        var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
        var snapshot = _pairUiService.GetSnapshot();
        _currentSyncshells = [.. snapshot.GroupPairs.Keys];

        _recentlyJoined.RemoveWhere(g => _currentSyncshells.Exists(s => string.Equals(s.GID, g, StringComparison.Ordinal)));

        List<GroupJoinDto>? updatedList = [];

#if DEBUG
        if (_useTestSyncshells)
        {
            updatedList = BuildTestSyncshells();
        }
        else
#endif
        {
            if (syncshellBroadcasts.Count == 0)
            {
                ClearSyncshells();
                return;
            }

            try
            {
                var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false);
                updatedList = groups?.DistinctBy(g => g.Group.GID).ToList();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to refresh broadcasted syncshells.");
                return;
            }
        }

        if (updatedList == null || updatedList.Count == 0)
        {
            ClearSyncshells();
            return;
        }

        if (gid != null && _recentlyJoined.Contains(gid))
            _recentlyJoined.Clear();

        var previousGid = GetSelectedGid();

        _nearbySyncshells.Clear();
        _nearbySyncshells.AddRange(updatedList);

        if (previousGid != null)
        {
            var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal));
            if (newIndex >= 0)
            {
                _selectedNearbyIndex = newIndex;
                return;
            }
        }

        ClearSelection();
    }

#if DEBUG
    private static List<GroupJoinDto> BuildTestSyncshells()
    {
        return
        [
            new(new GroupData("TEST-ALPHA", "Alpha Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-BETA", "Beta Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-GAMMA", "Gamma Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-DELTA", "Delta Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-EPSILON", "Epsilon Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-ZETA", "Zeta Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-ETA", "Eta Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-THETA", "Theta Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-IOTA", "Iota Shell"), "", GroupUserPreferredPermissions.NoneSet),
            new(new GroupData("TEST-KAPPA", "Kappa Shell"), "", GroupUserPreferredPermissions.NoneSet),
        ];
    }
#endif

    private void ClearSyncshells()
    {
        if (_nearbySyncshells.Count == 0) return;
        _nearbySyncshells.Clear();
        ClearSelection();
    }

    private void ClearSelection()
    {
        _selectedNearbyIndex = -1;
        _joinDto = null;
        _joinInfo = null;
    }

    private string? GetSelectedGid()
    {
        if (_selectedNearbyIndex < 0 || _selectedNearbyIndex >= _nearbySyncshells.Count)
            return null;
        return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
    }

    #endregion

    #region Helpers

    private (float widthUsed, float rowHeight) RenderProfileTagsSingleRow(IReadOnlyList<ProfileTagDefinition> tags, float scale)
    {
        if (tags == null || tags.Count == 0)
            return (0f, 0f);

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

        var baseLocal = ImGui.GetCursorPos();
        var baseScreen = ImGui.GetCursorScreenPos();
        float availableWidth = ImGui.GetContentRegionAvail().X;
        if (availableWidth <= 0f)
            availableWidth = 1f;

        float cursorLocalX = baseLocal.X;
        float cursorScreenX = baseScreen.X;
        float rowHeight = 0f;

        for (int i = 0; i < tags.Count; i++)
        {
            var tag = tags[i];
            if (!tag.HasContent)
                continue;

            var tagSize = ProfileTagRenderer.MeasureTag(tag, scale, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger);

            float tagWidth = tagSize.X;
            float tagHeight = tagSize.Y;

            if (cursorLocalX > baseLocal.X && cursorLocalX + tagWidth > baseLocal.X + availableWidth)
                break;

            var tagScreenPos = new Vector2(cursorScreenX, baseScreen.Y);
            ImGui.SetCursorScreenPos(tagScreenPos);
            ImGui.InvisibleButton($"##profileTagInline_{i}", tagSize);

            ProfileTagRenderer.RenderTag(tag, tagScreenPos, scale, drawList, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger);

            cursorLocalX += tagWidth + style.ItemSpacing.X;
            cursorScreenX += tagWidth + style.ItemSpacing.X;
            rowHeight = MathF.Max(rowHeight, tagHeight);
        }

        ImGui.SetCursorPos(new Vector2(baseLocal.X, baseLocal.Y + rowHeight));

        float widthUsed = cursorLocalX - baseLocal.X;
        return (widthUsed, rowHeight);
    }

    private static string TruncateTextToWidth(string text, float maxWidth)
    {
        if (string.IsNullOrEmpty(text))
            return text;

        const string ellipsis = "...";
        float ellipsisWidth = ImGui.CalcTextSize(ellipsis).X;

        if (maxWidth <= ellipsisWidth)
            return ellipsis;

        int low = 0;
        int high = text.Length;
        string best = ellipsis;

        while (low <= high)
        {
            int mid = (low + high) / 2;
            string candidate = string.Concat(text.AsSpan(0, mid), ellipsis);
            float width = ImGui.CalcTextSize(candidate).X;

            if (width <= maxWidth)
            {
                best = candidate;
                low = mid + 1;
            }
            else
            {
                high = mid - 1;
            }
        }

        return best;
    }

    private IDalamudTextureWrap? GetIconWrap(uint iconId)
    {
        try
        {
            if (_uiSharedService.TryGetIcon(iconId, out var wrap) && wrap != null)
                return wrap;
        }
        catch (Exception ex)
        {
            _logger.LogDebug(ex, "Failed to resolve icon {IconId}", iconId);
        }

        return null;
    }

    #endregion
}