using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using HSUI.Config;
using HSUI.Helpers;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Jobs;
using HSUI.Interface.Nameplates;
using HSUI.Interface.Party;
using HSUI.Interface.PartyCooldowns;
using HSUI.Interface.StatusEffects;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Dalamud.Bindings.ImGui;
using System;
using System.Collections.Generic;
using System.Numerics;

namespace HSUI.Interface
{
    public class HudManager : IDisposable
    {
        private GridConfig? _gridConfig;
        private HUDOptionsConfig? _hudOptions;
        private DraggableHudElement? _selectedElement = null;

        private SortedList<PluginConfigObject, DraggableHudElement> _hudElements = null!;
        private List<IHudElementWithActor> _hudElementsUsingPlayer = null!;
        private List<IHudElementWithActor> _hudElementsUsingTarget = null!;
        private List<IHudElementWithActor> _hudElementsUsingTargetOfTarget = null!;
        private List<IHudElementWithActor> _hudElementsUsingFocusTarget = null!;
        private List<IHudElementWithPreview> _hudElementsWithPreview = null!;

        private UnitFrameHud _playerUnitFrameHud = null!;
        private UnitFrameHud _targetUnitFrameHud = null!;
        private UnitFrameHud _totUnitFrameHud = null!;
        private UnitFrameHud _focusTargetUnitFrameHud = null!;

        private PlayerCastbarHud _playerCastbarHud = null!;
        private CustomEffectsListHud _customEffectsHud = null!;
        private PrimaryResourceHud _playerManaBarHud = null!;
        private JobHud? _jobHud = null;

        private Dictionary<uint, JobHudTypes> _jobsMap = null!;
        private Dictionary<uint, Type> _unsupportedJobsMap = null!;
        private List<Type> _jobTypes = null!;

        private NameplatesHud _nameplatesHud = null!;

        private double _occupiedInQuestStartTime = -1;

        private HudHelper _hudHelper = new HudHelper();

        public HudManager()
        {
            CreateJobsMap();

            ConfigurationManager.Instance.ResetEvent += OnConfigReset;
            ConfigurationManager.Instance.LockEvent += OnHUDLockChanged;
            ConfigurationManager.Instance.ConfigClosedEvent += OnConfingWindowClosed;
            ConfigurationManager.Instance.StrataLevelsChangedEvent += OnStrataLevelsChanged;
            ConfigurationManager.Instance.GlobalVisibilityEvent += OnGlobalVisibilityChanged;

            CreateHudElements();
        }

        ~HudManager()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            _hudHelper.Dispose();

            foreach (var element in _hudElements.Values)
            {
                try { element.Dispose(); }
                catch (Exception ex) { Plugin.Logger.Error($"Error disposing HUD element {element.ID}: {ex.Message}"); }
            }
            try { _jobHud?.Dispose(); }
            catch (Exception ex) { Plugin.Logger.Error($"Error disposing JobHud: {ex.Message}"); }

            _hudElements.Clear();
            _hudElementsUsingPlayer.Clear();
            _hudElementsUsingTarget.Clear();
            _hudElementsUsingTargetOfTarget.Clear();
            _hudElementsUsingFocusTarget.Clear();

            ConfigurationManager.Instance.ResetEvent -= OnConfigReset;
            ConfigurationManager.Instance.LockEvent -= OnHUDLockChanged;
            ConfigurationManager.Instance.ConfigClosedEvent -= OnConfingWindowClosed;
            ConfigurationManager.Instance.StrataLevelsChangedEvent -= OnStrataLevelsChanged;
            ConfigurationManager.Instance.GlobalVisibilityEvent -= OnGlobalVisibilityChanged;
        }

        private void OnConfigReset(ConfigurationManager sender)
        {
            CreateHudElements();
            _jobHud = null;
        }

        private void OnHUDLockChanged(ConfigurationManager sender)
        {
            var draggingEnabled = !sender.LockHUD;

            foreach (var element in _hudElements.Values)
            {
                element.DraggingEnabled = draggingEnabled;
                element.Selected = false;
            }

            if (_jobHud != null)
            {
                _jobHud.DraggingEnabled = draggingEnabled;
            }

            _selectedElement = null;
        }

        private void OnConfingWindowClosed(ConfigurationManager sender)
        {
            if (_hudOptions == null || !_hudOptions.AutomaticPreviewDisabling)
            {
                return;
            }

            foreach (IHudElementWithPreview element in _hudElementsWithPreview)
            {
                element.StopPreview();
            }

            _nameplatesHud.StopPreview();
        }

        private void OnStrataLevelsChanged(ConfigurationManager sender, PluginConfigObject config)
        {
            SortedList<PluginConfigObject, DraggableHudElement> tmp = new SortedList<PluginConfigObject, DraggableHudElement>(new StrataLevelComparer<PluginConfigObject>());

            foreach (DraggableHudElement element in _hudElements.Values)
            {
                tmp.Add(element.GetConfig(), element);
            }

            _hudElements = tmp;
        }

        private void OnGlobalVisibilityChanged(ConfigurationManager sender, VisibilityConfig config)
        {
            foreach (DraggableHudElement element in _hudElements.Values)
            {
                if (element is IHudElementWithVisibilityConfig e)
                {
                    e.VisibilityConfig?.CopyFrom(config);
                }
            }

            foreach (Type jobType in _jobTypes)
            {
                JobConfig jobConfig = (JobConfig)ConfigurationManager.Instance.GetConfigObjectForType(jobType);
                jobConfig.VisibilityConfig.CopyFrom(config);
            }
        }

        private void OnDraggableElementSelected(DraggableHudElement sender)
        {
            foreach (var element in _hudElements.Values)
            {
                element.Selected = element == sender;
            }

            if (_jobHud != null)
            {
                _jobHud.Selected = _jobHud == sender;
            }

            _selectedElement = sender;
        }

        private void CreateHudElements()
        {
            _gridConfig = ConfigurationManager.Instance.GetConfigObject<GridConfig>();
            _hudOptions = ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();

            _hudElements = new SortedList<PluginConfigObject, DraggableHudElement>(new StrataLevelComparer<PluginConfigObject>());
            _hudElementsUsingPlayer = new List<IHudElementWithActor>();
            _hudElementsUsingTarget = new List<IHudElementWithActor>();
            _hudElementsUsingTargetOfTarget = new List<IHudElementWithActor>();
            _hudElementsUsingFocusTarget = new List<IHudElementWithActor>();
            _hudElementsWithPreview = new List<IHudElementWithPreview>();

            _nameplatesHud = new NameplatesHud(ConfigurationManager.Instance.GetConfigObject<NameplatesGeneralConfig>());

            CreateUnitFrames();
            CreateManaBars();
            CreateCastbars();
            CreateStatusEffectsLists();
            CreateMiscElements();

            foreach (var element in _hudElements.Values)
            {
                element.SelectEvent += OnDraggableElementSelected;
            }
        }

        private void CreateUnitFrames()
        {
            var playerUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<PlayerUnitFrameConfig>();
            _playerUnitFrameHud = new PlayerUnitFrameHud(playerUnitFrameConfig, "Player");
            _hudElements.Add(playerUnitFrameConfig, _playerUnitFrameHud);
            _hudElementsUsingPlayer.Add(_playerUnitFrameHud);
            _hudElementsWithPreview.Add(_playerUnitFrameHud);

            var targetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<TargetUnitFrameConfig>();
            _targetUnitFrameHud = new UnitFrameHud(targetUnitFrameConfig, "Target");
            _hudElements.Add(targetUnitFrameConfig, _targetUnitFrameHud);
            _hudElementsUsingTarget.Add(_targetUnitFrameHud);
            _hudElementsWithPreview.Add(_targetUnitFrameHud);

            var targetOfTargetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetUnitFrameConfig>();
            _totUnitFrameHud = new UnitFrameHud(targetOfTargetUnitFrameConfig, "Target of Target");
            _hudElements.Add(targetOfTargetUnitFrameConfig, _totUnitFrameHud);
            _hudElementsUsingTargetOfTarget.Add(_totUnitFrameHud);
            _hudElementsWithPreview.Add(_totUnitFrameHud);

            var focusTargetUnitFrameConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetUnitFrameConfig>();
            _focusTargetUnitFrameHud = new UnitFrameHud(focusTargetUnitFrameConfig, "Focus Target");
            _hudElements.Add(focusTargetUnitFrameConfig, _focusTargetUnitFrameHud);
            _hudElementsUsingFocusTarget.Add(_focusTargetUnitFrameHud);
            _hudElementsWithPreview.Add(_focusTargetUnitFrameHud);

            var partyFramesConfig = ConfigurationManager.Instance.GetConfigObject<PartyFramesConfig>();
            var partyFramesHud = new PartyFramesHud(partyFramesConfig, "Party Frames");
            _hudElements.Add(partyFramesConfig, partyFramesHud);
            _hudElementsWithPreview.Add(partyFramesHud);

            var allianceFrames1Config = ConfigurationManager.Instance.GetConfigObject<AllianceFrames1Config>();
            var allianceFrames1Hud = new AllianceFramesHud(allianceFrames1Config, "Alliance Frames 1");
            _hudElements.Add(allianceFrames1Config, allianceFrames1Hud);
            _hudElementsWithPreview.Add(allianceFrames1Hud);

            var allianceFrames2Config = ConfigurationManager.Instance.GetConfigObject<AllianceFrames2Config>();
            var allianceFrames2Hud = new AllianceFramesHud(allianceFrames2Config, "Alliance Frames 2");
            _hudElements.Add(allianceFrames2Config, allianceFrames2Hud);
            _hudElementsWithPreview.Add(allianceFrames2Hud);

            var enemyListConfig = ConfigurationManager.Instance.GetConfigObject<EnemyListConfig>();
            var enemyListHud = new EnemyListHud(enemyListConfig, "Enemy List");
            _hudElements.Add(enemyListConfig, enemyListHud);
            _hudElementsWithPreview.Add(enemyListHud);
        }

        private void CreateManaBars()
        {
            var playerManaBarConfig = ConfigurationManager.Instance.GetConfigObject<PlayerPrimaryResourceConfig>();
            _playerManaBarHud = new PrimaryResourceHud(playerManaBarConfig, "Player Mana Bar");
            _playerManaBarHud.ParentConfig = _playerUnitFrameHud.Config;
            _hudElements.Add(playerManaBarConfig, _playerManaBarHud);
            _hudElementsUsingPlayer.Add(_playerManaBarHud);

            var targetManaBarConfig = ConfigurationManager.Instance.GetConfigObject<TargetPrimaryResourceConfig>();
            var targetManaBarHud = new PrimaryResourceHud(targetManaBarConfig, "Target Mana Bar");
            targetManaBarHud.ParentConfig = _targetUnitFrameHud.Config;
            _hudElements.Add(targetManaBarConfig, targetManaBarHud);
            _hudElementsUsingTarget.Add(targetManaBarHud);

            var totManaBarConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetPrimaryResourceConfig>();
            var totManaBarHud = new PrimaryResourceHud(totManaBarConfig, "ToT Mana Bar");
            totManaBarHud.ParentConfig = _totUnitFrameHud.Config;
            _hudElements.Add(totManaBarConfig, totManaBarHud);
            _hudElementsUsingTargetOfTarget.Add(totManaBarHud);

            var focusManaBarConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetPrimaryResourceConfig>();
            var focusManaBarHud = new PrimaryResourceHud(focusManaBarConfig, "Focus Mana Bar");
            focusManaBarHud.ParentConfig = _focusTargetUnitFrameHud.Config;
            _hudElements.Add(focusManaBarConfig, focusManaBarHud);
            _hudElementsUsingFocusTarget.Add(focusManaBarHud);
        }

        private void CreateCastbars()
        {
            var playerCastbarConfig = ConfigurationManager.Instance.GetConfigObject<PlayerCastbarConfig>();
            _playerCastbarHud = new PlayerCastbarHud(playerCastbarConfig, "Player Castbar");
            _playerCastbarHud.ParentConfig = _playerUnitFrameHud.Config;
            _hudElements.Add(playerCastbarConfig, _playerCastbarHud);
            _hudElementsUsingPlayer.Add(_playerCastbarHud);
            _hudElementsWithPreview.Add(_playerCastbarHud);

            var targetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<TargetCastbarConfig>();
            var targetCastbar = new TargetCastbarHud(targetCastbarConfig, "Target Castbar");
            targetCastbar.ParentConfig = _targetUnitFrameHud.Config;
            _hudElements.Add(targetCastbarConfig, targetCastbar);
            _hudElementsUsingTarget.Add(targetCastbar);
            _hudElementsWithPreview.Add(targetCastbar);

            var targetOfTargetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<TargetOfTargetCastbarConfig>();
            var targetOfTargetCastbar = new TargetOfTargetCastbarHud(targetOfTargetCastbarConfig, "ToT Castbar");
            targetOfTargetCastbar.ParentConfig = _totUnitFrameHud.Config;
            _hudElements.Add(targetOfTargetCastbarConfig, targetOfTargetCastbar);
            _hudElementsUsingTargetOfTarget.Add(targetOfTargetCastbar);
            _hudElementsWithPreview.Add(targetOfTargetCastbar);

            var focusTargetCastbarConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetCastbarConfig>();
            var focusTargetCastbar = new FocusTargetCastbarHud(focusTargetCastbarConfig, "Focus Castbar");
            focusTargetCastbar.ParentConfig = _focusTargetUnitFrameHud.Config;
            _hudElements.Add(focusTargetCastbarConfig, focusTargetCastbar);
            _hudElementsUsingFocusTarget.Add(focusTargetCastbar);
            _hudElementsWithPreview.Add(focusTargetCastbar);
        }

        private void CreateStatusEffectsLists()
        {
            var playerBuffsConfig = ConfigurationManager.Instance.GetConfigObject<PlayerBuffsListConfig>();
            var playerBuffs = new StatusEffectsListHud(playerBuffsConfig, "Buffs");
            playerBuffs.ParentConfig = _playerUnitFrameHud.Config;
            _hudElements.Add(playerBuffsConfig, playerBuffs);
            _hudElementsUsingPlayer.Add(playerBuffs);
            _hudElementsWithPreview.Add(playerBuffs);

            var playerDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<PlayerDebuffsListConfig>();
            var playerDebuffs = new StatusEffectsListHud(playerDebuffsConfig, "Debuffs");
            playerDebuffs.ParentConfig = _playerUnitFrameHud.Config;
            _hudElements.Add(playerDebuffsConfig, playerDebuffs);
            _hudElementsUsingPlayer.Add(playerDebuffs);
            _hudElementsWithPreview.Add(playerDebuffs);

            var targetBuffsConfig = ConfigurationManager.Instance.GetConfigObject<TargetBuffsListConfig>();
            var targetBuffs = new StatusEffectsListHud(targetBuffsConfig, "Target Buffs");
            targetBuffs.ParentConfig = _targetUnitFrameHud.Config;
            _hudElements.Add(targetBuffsConfig, targetBuffs);
            _hudElementsUsingTarget.Add(targetBuffs);
            _hudElementsWithPreview.Add(targetBuffs);

            var targetDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<TargetDebuffsListConfig>();
            var targetDebuffs = new StatusEffectsListHud(targetDebuffsConfig, "Target Debuffs");
            targetDebuffs.ParentConfig = _targetUnitFrameHud.Config;
            _hudElements.Add(targetDebuffsConfig, targetDebuffs);
            _hudElementsUsingTarget.Add(targetDebuffs);
            _hudElementsWithPreview.Add(targetDebuffs);

            var focusTargetBuffsConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetBuffsListConfig>();
            var focusTargetBuffs = new StatusEffectsListHud(focusTargetBuffsConfig, "focusTarget Buffs");
            focusTargetBuffs.ParentConfig = _focusTargetUnitFrameHud.Config;
            _hudElements.Add(focusTargetBuffsConfig, focusTargetBuffs);
            _hudElementsUsingFocusTarget.Add(focusTargetBuffs);
            _hudElementsWithPreview.Add(focusTargetBuffs);

            var focusTargetDebuffsConfig = ConfigurationManager.Instance.GetConfigObject<FocusTargetDebuffsListConfig>();
            var focusTargetDebuffs = new StatusEffectsListHud(focusTargetDebuffsConfig, "focusTarget Debuffs");
            focusTargetDebuffs.ParentConfig = _focusTargetUnitFrameHud.Config;
            _hudElements.Add(focusTargetDebuffsConfig, focusTargetDebuffs);
            _hudElementsUsingFocusTarget.Add(focusTargetDebuffs);
            _hudElementsWithPreview.Add(focusTargetDebuffs);

            var custonEffectsConfig = ConfigurationManager.Instance.GetConfigObject<CustomEffectsListConfig>();
            _customEffectsHud = new CustomEffectsListHud(custonEffectsConfig, "Custom Effects");
            _hudElements.Add(custonEffectsConfig, _customEffectsHud);
            _hudElementsUsingPlayer.Add(_customEffectsHud);
            _hudElementsWithPreview.Add(_customEffectsHud);
        }

        private void CreateMiscElements()
        {
            var gcdIndicatorConfig = ConfigurationManager.Instance.GetConfigObject<GCDIndicatorConfig>();
            var gcdIndicator = new GCDIndicatorHud(gcdIndicatorConfig, "GCD Indicator");
            _hudElements.Add(gcdIndicatorConfig, gcdIndicator);
            _hudElementsUsingPlayer.Add(gcdIndicator);

            var mouseGcdIndicatorConfig = ConfigurationManager.Instance.GetConfigObject<MouseGCDIndicatorConfig>();
            var mouseGcdIndicator = new MouseGCDIndicatorHud(mouseGcdIndicatorConfig, "Mouse GCD Indicator");
            _hudElements.Add(mouseGcdIndicatorConfig, mouseGcdIndicator);
            _hudElementsUsingPlayer.Add(mouseGcdIndicator);

            var mpTickerConfig = ConfigurationManager.Instance.GetConfigObject<MPTickerConfig>();
            var mpTicker = new MPTickerHud(mpTickerConfig, "MP Ticker");
            _hudElements.Add(mpTickerConfig, mpTicker);
            _hudElementsUsingPlayer.Add(mpTicker);

            var expBarConfig = ConfigurationManager.Instance.GetConfigObject<ExperienceBarConfig>();
            var expBarHud = new ExperienceBarHud(expBarConfig, "Experience Bar");
            _hudElements.Add(expBarConfig, expBarHud);
            _hudElementsUsingPlayer.Add(expBarHud);

            var pullTimerConfig = ConfigurationManager.Instance.GetConfigObject<PullTimerConfig>();
            var pullTimerHud = new PullTimerHud(pullTimerConfig, "Pull Timer");
            _hudElements.Add(pullTimerConfig, pullTimerHud);
            _hudElementsUsingPlayer.Add(pullTimerHud);

            var limitBreakConfig = ConfigurationManager.Instance.GetConfigObject<LimitBreakConfig>();
            var limitBreakHud = new LimitBreakHud(limitBreakConfig, "Limit Break");
            _hudElements.Add(limitBreakConfig, limitBreakHud);

            var hotbarsConfig = ConfigurationManager.Instance.GetConfigObject<HotbarsConfig>();
            HotbarBarConfig[] hotbarConfigs = new HotbarBarConfig[]
            {
                ConfigurationManager.Instance.GetConfigObject<Hotbar1BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar2BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar3BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar4BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar5BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar6BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar7BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar8BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar9BarConfig>(),
                ConfigurationManager.Instance.GetConfigObject<Hotbar10BarConfig>()
            };
            for (int i = 0; i < hotbarConfigs.Length; i++)
            {
                var barConfig = hotbarConfigs[i];
                var barHud = new ActionBarsHud(barConfig, $"Hotbar {i + 1}");
                _hudElements.Add(barConfig, barHud);
                _hudElementsUsingPlayer.Add(barHud);
            }

            ControllerBarConfig[] controllerBarConfigs = new ControllerBarConfig[]
            {
                ConfigurationManager.Instance.GetConfigObject<ControllerBar1Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar2Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar3Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar4Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar5Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar6Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar7Config>(),
                ConfigurationManager.Instance.GetConfigObject<ControllerBar8Config>()
            };
            for (int i = 0; i < controllerBarConfigs.Length; i++)
            {
                var barConfig = controllerBarConfigs[i];
                var crossBarHud = new CrossBarHud(barConfig, $"Cross Bar {i + 1}");
                _hudElements.Add(barConfig, crossBarHud);
                _hudElementsUsingPlayer.Add(crossBarHud);
            }

            var partyCooldownsConfig = ConfigurationManager.Instance.GetConfigObject<PartyCooldownsConfig>();
            var partyCooldownsHud = new PartyCooldownsHud(partyCooldownsConfig, "Party Cooldowns");
            _hudElements.Add(partyCooldownsConfig, partyCooldownsHud);
            _hudElementsWithPreview.Add(partyCooldownsHud);
        }

        public void Draw(uint jobId)
        {
            if (!FontsManager.Instance.DefaultFontBuilt)
            {
                Plugin.UiBuilder.FontAtlas.BuildFontsAsync();
            }

            try
            {
                PullTimerHelper.Instance.Update();
            }
            catch { }

            TooltipsHelper.Instance.RemoveTooltip(); // remove tooltip from previous frame

            _hudHelper.Update();

            if (!ShouldBeVisible())
            {
                return;
            }

            WhosTalkingHelper.Instance?.Update();

            ClipRectsHelper.Instance.Update();

            ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0);
            ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0));
            ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0);

            ImGuiHelpers.ForceNextWindowMainViewport();
            ImGui.SetNextWindowPos(Vector2.Zero);
            ImGui.SetNextWindowSize(ImGui.GetMainViewport().Size);

            var begin = ImGui.Begin(
                "HSUI_HUD",
                ImGuiWindowFlags.NoTitleBar
              | ImGuiWindowFlags.NoScrollbar
              | ImGuiWindowFlags.AlwaysAutoResize
              | ImGuiWindowFlags.NoBackground
              | ImGuiWindowFlags.NoInputs
              | ImGuiWindowFlags.NoBringToFrontOnFocus
              | ImGuiWindowFlags.NoFocusOnAppearing
              | ImGuiWindowFlags.NoSavedSettings
            );

            ImGui.PopStyleVar(3);

            if (!begin)
            {
                ImGui.End();
                return;
            }

            UpdateJob(jobId);
            AssignActors();

            var origin = ImGui.GetMainViewport().Size / 2f;
            if (_hudOptions != null && _hudOptions.UseGlobalHudShift)
            {
                origin += _hudOptions.HudOffset;
            }

            // show only castbar during quest events
            if (ShouldOnlyShowCastbar())
            {
                _playerCastbarHud?.PrepareForDraw(origin);
                _playerCastbarHud?.Draw(origin);

                ImGui.End();
                return;
            }

            // grid
            if (_gridConfig is not null && _gridConfig.Enabled)
            {
                DraggablesHelper.DrawGrid(_gridConfig, _hudOptions, _selectedElement);
            }

            // nameplates
            if (_nameplatesHud.GetConfig().Enabled)
            {
                ClipRectsHelper.Instance?.AddNameplatesClipRects();

                _nameplatesHud.PrepareForDraw(origin);
                _nameplatesHud.Draw(origin);

                ClipRectsHelper.Instance?.RemoveNameplatesClipRects();
            }

            // draw elements
            lock (_hudElements)
            {
                DraggablesHelper.DrawElements(origin, _hudHelper, _hudElements.Values, _jobHud, _selectedElement);
            }

            // world object tooltip (mouseover in 3D world)
            TooltipsHelper.Instance.ShowWorldObjectTooltip();

            // tooltip
            TooltipsHelper.Instance.Draw();

            ImGui.End();
        }

        protected unsafe bool ShouldBeVisible()
        {
            if (!ConfigurationManager.Instance.ShowHUD || Plugin.ObjectTable.LocalPlayer == null)
            {
                return false;
            }

            bool hudHidden =
                Plugin.Condition[ConditionFlag.WatchingCutscene] ||
                Plugin.Condition[ConditionFlag.WatchingCutscene78] ||
                Plugin.Condition[ConditionFlag.OccupiedInCutSceneEvent] ||
                Plugin.Condition[ConditionFlag.CreatingCharacter] ||
                Plugin.Condition[ConditionFlag.BetweenAreas] ||
                Plugin.Condition[ConditionFlag.BetweenAreas51] ||
                Plugin.Condition[ConditionFlag.OccupiedSummoningBell];

            if (hudHidden)
            {
                return false;
            }

            // When "Show HUD during dialogue" is on, keep HSUI visible during NPC dialogue and object interaction
            // even if the game hides the parameter bar
            var hudOptions = ConfigurationManager.Instance?.GetConfigObject<HUDOptionsConfig>();
            if (hudOptions?.ShowHudDuringDialogue == true &&
                (Plugin.Condition[ConditionFlag.OccupiedInQuestEvent] || Plugin.Condition[ConditionFlag.OccupiedInEvent]))
            {
                return true;
            }

            AtkUnitBase* parameterWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_ParameterWidget", 1).Address;
            AtkUnitBase* fadeMiddleWidget = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("FadeMiddle", 1).Address;

            bool paramenterVisible = parameterWidget != null && parameterWidget->IsVisible;
            bool fadeMiddleVisible = fadeMiddleWidget != null && fadeMiddleWidget->IsVisible;

            return paramenterVisible && !fadeMiddleVisible;
        }

        protected bool ShouldOnlyShowCastbar()
        {
            var hudOptions = ConfigurationManager.Instance?.GetConfigObject<HUDOptionsConfig>();
            if (hudOptions?.ShowHudDuringDialogue == true)
                return false;

            // when in quest dialogs and events, hide everything except castbars
            // this includes talking to npcs or interacting with quest related stuff
            if (Plugin.Condition[ConditionFlag.OccupiedInQuestEvent] ||
                Plugin.Condition[ConditionFlag.OccupiedInEvent])
            {
                // we have to wait a bit to avoid weird flickering when clicking shiny stuff
                // we hide HSUI after half a second passed in this state
                // interestingly enough, default hotbars seem to do something similar
                var time = ImGui.GetTime();
                if (_occupiedInQuestStartTime > 0)
                {
                    if (time - _occupiedInQuestStartTime > 0.5)
                    {
                        return true;
                    }
                }
                else
                {
                    _occupiedInQuestStartTime = time;
                }
            }
            else
            {
                _occupiedInQuestStartTime = -1;
            }

            return false;
        }

        private void UpdateJob(uint newJobId)
        {
            if (_jobHud != null && _jobHud.Config.JobId == newJobId)
            {
                return;
            }

            JobConfig? config = null;

            // unsupported jobs
            if (_unsupportedJobsMap.ContainsKey(newJobId) && _unsupportedJobsMap.TryGetValue(newJobId, out var type))
            {
                config = (JobConfig)Activator.CreateInstance(type)!;
                _jobHud = new JobHud(config);
            }

            // supported jobs
            if (_jobsMap.TryGetValue(newJobId, out var types))
            {
                config = (JobConfig)ConfigurationManager.Instance.GetConfigObjectForType(types.ConfigType);
                _jobHud = (JobHud)Activator.CreateInstance(types.HudType, config, types.DisplayName)!;
                _jobHud.SelectEvent += OnDraggableElementSelected;
            }
        }

        private void AssignActors()
        {
            // player
            IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
            foreach (var element in _hudElementsUsingPlayer)
            {
                element.Actor = player;

                if (_jobHud != null)
                {
                    _jobHud.Actor = player;
                }
            }

            // target
            IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
            foreach (var element in _hudElementsUsingTarget)
            {
                element.Actor = target;

                if (_customEffectsHud != null)
                {
                    _customEffectsHud.TargetActor = target;
                }
            }

            // target of target
            IGameObject? targetOfTarget = Utils.FindTargetOfTarget(target, player, Plugin.ObjectTable);
            foreach (var element in _hudElementsUsingTargetOfTarget)
            {
                element.Actor = targetOfTarget;
            }

            // focus
            IGameObject? focusTarget = Plugin.TargetManager.FocusTarget;
            foreach (var element in _hudElementsUsingFocusTarget)
            {
                element.Actor = focusTarget;
            }

            // player mana bar
            if (_jobHud != null && _playerManaBarHud != null && !_jobHud.Config.UseDefaultPrimaryResourceBar)
            {
                _playerManaBarHud.ResourceType = PrimaryResourceTypes.None;
            }
        }

        protected void CreateJobsMap()
        {
            _jobsMap = new Dictionary<uint, JobHudTypes>()
            {
                // tanks
                [JobIDs.PLD] = new JobHudTypes(typeof(PaladinHud), typeof(PaladinConfig), "Paladin HUD"),
                [JobIDs.WAR] = new JobHudTypes(typeof(WarriorHud), typeof(WarriorConfig), "Warrior HUD"),
                [JobIDs.DRK] = new JobHudTypes(typeof(DarkKnightHud), typeof(DarkKnightConfig), "Dark Knight HUD"),
                [JobIDs.GNB] = new JobHudTypes(typeof(GunbreakerHud), typeof(GunbreakerConfig), "Gunbreaker HUD"),

                // healers
                [JobIDs.WHM] = new JobHudTypes(typeof(WhiteMageHud), typeof(WhiteMageConfig), "White Mage HUD"),
                [JobIDs.SCH] = new JobHudTypes(typeof(ScholarHud), typeof(ScholarConfig), "Scholar HUD"),
                [JobIDs.AST] = new JobHudTypes(typeof(AstrologianHud), typeof(AstrologianConfig), "Astrologian HUD"),
                [JobIDs.SGE] = new JobHudTypes(typeof(SageHud), typeof(SageConfig), "Sage HUD"),

                // melee
                [JobIDs.MNK] = new JobHudTypes(typeof(MonkHud), typeof(MonkConfig), "Monk HUD"),
                [JobIDs.DRG] = new JobHudTypes(typeof(DragoonHud), typeof(DragoonConfig), "Dragoon HUD"),
                [JobIDs.NIN] = new JobHudTypes(typeof(NinjaHud), typeof(NinjaConfig), "Ninja HUD"),
                [JobIDs.SAM] = new JobHudTypes(typeof(SamuraiHud), typeof(SamuraiConfig), "Samurai HUD"),
                [JobIDs.RPR] = new JobHudTypes(typeof(ReaperHud), typeof(ReaperConfig), "Reaper HUD"),
                [JobIDs.VPR] = new JobHudTypes(typeof(ViperHud), typeof(ViperConfig), "Viper HUD"),

                // ranged
                [JobIDs.BRD] = new JobHudTypes(typeof(BardHud), typeof(BardConfig), "Bard HUD"),
                [JobIDs.MCH] = new JobHudTypes(typeof(MachinistHud), typeof(MachinistConfig), "Mechanic HUD"),
                [JobIDs.DNC] = new JobHudTypes(typeof(DancerHud), typeof(DancerConfig), "Dancer HUD"),

                // casters
                [JobIDs.BLM] = new JobHudTypes(typeof(BlackMageHud), typeof(BlackMageConfig), "Black Mage HUD"),
                [JobIDs.SMN] = new JobHudTypes(typeof(SummonerHud), typeof(SummonerConfig), "Summoner HUD"),
                [JobIDs.RDM] = new JobHudTypes(typeof(RedMageHud), typeof(RedMageConfig), "Red Mage HUD"),
                [JobIDs.BLU] = new JobHudTypes(typeof(BlueMageHud), typeof(BlueMageConfig), "Blue Mage HUD"),
                [JobIDs.PCT] = new JobHudTypes(typeof(PictomancerHud), typeof(PictomancerConfig), "Pictomancer HUD")
            };

            _unsupportedJobsMap = new Dictionary<uint, Type>()
            {
                // base jobs
                [JobIDs.GLA] = typeof(GladiatorConfig),
                [JobIDs.MRD] = typeof(MarauderConfig),
                [JobIDs.PGL] = typeof(PugilistConfig),
                [JobIDs.LNC] = typeof(LancerConfig),
                [JobIDs.ROG] = typeof(RogueConfig),
                [JobIDs.ARC] = typeof(ArcherConfig),
                [JobIDs.THM] = typeof(ThaumaturgeConfig),
                [JobIDs.ACN] = typeof(ArcanistConfig),
                [JobIDs.CNJ] = typeof(ConjurerConfig),

                // crafters
                [JobIDs.CRP] = typeof(CarpenterConfig),
                [JobIDs.BSM] = typeof(BlacksmithConfig),
                [JobIDs.ARM] = typeof(ArmorerConfig),
                [JobIDs.GSM] = typeof(GoldsmithConfig),
                [JobIDs.LTW] = typeof(LeatherworkerConfig),
                [JobIDs.WVR] = typeof(WeaverConfig),
                [JobIDs.ALC] = typeof(AlchemistConfig),
                [JobIDs.CUL] = typeof(CulinarianConfig),

                // gatherers
                [JobIDs.MIN] = typeof(MinerConfig),
                [JobIDs.BOT] = typeof(BotanistConfig),
                [JobIDs.FSH] = typeof(FisherConfig)
            };

            _jobTypes = new List<Type>()
            {
                typeof(PaladinConfig),
                typeof(WarriorConfig),
                typeof(DarkKnightConfig),
                typeof(GunbreakerConfig),

                typeof(WhiteMageConfig),
                typeof(ScholarConfig),
                typeof(AstrologianConfig),
                typeof(SageConfig),

                typeof(MonkConfig),
                typeof(DragoonConfig),
                typeof(NinjaConfig),
                typeof(SamuraiConfig),
                typeof(ReaperConfig),
                typeof(ViperConfig),

                typeof(BardConfig),
                typeof(MachinistConfig),
                typeof(DancerConfig),

                typeof(BlackMageConfig),
                typeof(SummonerConfig),
                typeof(RedMageConfig),
                typeof(BlueMageConfig),
                typeof(PictomancerConfig)
            };
        }
    }

    internal struct JobHudTypes
    {
        public Type HudType;
        public Type ConfigType;
        public string DisplayName;

        public JobHudTypes(Type hudType, Type configType, string displayName)
        {
            HudType = hudType;
            ConfigType = configType;
            DisplayName = displayName;
        }
    }

    public static class ForcedJob
    {
        internal static bool Enabled;
        internal static uint ForcedJobId;
    }

    internal static class HUDConstants
    {
        internal static int BaseHUDOffsetY = (int)(ImGui.GetMainViewport().Size.Y * 0.3f);
        internal static int UnitFramesOffsetX = 160;
        internal static int PlayerCastbarY = BaseHUDOffsetY - 13;
        internal static int JobHudsBaseY = PlayerCastbarY - 14;

        internal static Vector2 DefaultBigUnitFrameSize = new Vector2(270, 50);
        internal static Vector2 DefaultSmallUnitFrameSize = new Vector2(120, 20);
        internal static Vector2 DefaultStatusEffectsListSize = new Vector2(292, 82);

        internal static Vector2 DefaultPlayerNameplateBarSize = new Vector2(120, 10);
        internal static Vector2 DefaultEnemyNameplateBarSize = new Vector2(220, 26);
    }
}
