using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using Dalamud.Interface.Textures.TextureWraps;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.StatusEffects;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace HSUI.Interface.Party
{
    public class PartyFramesBar
    {
        public delegate void PartyFramesBarEventHandler(PartyFramesBar bar);
        public PartyFramesBarEventHandler? OpenContextMenuEvent;

        private PartyFramesConfigs _configs;

        private LabelHud _nameLabelHud;
        private LabelHud _healthLabelHud;
        private LabelHud _orderLabelHud;
        private LabelHud _statusLabelHud;
        private LabelHud _raiseLabelHud;
        private LabelHud _invulnLabelHud;
        private PrimaryResourceHud _manaBarHud;
        private CastbarHud _castbarHud;
        private StatusEffectsListHud _buffsListHud;
        private StatusEffectsListHud _debuffsListHud;
        private PartyFramesCooldownListHud _cooldownListHud;

        private IDalamudTextureWrap? _readyCheckTexture =>
            TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck_hr1.tex") ??
            TexturesHelper.GetTextureFromPath("ui/uld/ReadyCheck.tex");

        public bool Visible = false;
        public Vector2 Position;

        private SmoothHPHelper _smoothHPHelper = new SmoothHPHelper();

        private bool _wasHovering = false;

        public IPartyFramesMember? Member;

        public PartyFramesBar(string id, PartyFramesConfigs configs)
        {
            _configs = configs;

            _nameLabelHud = new LabelHud(_configs.HealthBar.NameLabelConfig);
            _healthLabelHud = new LabelHud(_configs.HealthBar.HealthLabelConfig);
            _orderLabelHud = new LabelHud(_configs.HealthBar.OrderNumberConfig);
            _statusLabelHud = new LabelHud(PlayerStatus.Label);
            _raiseLabelHud = new LabelHud(RaiseTracker.Icon.NumericLabel);
            _invulnLabelHud = new LabelHud(InvulnTracker.Icon.NumericLabel);

            _manaBarHud = new PrimaryResourceHud(_configs.ManaBar);
            _castbarHud = new CastbarHud(_configs.CastBar);
            _buffsListHud = new StatusEffectsListHud(_configs.Buffs);
            _debuffsListHud = new StatusEffectsListHud(_configs.Debuffs);

            _cooldownListHud = new PartyFramesCooldownListHud(_configs.CooldownList);
        }

        public PluginConfigColor GetColor(float scale)
        {
            if (Member == null || Member.MaxHP <= 0)
            {
                return _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor;
            }

            bool cleanseCheck = true;
            if (CleanseTracker.CleanseJobsOnly)
            {
                cleanseCheck = Utils.IsOnCleanseJob();
            }

            if (CleanseTracker.Enabled && CleanseTracker.ChangeHealthBarCleanseColor && Member.HasDispellableDebuff && cleanseCheck)
            {
                return CleanseTracker.HealthBarColor;
            }
            else if (_configs.HealthBar.ColorsConfig.ColorByHealth.Enabled)
            {
                if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseJobColorAsMaxHealth)
                {
                    return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
                        GlobalColors.Instance.SafeColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
                }
                else if (_configs.HealthBar.ColorsConfig.ColorByHealth.UseRoleColorAsMaxHealth)
                {
                    return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColorThreshold / 100f, _configs.HealthBar.ColorsConfig.ColorByHealth.LowHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.FullHealthColor,
                        GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId), _configs.HealthBar.ColorsConfig.ColorByHealth.UseMaxHealthColor, _configs.HealthBar.ColorsConfig.ColorByHealth.BlendMode);
                }
                return ColorUtils.GetColorByScale(scale, _configs.HealthBar.ColorsConfig.ColorByHealth);
            }
            else if (Member.JobId > 0)
            {
                return _configs.HealthBar.ColorsConfig.UseRoleColors switch
                {
                    true => GlobalColors.Instance.SafeRoleColorForJobId(Member.JobId),
                    _ => GlobalColors.Instance.SafeColorForJobId(Member.JobId)
                };
            }

            return Member.Character?.ObjectKind switch
            {
                ObjectKind.BattleNpc => GlobalColors.Instance.NPCFriendlyColor,
                _ => _configs.HealthBar.ColorsConfig.OutOfReachBackgroundColor
            };
        }

        public void StopPreview()
        {
            _castbarHud.StopPreview();
            _buffsListHud.StopPreview();
            _debuffsListHud.StopPreview();
            _cooldownListHud.StopPreview();
            _configs.HealthBar.MouseoverAreaConfig.Preview = false;
        }

        public void StopMouseover()
        {
            if (_wasHovering)
            {
                InputsHelper.Instance.ClearTarget();
                _wasHovering = false;
            }
        }

        public void Dispose()
        {
            _cooldownListHud.Dispose();
        }

        public List<(StrataLevel, Action)> GetBarDrawActions(Vector2 origin, PluginConfigColor? borderColor = null)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();

            if (!Visible || Member is null)
            {
                StopMouseover();
                return drawActions;
            }

            // click
            var (areaStart, areaEnd) = _configs.HealthBar.MouseoverAreaConfig.GetArea(Position, _configs.HealthBar.Size);
            bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
            bool ignoreMouseover = _configs.HealthBar.MouseoverAreaConfig.Enabled && _configs.HealthBar.MouseoverAreaConfig.Ignore;
            ICharacter? character = Member.Character;

            if (isHovering)
            {
                _wasHovering = true;
                InputsHelper.Instance.SetTarget(character, ignoreMouseover);

                // left click
                if (InputsHelper.Instance.LeftButtonClicked && character != null)
                {
                    Plugin.TargetManager.Target = character;
                }
                // right click (context menu)
                else if (InputsHelper.Instance.RightButtonClicked)
                {
                    OpenContextMenuEvent?.Invoke(this);
                }
            }
            else if (_wasHovering)
            {
                InputsHelper.Instance.ClearTarget();
                _wasHovering = false;
            }

            // bg
            PluginConfigColor bgColor = _configs.HealthBar.ColorsConfig.BackgroundColor;
            if (Member.RaiseTime != null && RaiseTracker.Enabled && RaiseTracker.ChangeBackgroundColorWhenRaised)
            {
                bgColor = RaiseTracker.BackgroundColor;
            }
            else if (Member.InvulnStatus?.InvulnTime != null && InvulnTracker.Enabled && InvulnTracker.ChangeBackgroundColorWhenInvuln)
            {
                bgColor = Member.InvulnStatus?.InvulnId == 811 ? InvulnTracker.WalkingDeadBackgroundColor : InvulnTracker.BackgroundColor;
            }
            else if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
            {
                bgColor = _configs.HealthBar.RangeConfig.Enabled
                    ? GetDistanceColor(character, _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor)
                    : _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
            }
            else if (_configs.HealthBar.ColorsConfig.UseJobColorAsBackgroundColor && character is IBattleChara)
            {
                bgColor = GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
            }
            else if (_configs.HealthBar.ColorsConfig.UseRoleColorAsBackgroundColor && character is IBattleChara)
            {
                bgColor = _configs.HealthBar.RangeConfig.Enabled
                    ? GetDistanceColor(character, GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId))
                    : GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId);
            }

            Rect background = new Rect(Position, _configs.HealthBar.Size, bgColor);

            // hp
            uint currentHp = Member.HP;
            uint maxHp = Member.MaxHP;

            if (_configs.HealthBar.SmoothHealthConfig.Enabled)
            {
                currentHp = _smoothHPHelper.GetNextHp((int)currentHp, (int)maxHp, _configs.HealthBar.SmoothHealthConfig.Velocity);
            }

            float hpScale = maxHp > 0 ? (float)currentHp / (float)maxHp : 1;
            PluginConfigColor? hpColor = _configs.HealthBar.RangeConfig.Enabled && character != null
            ? GetDistanceColor(character, GetColor(hpScale))
            : GetColor(hpScale);

            Rect healthFill = BarUtilities.GetFillRect(Position, _configs.HealthBar.Size, _configs.HealthBar.FillDirection, hpColor, currentHp, maxHp);

            // bar
            int thickness = borderColor != null ? _configs.HealthBar.ColorsConfig.ActiveBorderThickness : _configs.HealthBar.ColorsConfig.InactiveBorderThickness;

            if (WhosTalkingIcon.ChangeBorders && Member.WhosTalkingState != WhosTalkingState.None)
            {
                thickness = WhosTalkingIcon.BorderThickness;
            }

            borderColor = borderColor ?? GetBorderColor(character);

            BarHud bar = new BarHud(
                _configs.HealthBar.ID,
                _configs.HealthBar.ColorsConfig.ShowBorder,
                borderColor,
                thickness,
                actor: character,
                current: currentHp,
                max: maxHp,
                shadowConfig: _configs.HealthBar.ShadowConfig,
                barTextureName: _configs.HealthBar.BarTextureName,
                barTextureDrawMode: _configs.HealthBar.BarTextureDrawMode
            );

            bar.NeedsInputs = true;
            bar.SetBackground(background);
            bar.AddForegrounds(healthFill);

            // missing health
            if (_configs.HealthBar.ColorsConfig.UseMissingHealthBar)
            {
                Vector2 healthMissingSize = _configs.HealthBar.Size - BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);
                Vector2 healthMissingPos = _configs.HealthBar.FillDirection.IsInverted() ? Position : Position + BarUtilities.GetFillDirectionOffset(healthFill.Size, _configs.HealthBar.FillDirection);

                PluginConfigColor? missingHealthColor = _configs.HealthBar.ColorsConfig.UseJobColorAsMissingHealthColor && character is IBattleChara
                    ? GlobalColors.Instance.SafeColorForJobId(character!.ClassJob.RowId)
                    : _configs.HealthBar.ColorsConfig.UseRoleColorAsMissingHealthColor && character is IBattleChara
                        ? GlobalColors.Instance.SafeRoleColorForJobId(character!.ClassJob.RowId)
                        : _configs.HealthBar.ColorsConfig.HealthMissingColor;

                if (_configs.HealthBar.ColorsConfig.UseDeathIndicatorBackgroundColor && Member.HP <= 0 && character != null)
                {
                    missingHealthColor = _configs.HealthBar.ColorsConfig.DeathIndicatorBackgroundColor;
                }

                if (_configs.Trackers.Invuln.ChangeBackgroundColorWhenInvuln && character is IBattleChara battleChara)
                {
                    IStatus? tankInvuln = Utils.GetTankInvulnerabilityID(battleChara);
                    if (tankInvuln is not null)
                    {
                        missingHealthColor = _configs.Trackers.Invuln.BackgroundColor;
                    }
                }

                if (_configs.HealthBar.RangeConfig.Enabled)
                {
                    missingHealthColor = GetDistanceColor(character, missingHealthColor);
                }

                bar.AddForegrounds(new Rect(healthMissingPos, healthMissingSize, missingHealthColor));
            }

            // shield
            if (_configs.HealthBar.ShieldConfig.Enabled)
            {
                if (Member.Shield > 0f)
                {
                    bar.AddForegrounds(
                        BarUtilities.GetShieldForeground(
                            _configs.HealthBar.ShieldConfig,
                            Position,
                            _configs.HealthBar.Size,
                            healthFill.Size,
                            _configs.HealthBar.FillDirection,
                            Member.Shield,
                            currentHp,
                            maxHp
                        )
                    );
                }
            }

            // highlight
            bool isSoftTarget = character != null && character == Plugin.TargetManager.SoftTarget;
            if (_configs.HealthBar.ColorsConfig.ShowHighlight && (isHovering || isSoftTarget))
            {
                Rect highlight = new Rect(Position, _configs.HealthBar.Size, _configs.HealthBar.ColorsConfig.HighlightColor);
                bar.AddForegrounds(highlight);
            }

            drawActions = bar.GetDrawActions(Vector2.Zero, _configs.HealthBar.StrataLevel);

            // mouseover area
            BarHud? mouseoverAreaBar = _configs.HealthBar.MouseoverAreaConfig.GetBar(
                Position,
                _configs.HealthBar.Size,
                _configs.HealthBar.ID + "_mouseoverArea"
            );

            if (mouseoverAreaBar != null)
            {
                drawActions.AddRange(mouseoverAreaBar.GetDrawActions(Vector2.Zero, StrataLevel.HIGHEST));
            }

            return drawActions;
        }

        private PluginConfigColor GetBorderColor(ICharacter? character)
        {
            IGameObject? target = Plugin.TargetManager.Target ?? Plugin.TargetManager.SoftTarget;

            return character != null && character == target ? _configs.HealthBar.ColorsConfig.TargetBordercolor : _configs.HealthBar.ColorsConfig.BorderColor;
        }

        private PluginConfigColor GetDistanceColor(ICharacter? character, PluginConfigColor color)
        {
            byte distance = character != null ? character.YalmDistanceX : byte.MaxValue;
            float currentAlpha = color.Vector.W * 100f;
            float alpha = _configs.HealthBar.RangeConfig.AlphaForDistance(distance, currentAlpha) / 100f;

            return color.WithAlpha(alpha);
        }

        // need to separate elements that have their own window so clipping doesn't get messy
        public List<(StrataLevel, Action)> GetElementsDrawActions(Vector2 origin)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();

            IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
            if (!Visible || Member is null || player == null)
            {
                StopMouseover();
                return drawActions;
            }

            ICharacter? character = Member.Character;

            // who's talking
            bool drawingWhosTalking = false;
            if (WhosTalkingIcon.Enabled && WhosTalkingIcon.Icon.Enabled && WhosTalkingIcon.EnabledForState(Member.WhosTalkingState))
            {
                IDalamudTextureWrap? texture = WhosTalkingHelper.Instance.GetTextureForState(Member.WhosTalkingState);

                if (texture != null)
                {
                    Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, WhosTalkingIcon.Icon.FrameAnchor);
                    Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + WhosTalkingIcon.Icon.Position, WhosTalkingIcon.Icon.Size, WhosTalkingIcon.Icon.Anchor);

                    drawActions.Add((WhosTalkingIcon.Icon.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(WhosTalkingIcon.Icon.ID, iconPos, WhosTalkingIcon.Icon.Size, false, (drawList) =>
                        {
                            ImGui.SetCursorPos(iconPos);
                            ImGui.Image(texture.Handle, WhosTalkingIcon.Icon.Size);
                        });
                    }
                    ));

                    drawingWhosTalking = true;
                }
            }

            // role/job icon
            if (RoleIcon.Enabled && (!drawingWhosTalking || !WhosTalkingIcon.ReplaceRoleJobIcon))
            {
                uint iconId = 0;

                // chocobo icon
                if (character is IBattleNpc battleNpc && battleNpc.BattleNpcKind == BattleNpcSubKind.Chocobo)
                {
                    iconId = JobsHelper.RoleIconIDForBattleCompanion + (uint)RoleIcon.Style * 100;
                }
                // role/job icon
                else if (Member.JobId > 0)
                {
                    iconId = RoleIcon.UseRoleIcons ?
                        JobsHelper.RoleIconIDForJob(Member.JobId, RoleIcon.UseSpecificDPSRoleIcons) :
                        JobsHelper.IconIDForJob(Member.JobId, (uint)RoleIcon.Style);
                }

                if (iconId > 0)
                {
                    Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RoleIcon.FrameAnchor);
                    Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RoleIcon.Position, RoleIcon.Size, RoleIcon.Anchor);

                    drawActions.Add((RoleIcon.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(RoleIcon.ID, iconPos, RoleIcon.Size, false, (drawList) =>
                        {
                            DrawHelper.DrawIcon(iconId, iconPos, RoleIcon.Size, false, drawList);
                        });
                    }
                    ));
                }
            }

            // sign icon
            if (SignIcon.Enabled)
            {
                uint? iconId = SignIcon.IconID(character);
                if (iconId.HasValue)
                {
                    Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, SignIcon.FrameAnchor);
                    Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + SignIcon.Position, SignIcon.Size, SignIcon.Anchor);

                    drawActions.Add((SignIcon.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(SignIcon.ID, iconPos, SignIcon.Size, false, (drawList) =>
                        {
                            DrawHelper.DrawIcon(iconId.Value, iconPos, SignIcon.Size, false, drawList);
                        });
                    }
                    ));
                }
            }

            // leader icon
            if (LeaderIcon.Enabled && Member.IsPartyLeader)
            {
                Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, LeaderIcon.FrameAnchor);
                Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + LeaderIcon.Position, LeaderIcon.Size, LeaderIcon.Anchor);

                drawActions.Add((LeaderIcon.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(LeaderIcon.ID, iconPos, LeaderIcon.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon(61521, iconPos, LeaderIcon.Size, false, drawList);
                    });
                }
                ));
            }

            // player status icon
            if (PlayerStatus.Enabled && PlayerStatus.Icon.Enabled)
            {
                uint? iconId = IconIdForStatus(Member.Status);
                if (iconId.HasValue)
                {
                    Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, PlayerStatus.Icon.FrameAnchor);
                    Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + PlayerStatus.Icon.Position, PlayerStatus.Icon.Size, PlayerStatus.Icon.Anchor);

                    drawActions.Add((PlayerStatus.Icon.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(PlayerStatus.Icon.ID, iconPos, PlayerStatus.Icon.Size, false, (drawList) =>
                        {
                            DrawHelper.DrawIcon(iconId.Value, iconPos, PlayerStatus.Icon.Size, false, drawList);
                        });
                    }
                    ));
                }
            }

            // ready check status icon
            if (Member.ReadyCheckStatus != ReadyCheckStatus.None && ReadyCheckIcon.Enabled && ReadyCheckIcon.Icon.Enabled && _readyCheckTexture != null)
            {
                Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, ReadyCheckIcon.Icon.FrameAnchor);
                Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + ReadyCheckIcon.Icon.Position, ReadyCheckIcon.Icon.Size, ReadyCheckIcon.Icon.Anchor);

                drawActions.Add((ReadyCheckIcon.Icon.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(ReadyCheckIcon.Icon.ID, iconPos, ReadyCheckIcon.Icon.Size, false, (drawList) =>
                    {
                        Vector2 uv0 = new Vector2(0.5f * (int)Member.ReadyCheckStatus, 0f);
                        Vector2 uv1 = new Vector2(0.5f + 0.5f * (int)Member.ReadyCheckStatus, 1f);
                        drawList.AddImage(_readyCheckTexture.Handle, iconPos, iconPos + ReadyCheckIcon.Icon.Size, uv0, uv1);
                    });
                }
                ));
            }

            // raise icon
            bool showingRaise = ShowingRaise();
            if (showingRaise && RaiseTracker.Icon.Enabled)
            {
                Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, RaiseTracker.Icon.FrameAnchor);
                Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + RaiseTracker.Icon.Position, RaiseTracker.Icon.Size, RaiseTracker.Icon.Anchor);

                drawActions.Add((RaiseTracker.Icon.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(RaiseTracker.Icon.ID, iconPos, RaiseTracker.Icon.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon(411, iconPos, RaiseTracker.Icon.Size, true, drawList);
                    });
                }
                ));
            }

            // invuln icon
            bool showingInvuln = ShowingInvuln();
            if (showingInvuln && InvulnTracker.Icon.Enabled)
            {
                Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, InvulnTracker.Icon.FrameAnchor);
                Vector2 iconPos = Utils.GetAnchoredPosition(parentPos + InvulnTracker.Icon.Position, InvulnTracker.Icon.Size, InvulnTracker.Icon.Anchor);

                drawActions.Add((InvulnTracker.Icon.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(InvulnTracker.Icon.ID, iconPos, InvulnTracker.Icon.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon(Member.InvulnStatus!.InvulnIcon, iconPos, InvulnTracker.Icon.Size, true, drawList);
                    });
                }
                ));
            }

            // mana
            if (ShowMana())
            {
                Vector2 parentPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.ManaBar.HealthBarAnchor);
                drawActions.Add((_configs.ManaBar.StrataLevel, () =>
                {
                    _manaBarHud.Actor = character;
                    _manaBarHud.PartyMember = Member;
                    _manaBarHud.PrepareForDraw(parentPos);
                    _manaBarHud.Draw(parentPos);
                }
                ));
            }

            // buffs / debuffs
            Vector2 buffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Buffs.HealthBarAnchor);
            drawActions.Add((_configs.Buffs.StrataLevel, () =>
            {
                _buffsListHud.Actor = character;
                _buffsListHud.PrepareForDraw(buffsPos);
                _buffsListHud.Draw(buffsPos);
            }
            ));

            Vector2 debuffsPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.Debuffs.HealthBarAnchor);
            drawActions.Add((_configs.Debuffs.StrataLevel, () =>
            {
                _debuffsListHud.Actor = character;
                _debuffsListHud.PrepareForDraw(debuffsPos);
                _debuffsListHud.Draw(debuffsPos);
            }
            ));

            // cooldown list
            Vector2 cooldownListPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CooldownList.HealthBarAnchor);
            drawActions.Add((_configs.CooldownList.StrataLevel, () =>
            {
                _cooldownListHud.Actor = character;
                _cooldownListHud.PrepareForDraw(cooldownListPos);
                _cooldownListHud.Draw(cooldownListPos);
            }
            ));

            // castbar
            Vector2 castbarPos = Utils.GetAnchoredPosition(Position, -_configs.HealthBar.Size, _configs.CastBar.HealthBarAnchor);
            drawActions.Add((_configs.CastBar.StrataLevel, () =>
            {
                _castbarHud.Actor = character;
                _castbarHud.PrepareForDraw(castbarPos);
                _castbarHud.Draw(castbarPos);
            }
            ));

            if (_configs.HealthBar.ShowLabels)
            {
                // name
                bool drawName = ShouldDrawName(character, showingRaise, showingInvuln);
                if (drawName)
                {
                    drawActions.Add((_configs.HealthBar.NameLabelConfig.StrataLevel, () =>
                    {
                        bool? playerName = null;
                        if (character == null || character.ObjectKind == ObjectKind.Player)
                        {
                            playerName = true;
                        }

                        _nameLabelHud.Draw(Position, _configs.HealthBar.Size, character, Member.Name, isPlayerName: playerName);
                    }
                    ));
                }

                // health label
                if (Member.MaxHP > 0)
                {
                    drawActions.Add((_configs.HealthBar.HealthLabelConfig.StrataLevel, () =>
                    {
                        _healthLabelHud.Draw(Position, _configs.HealthBar.Size, character, null, Member.HP, Member.MaxHP);
                    }
                    ));
                }

                // order
                if (character == null || character?.ObjectKind != ObjectKind.BattleNpc)
                {
                    string str = char.ConvertFromUtf32(0xE090 + Member.Order).ToString();

                    drawActions.Add((_configs.HealthBar.OrderNumberConfig.StrataLevel, () =>
                    {
                        _configs.HealthBar.OrderNumberConfig.SetText(str);
                        _orderLabelHud.Draw(Position, _configs.HealthBar.Size, character);
                    }
                    ));
                }

                // status
                string? statusString = StringForStatus(Member.Status);
                if (PlayerStatus.Enabled && PlayerStatus.Label.Enabled && statusString != null)
                {
                    drawActions.Add((PlayerStatus.Label.StrataLevel, () =>
                    {
                        PlayerStatus.Label.SetText(statusString);
                        _statusLabelHud.Draw(Position, _configs.HealthBar.Size);
                    }
                    ));
                }

                // raise label
                if (showingRaise)
                {
                    float duration = Math.Abs(Member.RaiseTime!.Value);

                    drawActions.Add((RaiseTracker.Icon.NumericLabel.StrataLevel, () =>
                    {
                        RaiseTracker.Icon.NumericLabel.SetValue(duration);
                        _raiseLabelHud.Draw(Position, _configs.HealthBar.Size);
                    }
                    ));
                }

                // invuln label
                if (showingInvuln)
                {
                    float duration = Math.Abs(Member.InvulnStatus!.InvulnTime);

                    drawActions.Add((InvulnTracker.Icon.NumericLabel.StrataLevel, () =>
                    {
                        InvulnTracker.Icon.NumericLabel.SetValue(duration);
                        _invulnLabelHud.Draw(Position, _configs.HealthBar.Size);
                    }
                    ));
                }
            }

            return drawActions;
        }

        private bool ShouldDrawName(ICharacter? character, bool showingRaise, bool showingInvuln)
        {
            if (showingRaise && RaiseTracker.HideNameWhenRaised)
            {
                return false;
            }

            if (showingInvuln && InvulnTracker.HideNameWhenInvuln)
            {
                return false;
            }

            if (Member != null && PlayerStatus.Enabled && PlayerStatus.HideName && Member.Status != PartyMemberStatus.None)
            {
                return false;
            }

            if (Member != null && ReadyCheckIcon.Enabled && ReadyCheckIcon.HideName && Member.ReadyCheckStatus != ReadyCheckStatus.None)
            {
                return false;
            }

            if (Utils.IsActorCasting(character) && _configs.CastBar.Enabled && _configs.CastBar.HideNameWhenCasting)
            {
                return false;
            }

            return true;
        }

        private bool ShowingRaise() =>
            Member != null && Member.RaiseTime.HasValue && RaiseTracker.Enabled &&
            (Member.RaiseTime.Value > 0 || RaiseTracker.KeepIconAfterCastFinishes);

        private bool ShowingInvuln() => Member != null && Member.InvulnStatus != null && InvulnTracker.Enabled && Member.InvulnStatus.InvulnTime > 0;

        private bool ShowMana()
        {
            if (Member == null)
            {
                return false;
            }

            var isHealer = JobsHelper.IsJobHealer(Member.JobId);

            return _configs.ManaBar.Enabled && Member.MaxHP > 0 && _configs.ManaBar.ManaBarDisplayMode switch
            {
                PartyFramesManaBarDisplayMode.Always => true,
                PartyFramesManaBarDisplayMode.HealersOnly => isHealer,
                PartyFramesManaBarDisplayMode.HealersAndRaiseJobs => isHealer || JobsHelper.IsJobWithRaise(Member.JobId, Member.Level),
                _ => true
            };
        }

        private static uint? IconIdForStatus(PartyMemberStatus status)
        {
            return status switch
            {
                PartyMemberStatus.ViewingCutscene => 61508,
                PartyMemberStatus.Offline => 61504,
                PartyMemberStatus.Dead => 61502,
                _ => null
            };
        }

        private static string? StringForStatus(PartyMemberStatus status)
        {
            return status switch
            {
                PartyMemberStatus.ViewingCutscene => "[Viewing Cutscene]",
                PartyMemberStatus.Offline => "[Offline]",
                PartyMemberStatus.Dead => "[Dead]",
                _ => null
            };
        }

        #region convenience
        private PartyFramesRoleIconConfig RoleIcon => _configs.Icons.Role;
        private SignIconConfig SignIcon => _configs.Icons.Sign;
        private PartyFramesLeaderIconConfig LeaderIcon => _configs.Icons.Leader;
        private PartyFramesPlayerStatusConfig PlayerStatus => _configs.Icons.PlayerStatus;
        private PartyFramesReadyCheckStatusConfig ReadyCheckIcon => _configs.Icons.ReadyCheckStatus;
        private PartyFramesWhosTalkingConfig WhosTalkingIcon => _configs.Icons.WhosTalking;
        private PartyFramesRaiseTrackerConfig RaiseTracker => _configs.Trackers.Raise;
        private PartyFramesInvulnTrackerConfig InvulnTracker => _configs.Trackers.Invuln;
        private PartyFramesCleanseTrackerConfig CleanseTracker => _configs.Trackers.Cleanse;
        #endregion
    }
}
