using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
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.Collections.Generic;
using System.Numerics;
using Action = System.Action;
using Character = Dalamud.Game.ClientState.Objects.Types.ICharacter;
using StructsCharacter = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;

namespace HSUI.Interface.Nameplates
{
    public class Nameplate
    {
        protected NameplateConfig _config;
        public bool Enabled => _config.Enabled;

        protected LabelHud _nameLabelHud;
        protected LabelHud _titleLabelHud;

        public Nameplate(NameplateConfig config)
        {
            _config = config;

            _nameLabelHud = new LabelHud(config.NameLabelConfig);
            _titleLabelHud = new LabelHud(config.TitleLabelConfig);
        }

        protected bool IsVisible(IGameObject? actor)
        {
            if (!_config.Enabled ||
                actor == null ||
                !_config.VisibilityConfig.IsElementVisible(null) ||
                (_config.OnlyShowWhenTargeted && actor.Address != Plugin.TargetManager.Target?.Address))
            {
                return false;
            }

            return true;
        }

        public virtual List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (!IsVisible(data.GameObject)) { return drawActions; }

            drawActions.AddRange(GetMainLabelDrawActions(data));

            return drawActions;
        }

        protected List<(StrataLevel, Action)> GetMainLabelDrawActions(NameplateData data, NameplateAnchor? barAnchor = null)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            Vector2 origin = _config.Position + (barAnchor?.Position ?? data.ScreenPosition);

            Vector2 swapOffset = Vector2.Zero;
            if (_config.SwapLabelsWhenNeeded && (data.IsTitlePrefix || data.Title.Length == 0))
            {
                swapOffset = _config.TitleLabelConfig.Position - _config.NameLabelConfig.Position;
            }

            // name
            float nameAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.NameLabelConfig.Color.Vector.W);
            var (nameText, namePos, nameSize, nameColor) = _nameLabelHud.PreCalculate(origin + swapOffset, barAnchor?.Size, data.GameObject, data.Name, isPlayerName: data.Kind == ObjectKind.Player);
            drawActions.Add((_config.NameLabelConfig.StrataLevel, () =>
            {
                _nameLabelHud.DrawLabel(nameText, namePos, nameSize, nameColor, nameAlpha);
            }
            ));

            // title
            float titleAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.TitleLabelConfig.Color.Vector.W);
            var (titleText, titlePos, titleSize, titleColor) = _titleLabelHud.PreCalculate(origin - swapOffset, barAnchor?.Size, data.GameObject, title: data.Title);
            if (data.Title.Length > 0)
            {
                drawActions.Add((_config.TitleLabelConfig.StrataLevel, () =>
                {
                    _titleLabelHud.DrawLabel(titleText, titlePos, titleSize, titleColor, titleAlpha);
                }
                ));
            }

            return drawActions;
        }
    }

    public class NameplateWithBar : Nameplate
    {
        protected NameplateBarConfig BarConfig => ((NameplateWithBarConfig)_config).GetBarConfig();

        private LabelHud _leftLabelHud;
        private LabelHud _rightLabelHud;
        private LabelHud _optionalLabelHud;

        public NameplateWithBar(NameplateConfig config) : base(config)
        {
            _leftLabelHud = new LabelHud(BarConfig.LeftLabelConfig);
            _rightLabelHud = new LabelHud(BarConfig.RightLabelConfig);
            _optionalLabelHud = new LabelHud(BarConfig.OptionalLabelConfig);
        }

        public (bool, bool) GetMouseoverState(NameplateData data)
        {
            if (data.GameObject is not ICharacter character) { return (false, false); }
            if (!BarConfig.IsVisible(character.CurrentHp, character.MaxHp) || BarConfig.DisableInteraction)
            {
                return (false, false);
            }

            bool targeted = Plugin.TargetManager.Target?.Address == character.Address;
            Vector2 barSize = BarConfig.GetSize(targeted);

            Vector2 origin = _config.Position + data.ScreenPosition;
            Vector2 barPos = Utils.GetAnchoredPosition(origin, barSize, BarConfig.Anchor) + BarConfig.Position;
            var (areaStart, areaEnd) = BarConfig.MouseoverAreaConfig.GetArea(barPos, barSize);

            bool isHovering = ImGui.IsMouseHoveringRect(areaStart, areaEnd);
            bool ignoreMouseover = BarConfig.MouseoverAreaConfig.Enabled && BarConfig.MouseoverAreaConfig.Ignore;

            return (isHovering, ignoreMouseover);
        }

        public unsafe List<(StrataLevel, Action)> GetBarDrawActions(NameplateData data)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (!IsVisible(data.GameObject)) { return drawActions; }
            if (data.GameObject is not Character character) { return drawActions; }

            uint currentHp = character.CurrentHp;
            uint maxHp = character.MaxHp;

            if (!BarConfig.IsVisible(currentHp, maxHp)) { return drawActions; }

            // colors
            PluginConfigColor fillColor = GetFillColor(character, currentHp, maxHp);
            fillColor = fillColor.WithAlpha(_config.RangeConfig.AlphaForDistance(data.Distance, fillColor.Vector.W));

            PluginConfigColor bgColor = GetBackgroundColor(character);
            bgColor = bgColor.WithAlpha(_config.RangeConfig.AlphaForDistance(data.Distance, bgColor.Vector.W));

            bool targeted = character.Address == Plugin.TargetManager.Target?.Address;
            PluginConfigColor borderColor = targeted ? BarConfig.TargetedBorderColor : BarConfig.BorderColor;
            borderColor = borderColor.WithAlpha(
                _config.RangeConfig.AlphaForDistance(data.Distance, BarConfig.BorderColor.Vector.W)
            );

            // bar
            Vector2 barSize = BarConfig.GetSize(targeted);
            Rect background = new Rect(BarConfig.Position, barSize, bgColor);
            Rect healthFill = BarUtilities.GetFillRect(BarConfig.Position, barSize, BarConfig.FillDirection, fillColor, currentHp, maxHp);

            BarHud bar = new BarHud(
                BarConfig.ID,
                BarConfig.DrawBorder,
                borderColor,
                targeted ? BarConfig.TargetedBorderThickness : BarConfig.BorderThickness,
                BarConfig.Anchor,
                character,
                current: currentHp,
                max: maxHp,
                shadowConfig: BarConfig.ShadowConfig,
                barTextureName: BarConfig.BarTextureName,
                barTextureDrawMode: BarConfig.BarTextureDrawMode
            );

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

            // shield
            PluginConfigColor shieldColor = BarConfig.ShieldConfig.Color.WithAlpha(
                _config.RangeConfig.AlphaForDistance(data.Distance, BarConfig.ShieldConfig.Color.Vector.W)
            );
            BarUtilities.AddShield(bar, BarConfig, BarConfig.ShieldConfig, character, healthFill.Size, shieldColor);

            // draw bar
            Vector2 origin = _config.Position + data.ScreenPosition;
            drawActions.AddRange(bar.GetDrawActions(origin, _config.StrataLevel));

            // mouseover area
            BarHud? mouseoverAreaBar = BarConfig.MouseoverAreaConfig.GetBar(
                BarConfig.Position,
                barSize,
                BarConfig.ID + "_mouseoverArea",
                BarConfig.Anchor
            );

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

            // labels
            Vector2 barPos = Utils.GetAnchoredPosition(origin, barSize, BarConfig.Anchor) + BarConfig.Position;
            LabelHud[] labels = GetLabels(maxHp);
            foreach (LabelHud label in labels)
            {
                LabelConfig labelConfig = (LabelConfig)label.GetConfig();
                float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, labelConfig.Color.Vector.W);
                var (labelText, labelPos, labelSize, labelColor) = label.PreCalculate(barPos, barSize, data.GameObject, data.Name, currentHp, maxHp, data.Kind == ObjectKind.Player);

                drawActions.Add((labelConfig.StrataLevel, () =>
                {
                    label.DrawLabel(labelText, labelPos, labelSize, labelColor, alpha);
                }
                ));
            }

            return drawActions;
        }

        public override List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (!IsVisible(data.GameObject)) { return drawActions; }

            NameplateAnchor? barAnchor = GetBarAnchor(data);
            drawActions.AddRange(GetMainLabelDrawActions(data, barAnchor));

            // Quest/state icon (e.g. ! ? above NPCs) - drawn when we have icon config and game provided icon ID
            if (_config is NameplateWithNPCBarConfig npcConfig &&
                npcConfig.IconConfig.Enabled &&
                data.NamePlateIconId > 0)
            {
                float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, 1f);
                Vector2 anchorPos = barAnchor?.Position ?? (_config.Position + data.ScreenPosition);
                Vector2 anchorSize = barAnchor?.Size ?? Vector2.Zero;
                var pos = Utils.GetAnchoredPosition(anchorPos, -anchorSize, npcConfig.IconConfig.FrameAnchor);
                Vector2 iconPos = Utils.GetAnchoredPosition(pos + npcConfig.IconConfig.Position, npcConfig.IconConfig.Size, npcConfig.IconConfig.Anchor);

                drawActions.Add((npcConfig.IconConfig.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(_config.ID + "_npcIcon", iconPos, npcConfig.IconConfig.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, npcConfig.IconConfig.Size, false, alpha, drawList);
                    });
                }));
            }

            return drawActions;
        }

        protected virtual NameplateAnchor? GetBarAnchor(NameplateData data)
        {
            if (data.GameObject is Character chara &&
                BarConfig.IsVisible(chara.CurrentHp, chara.MaxHp))
            {
                bool targeted = Plugin.TargetManager.Target?.Address == data.GameObject.Address;
                Vector2 size = BarConfig.GetSize(targeted);
                Vector2 pos = Utils.GetAnchoredPosition(data.ScreenPosition + BarConfig.Position, size, BarConfig.Anchor);

                return new NameplateAnchor(pos, size);
            }

            return null;
        }

        private LabelHud[] GetLabels(uint maxHp)
        {
            List<LabelHud> labels = new List<LabelHud>();

            if (BarConfig.HideHealthIfPossible && maxHp <= 0)
            {
                if (!Utils.IsHealthLabel(BarConfig.LeftLabelConfig))
                {
                    labels.Add(_leftLabelHud);
                }

                if (!Utils.IsHealthLabel(BarConfig.RightLabelConfig))
                {
                    labels.Add(_rightLabelHud);
                }

                if (!Utils.IsHealthLabel(BarConfig.OptionalLabelConfig))
                {
                    labels.Add(_optionalLabelHud);
                }
            }
            else
            {
                labels.Add(_leftLabelHud);
                labels.Add(_rightLabelHud);
                labels.Add(_optionalLabelHud);
            }

            return labels.ToArray();
        }

        protected virtual PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
        {
            return ColorUtils.ColorForCharacter(
                character,
                currentHp,
                maxHp,
                false,
                false,
                BarConfig.ColorByHealth
            ) ?? BarConfig.FillColor;
        }

        protected virtual PluginConfigColor GetBackgroundColor(Character character)
        {
            return BarConfig.BackgroundColor;
        }
    }

    public class NameplateWithBarAndExtras : NameplateWithBar
    {
        public NameplateWithBarAndExtras(NameplateConfig config) : base(config)
        {
        }

        public override List<(StrataLevel, Action)> GetElementsDrawActions(NameplateData data)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (!IsVisible(data.GameObject)) { return drawActions; }

            NameplateAnchor? barAnchor = GetBarAnchor(data);
            Vector2 origin = _config.Position + (barAnchor?.Position ?? data.ScreenPosition);

            Vector2 swapOffset = Vector2.Zero;
            if (_config.SwapLabelsWhenNeeded && (data.IsTitlePrefix || data.Title.Length == 0))
            {
                swapOffset = _config.TitleLabelConfig.Position - _config.NameLabelConfig.Position;
            }

            // name
            float nameAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.NameLabelConfig.Color.Vector.W);
            var (nameText, namePos, nameSize, nameColor) = _nameLabelHud.PreCalculate(origin + swapOffset, barAnchor?.Size, data.GameObject, data.Name, isPlayerName: data.Kind == ObjectKind.Player);
            drawActions.Add((_config.NameLabelConfig.StrataLevel, () =>
            {
                _nameLabelHud.DrawLabel(nameText, namePos, nameSize, nameColor, nameAlpha);
            }
            ));

            // title
            float titleAlpha = _config.RangeConfig.AlphaForDistance(data.Distance, _config.TitleLabelConfig.Color.Vector.W);
            var (titleText, titlePos, titleSize, titleColor) = _titleLabelHud.PreCalculate(origin - swapOffset, barAnchor?.Size, data.GameObject, title: data.Title);
            if (data.Title.Length > 0)
            {
                drawActions.Add((_config.TitleLabelConfig.StrataLevel, () =>
                {
                    _titleLabelHud.DrawLabel(titleText, titlePos, titleSize, titleColor, titleAlpha);
                }
                ));
            }

            // extras anchor
            NameplateExtrasAnchors extrasAnchors = new NameplateExtrasAnchors(
                barAnchor,
                _config.NameLabelConfig.Enabled ? new NameplateAnchor(namePos, nameSize) : null,
                _config.TitleLabelConfig.Enabled && data.Title.Length > 0 ? new NameplateAnchor(titlePos, titleSize) : null
            );

            drawActions.AddRange(GetExtrasDrawActions(data, extrasAnchors));

            return drawActions;
        }

        protected virtual List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
        {
            // override
            return new List<(StrataLevel, Action)>();
        }
    }

    public class NameplateWithPlayerBar : NameplateWithBarAndExtras
    {
        private NameplateWithPlayerBarConfig Config => (NameplateWithPlayerBarConfig)_config;

        public NameplateWithPlayerBar(NameplateWithPlayerBarConfig config) : base(config)
        {
        }

        protected override List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (data.GameObject is not IPlayerCharacter character) { return drawActions; }

            float alpha = _config.RangeConfig.AlphaForDistance(data.Distance);

            // role/job icon
            if (Config.RoleIconConfig.Enabled)
            {
                NameplateAnchor? anchor = anchors.GetAnchor(Config.RoleIconConfig.NameplateLabelAnchor, Config.RoleIconConfig.PrioritizeHealthBarAnchor);
                anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);

                uint jobId = character.ClassJob.RowId;
                uint iconId = Config.RoleIconConfig.UseRoleIcons ?
                        JobsHelper.RoleIconIDForJob(jobId, Config.RoleIconConfig.UseSpecificDPSRoleIcons) :
                        JobsHelper.IconIDForJob(jobId, (uint)Config.RoleIconConfig.Style);

                if (iconId > 0)
                {
                    var pos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.RoleIconConfig.FrameAnchor);
                    var iconPos = Utils.GetAnchoredPosition(pos + Config.RoleIconConfig.Position, Config.RoleIconConfig.Size, Config.RoleIconConfig.Anchor);

                    drawActions.Add((Config.RoleIconConfig.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(_config.ID + "_jobIcon", iconPos, Config.RoleIconConfig.Size, false, (drawList) =>
                        {
                            DrawHelper.DrawIcon(iconId, iconPos, Config.RoleIconConfig.Size, false, alpha, drawList);
                        });
                    }
                    ));
                }
            }

            // state icon
            if (Config.StateIconConfig.Enabled && 
                data.NamePlateIconId > 0 && 
                Config.StateIconConfig.ShouldDrawIcon(data.NamePlateIconId))
            {
                NameplateAnchor? anchor = anchors.GetAnchor(Config.StateIconConfig.NameplateLabelAnchor, Config.StateIconConfig.PrioritizeHealthBarAnchor);
                anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);

                var pos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.StateIconConfig.FrameAnchor);
                var iconPos = Utils.GetAnchoredPosition(pos + Config.StateIconConfig.Position, Config.StateIconConfig.Size, Config.StateIconConfig.Anchor);

                drawActions.Add((Config.StateIconConfig.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(_config.ID + "_stateIcon", iconPos, Config.StateIconConfig.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, Config.StateIconConfig.Size, false, alpha, drawList);
                    });
                }
                ));
            }

            return drawActions;
        }

        protected override PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
        {
            NameplatePlayerBarConfig config = (NameplatePlayerBarConfig)BarConfig;

            return ColorUtils.ColorForCharacter(
                character,
                currentHp,
                maxHp,
                config.UseJobColor,
                config.UseRoleColor,
                config.ColorByHealth
            ) ?? config.FillColor;
        }

        protected override PluginConfigColor GetBackgroundColor(Character character)
        {
            NameplatePlayerBarConfig config = (NameplatePlayerBarConfig)BarConfig;

            if (config.UseJobColorAsBackgroundColor)
            {
                return GlobalColors.Instance.SafeColorForJobId(character.ClassJob.RowId);
            }
            else if (config.UseRoleColorAsBackgroundColor)
            {
                return GlobalColors.Instance.SafeRoleColorForJobId(character.ClassJob.RowId);
            }

            return config.BackgroundColor;
        }
    }

    public class NameplateWithEnemyBar : NameplateWithBarAndExtras
    {
        private NameplateWithEnemyBarConfig Config => (NameplateWithEnemyBarConfig)_config;

        private LabelHud _orderLabelHud;
        private StatusEffectsListHud _debuffsHud;
        private NameplateCastbarHud _castbarHud;

        public NameplateWithEnemyBar(NameplateWithEnemyBarConfig config) : base(config)
        {
            _orderLabelHud = new LabelHud(config.BarConfig.OrderLabelConfig);
            _debuffsHud = new StatusEffectsListHud(config.DebuffsConfig);
            _castbarHud = new NameplateCastbarHud(config.CastbarConfig);
        }

        public void StopPreview()
        {
            _debuffsHud.StopPreview();
            _castbarHud.StopPreview();
        }

        protected override List<(StrataLevel, Action)> GetExtrasDrawActions(NameplateData data, NameplateExtrasAnchors anchors)
        {
            List<(StrataLevel, Action)> drawActions = new List<(StrataLevel, Action)>();
            if (data.GameObject is not Character character) { return drawActions; }

            NameplateEnemyBarConfig barConfig = Config.BarConfig;
            NameplateAnchor? anchor = barConfig.IsVisible(character.CurrentHp, character.MaxHp) ? anchors.BarAnchor : anchors.NameLabelAnchor;
            anchor = anchor ?? new NameplateAnchor(_config.Position + data.ScreenPosition, Vector2.Zero);

            // order label
            float alpha = _config.RangeConfig.AlphaForDistance(data.Distance, barConfig.OrderLabelConfig.Color.Vector.W);

            barConfig.OrderLabelConfig.SetText(data.Order);
            var (labelText, labelPos, labelSize, labelColor) = _orderLabelHud.PreCalculate(anchor.Value.Position, anchor.Value.Size, data.GameObject);
            drawActions.Add((barConfig.OrderLabelConfig.StrataLevel, () =>
            {
                _orderLabelHud.DrawLabel(labelText, labelPos, labelSize, labelColor, alpha);
            }
            ));

            // debuffs
            Vector2 buffsPos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.DebuffsConfig.HealthBarAnchor);
            drawActions.Add((Config.DebuffsConfig.StrataLevel, () =>
            {
                _debuffsHud.Actor = character;
                _debuffsHud.PrepareForDraw(buffsPos);
                _debuffsHud.Draw(buffsPos);
            }
            ));

            // castbar
            Vector2 castbarPos = Utils.GetAnchoredPosition(anchor.Value.Position, -anchor.Value.Size, Config.CastbarConfig.HealthBarAnchor);
            drawActions.Add((Config.CastbarConfig.StrataLevel, () =>
            {
                _castbarHud.ParentSize = anchor.Value.Size;
                _castbarHud.Actor = character;
                _castbarHud.PrepareForDraw(castbarPos);
                _castbarHud.Draw(castbarPos);
            }
            ));

            // icon
            if (Config.IconConfig.Enabled && data.NamePlateIconId > 0)
            {
                anchor = anchors.GetAnchor(Config.IconConfig.NameplateLabelAnchor, Config.IconConfig.PrioritizeHealthBarAnchor);
                anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);

                var pos = Utils.GetAnchoredPosition(_config.Position + anchor.Value.Position, -anchor.Value.Size, Config.IconConfig.FrameAnchor);
                var iconPos = Utils.GetAnchoredPosition(pos + Config.IconConfig.Position, Config.IconConfig.Size, Config.IconConfig.Anchor);

                drawActions.Add((Config.IconConfig.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(_config.ID + "_enemyIcon", iconPos, Config.IconConfig.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon((uint)data.NamePlateIconId, iconPos, Config.IconConfig.Size, false, alpha, drawList);
                    });
                }
                ));

            }

            // company icon (PvP Frontline: Maelstrom, Flames, Adders)
            var gcIconOverrides = (Config.CompanyIconConfig.IconIdTeam1, Config.CompanyIconConfig.IconIdTeam2, Config.CompanyIconConfig.IconIdTeam3);
            if (Config.CompanyIconConfig.Enabled && Utils.GrandCompanyIconIdForPvPEnemy(data.GameObject, gcIconOverrides) is uint gcIconId)
            {
                anchor = anchors.GetAnchor(Config.CompanyIconConfig.NameplateLabelAnchor, Config.CompanyIconConfig.PrioritizeHealthBarAnchor);
                anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);

                var pos = Utils.GetAnchoredPosition(_config.Position + anchor.Value.Position, -anchor.Value.Size, Config.CompanyIconConfig.FrameAnchor);
                var iconPos = Utils.GetAnchoredPosition(pos + Config.CompanyIconConfig.Position, Config.CompanyIconConfig.Size, Config.CompanyIconConfig.Anchor);

                drawActions.Add((Config.CompanyIconConfig.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(_config.ID + "_enemyCompanyIcon", iconPos, Config.CompanyIconConfig.Size, false, (drawList) =>
                    {
                        DrawHelper.DrawIcon(gcIconId, iconPos, Config.CompanyIconConfig.Size, false, alpha, drawList);
                    });
                }
                ));
            }

            // role/job icon (enemy players only; has ClassJob)
            if (Config.RoleIconConfig.Enabled && data.GameObject is IPlayerCharacter playerCharacter)
            {
                uint jobId = playerCharacter.ClassJob.RowId;
                uint iconId = Config.RoleIconConfig.UseRoleIcons
                    ? JobsHelper.RoleIconIDForJob(jobId, Config.RoleIconConfig.UseSpecificDPSRoleIcons)
                    : JobsHelper.IconIDForJob(jobId, (uint)Config.RoleIconConfig.Style);

                if (iconId > 0)
                {
                    anchor = anchors.GetAnchor(Config.RoleIconConfig.NameplateLabelAnchor, Config.RoleIconConfig.PrioritizeHealthBarAnchor);
                    anchor = anchor ?? new NameplateAnchor(data.ScreenPosition, Vector2.Zero);

                    var pos = Utils.GetAnchoredPosition(_config.Position + anchor.Value.Position, -anchor.Value.Size, Config.RoleIconConfig.FrameAnchor);
                    var iconPos = Utils.GetAnchoredPosition(pos + Config.RoleIconConfig.Position, Config.RoleIconConfig.Size, Config.RoleIconConfig.Anchor);

                    drawActions.Add((Config.RoleIconConfig.StrataLevel, () =>
                    {
                        DrawHelper.DrawInWindow(_config.ID + "_enemyRoleJobIcon", iconPos, Config.RoleIconConfig.Size, false, (drawList) =>
                        {
                            DrawHelper.DrawIcon(iconId, iconPos, Config.RoleIconConfig.Size, false, alpha, drawList);
                        });
                    }
                    ));
                }
            }

            return drawActions;
        }

        protected override unsafe PluginConfigColor GetFillColor(Character character, uint currentHp, uint maxHp)
        {
            NameplateEnemyBarConfig config = (NameplateEnemyBarConfig)BarConfig;

            bool targetingPlayer = character.TargetObjectId == Plugin.ObjectTable.LocalPlayer?.GameObjectId;
            if (targetingPlayer && config.UseCustomColorWhenBeingTargeted)
            {
                return config.CustomColorWhenBeingTargeted;
            }

            if (config.UseStateColor)
            {
                StructsCharacter* chara = (StructsCharacter*)character.Address;
                byte nameplateColorId = chara->GetNamePlateColorType();

                switch (nameplateColorId) {
                    case 7: return (character.StatusFlags & StatusFlags.Hostile) != 0 ? config.UnengagedHostileColor : config.UnengagedColor;
                    case 9: return config.EngagedColor;
                    case 10: return config.ClaimedColor;
                    case 11: return config.UnclaimedColor;
                    default: break;
                }
            }

            return base.GetFillColor(character, currentHp, maxHp);
        }
    }

    #region utils
    public struct NameplateAnchor
    {
        public Vector2 Position;
        public Vector2 Size;

        internal NameplateAnchor(Vector2 position, Vector2 size)
        {
            Position = position;
            Size = size;
        }
    }

    public struct NameplateExtrasAnchors
    {
        public NameplateAnchor? BarAnchor;
        public NameplateAnchor? NameLabelAnchor;
        public NameplateAnchor? TitleLabelAnchor;
        public NameplateAnchor? HighestLabelAnchor;
        public NameplateAnchor? LowestLabelAnchor;
        private NameplateAnchor? DefaultLabelAnchor;

        internal NameplateExtrasAnchors(NameplateAnchor? barAnchor, NameplateAnchor? nameLabelAnchor, NameplateAnchor? titleLabelAnchor)
        {
            BarAnchor = barAnchor;
            NameLabelAnchor = nameLabelAnchor;
            TitleLabelAnchor = titleLabelAnchor;
            DefaultLabelAnchor = nameLabelAnchor;

            float nameY = -1;
            if (nameLabelAnchor.HasValue)
            {
                nameY = nameLabelAnchor.Value.Position.Y;
            }

            float titleY = -1;
            if (titleLabelAnchor.HasValue)
            {
                titleY = titleLabelAnchor.Value.Position.Y;
            }

            if (nameY == -1)
            {
                DefaultLabelAnchor = titleLabelAnchor;
            }
            else if (nameY < titleY)
            {
                HighestLabelAnchor = nameLabelAnchor;
                LowestLabelAnchor = titleLabelAnchor;
            }
            else if (nameY > titleY)
            {
                HighestLabelAnchor = titleLabelAnchor;
                LowestLabelAnchor = nameLabelAnchor;
            }
        }

        internal NameplateAnchor? GetAnchor(NameplateLabelAnchor label, bool prioritizeHealthBar)
        {
            if (prioritizeHealthBar && BarAnchor != null) { return BarAnchor; }

            NameplateAnchor? labelAnchor = null;

            switch (label)
            {
                case NameplateLabelAnchor.Name: labelAnchor = NameLabelAnchor; break;
                case NameplateLabelAnchor.Title: labelAnchor = TitleLabelAnchor; break;
                case NameplateLabelAnchor.Highest: labelAnchor = HighestLabelAnchor; break;
                case NameplateLabelAnchor.Lowest: labelAnchor = LowestLabelAnchor; break;
            }

            return labelAnchor ?? DefaultLabelAnchor;
        }
        #endregion
    }
}
