using Dalamud.Game.Addon.Lifecycle;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects.SubKinds;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.EnemyList;
using HSUI.Interface.GeneralElements;
using HSUI.Interface.Party;
using HSUI.Interface.Jobs;
using HSUI.Interface.Nameplates;
using HSUI.Interface.StatusEffects;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using System.Numerics;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;

namespace HSUI.Interface
{
    public class HudHelper : IDisposable
    {
        private HUDOptionsConfig Config => ConfigurationManager.Instance.GetConfigObject<HUDOptionsConfig>();

        private bool _firstUpdate = true;

        private Vector2 _castBarPos = Vector2.Zero;
        private bool _hidingCastBar = false;
        private Vector2 _pullTimerPos = Vector2.Zero;
        private bool _hidingPullTimer = false;

        public HudHelper()
        {
            Config.ValueChangeEvent += ConfigValueChanged;
        }

        ~HudHelper()
        {
            Dispose(false);
        }

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

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

            Config.ValueChangeEvent -= ConfigValueChanged;

            void RestoreDefaults()
            {
                try
                {
                    SetGameHudElementsHidden(Array.Empty<uint>(), true);
                    UpdateDefaultCastBar(true);
                    UpdateDefaultPulltimer(true);
                    UpdateDefaultNameplates(true);
                    UpdateJobGauges(true);
                }
                catch (Exception ex)
                {
                    Plugin.Logger.Error($"Exception during HudHelper.Dispose restore: {ex.Message}");
                }
            }

            if (Plugin.Framework.IsInFrameworkUpdateThread && Plugin.ObjectTable.LocalPlayer != null)
            {
                RestoreDefaults();
            }
            else
            {
                try
                {
                    Plugin.Framework.RunOnFrameworkThread(() =>
                    {
                        if (Plugin.ObjectTable.LocalPlayer != null)
                            RestoreDefaults();
                    });
                }
                catch (Exception ex)
                {
                    Plugin.Logger.Error($"Exception scheduling HudHelper.Dispose restore: {ex.Message}");
                }
            }
        }

        public void Update()
        {
            try
            {
                if (_firstUpdate)
                {
                    _firstUpdate = false;
                    UpdateDefaultCastBar();
                    UpdateDefaultPulltimer();
                }

                UpdateJobGauges();
                UpdateDefaultHudElementsHidden();
                UpdateDefaultCastBar();
                UpdateDefaultNameplates();
            }
            catch (Exception ex)
            {
                var now = DateTime.UtcNow;
                if ((now - _lastHudElementsErrorLog).TotalSeconds >= HudElementsErrorLogIntervalSeconds)
                {
                    _lastHudElementsErrorLog = now;
                    Plugin.Logger.Warning($"[HSUI] HudHelper.Update: {ex.Message}");
                }
            }
        }

        public bool IsElementHidden(HudElement element)
        {
            IHudElementWithVisibilityConfig? e = element as IHudElementWithVisibilityConfig;
            if (e == null || e.VisibilityConfig == null) { return false; }

            return !e.VisibilityConfig.IsElementVisible(element);
        }

        private void ConfigValueChanged(object sender, OnChangeBaseArgs e)
        {
            switch (e.PropertyName)
            {
                case "HideDefaultHudWhenReplaced":
                    UpdateDefaultHudElementsHidden();
                    UpdateDefaultCastBar();
                    break;
                case "HideDefaultCastbar":
                    UpdateDefaultCastBar();
                    break;
                case "HideDefaultPulltimer":
                    UpdateDefaultPulltimer();
                    break;
            }
        }

        private static DateTime _lastHudElementsErrorLog = DateTime.MinValue;
        private const double HudElementsErrorLogIntervalSeconds = 10.0;

        private void UpdateDefaultHudElementsHidden()
        {
            try
            {
                UpdateDefaultHudElementsHiddenCore();
            }
            catch (Exception ex)
            {
                var now = DateTime.UtcNow;
                if ((now - _lastHudElementsErrorLog).TotalSeconds >= HudElementsErrorLogIntervalSeconds)
                {
                    _lastHudElementsErrorLog = now;
                    Plugin.Logger.Warning($"[HSUI] HudHelper: skipped HUD hide update (resolver not ready): {ex.Message}");
                }
            }
        }

        private void UpdateDefaultHudElementsHiddenCore()
        {
            if (Plugin.Condition.Any(
                   ConditionFlag.OccupiedInEvent,
                   ConditionFlag.OccupiedInQuestEvent,
                   ConditionFlag.OccupiedInCutSceneEvent,
                   ConditionFlag.OccupiedSummoningBell,
                   ConditionFlag.Occupied,
                   ConditionFlag.Occupied30,
                   ConditionFlag.Occupied33,
                   ConditionFlag.Occupied38,
                   ConditionFlag.Occupied39,
                   ConditionFlag.WatchingCutscene,
                   ConditionFlag.WatchingCutscene78,
                   ConditionFlag.CreatingCharacter,
                   ConditionFlag.BetweenAreas,
                   ConditionFlag.BetweenAreas51,
                   ConditionFlag.BoundByDuty95,
                   ConditionFlag.ChocoboRacing,
                   ConditionFlag.PlayingLordOfVerminion)
                   || Plugin.ClientState.IsPvP)
            {
                return;
            }

            if (!Config.HideDefaultHudWhenReplaced)
            {
                SetGameHudElementsHidden(Array.Empty<uint>(), false);
                return;
            }

            var hashesToHide = new List<uint>();
            void AddElements(params ElementKind[] elements)
            {
                foreach (var e in elements)
                    hashesToHide.Add((uint)e);
            }

            // Use ElementKind hashes (HUD Layout config) — the proper way per DelvUI. Source: HUDManager ElementKind.cs
            var hotbarsConfig = ConfigurationManager.Instance?.GetConfigObject<HotbarsConfig>();
            if (hotbarsConfig?.Enabled == true && AnyHotbarEnabled())
            {
                AddElements(ElementKind.Hotbar1, ElementKind.Hotbar2, ElementKind.Hotbar3, ElementKind.Hotbar4, ElementKind.Hotbar5,
                    ElementKind.Hotbar6, ElementKind.Hotbar7, ElementKind.Hotbar8, ElementKind.Hotbar9, ElementKind.Hotbar10, ElementKind.CrossHotbar);
            }

            var playerUnitFrame = ConfigurationManager.Instance?.GetConfigObject<PlayerUnitFrameConfig>();
            if (playerUnitFrame?.Enabled == true)
                AddElements(ElementKind.ParameterBar);

            var targetUnitFrame = ConfigurationManager.Instance?.GetConfigObject<TargetUnitFrameConfig>();
            if (targetUnitFrame?.Enabled == true)
                AddElements(ElementKind.TargetBar, ElementKind.TargetInfoHp, ElementKind.TargetInfoProgressBar, ElementKind.TargetInfoStatus);

            var focusUnitFrame = ConfigurationManager.Instance?.GetConfigObject<FocusTargetUnitFrameConfig>();
            if (focusUnitFrame?.Enabled == true)
                AddElements(ElementKind.FocusTargetBar);

            var playerCastbar = ConfigurationManager.Instance?.GetConfigObject<PlayerCastbarConfig>();
            if (playerCastbar?.Enabled == true)
                AddElements(ElementKind.ProgressBar);

            var expBar = ConfigurationManager.Instance?.GetConfigObject<ExperienceBarConfig>();
            if (expBar?.Enabled == true)
                AddElements(ElementKind.ExperienceBar);

            var limitBreak = ConfigurationManager.Instance?.GetConfigObject<LimitBreakConfig>();
            if (limitBreak?.Enabled == true)
                AddElements(ElementKind.LimitGauge);

            var playerBuffs = ConfigurationManager.Instance?.GetConfigObject<PlayerBuffsListConfig>();
            var playerDebuffs = ConfigurationManager.Instance?.GetConfigObject<PlayerDebuffsListConfig>();
            if (playerBuffs?.Enabled == true || playerDebuffs?.Enabled == true)
                AddElements(ElementKind.StatusEffects, ElementKind.StatusInfoEnhancements, ElementKind.StatusInfoEnfeeblements, ElementKind.StatusInfoOther, ElementKind.StatusInfoConditionalEnhancements);

            var partyFrames = ConfigurationManager.Instance?.GetConfigObject<PartyFramesConfig>();
            if (partyFrames?.Enabled == true)
                AddElements(ElementKind.PartyList);

            var alliance1 = ConfigurationManager.Instance?.GetConfigObject<AllianceFrames1Config>();
            var alliance2 = ConfigurationManager.Instance?.GetConfigObject<AllianceFrames2Config>();
            if ((alliance1?.Enabled == true || alliance2?.Enabled == true))
                AddElements(ElementKind.AllianceList1, ElementKind.AllianceList2);

            var enemyList = ConfigurationManager.Instance?.GetConfigObject<EnemyListConfig>();
            if (enemyList?.Enabled == true)
                AddElements(ElementKind.EnemyList);

            // NamePlate is not in HUD Layout config; use UpdateDefaultNameplates (IsVisible) for that
            if (IsCurrentJobHudEnabled())
            {
                var player = Plugin.ObjectTable.LocalPlayer;
                if (player != null)
                {
                    foreach (var ek in ElementKindHelper.GetJobGaugeElementKinds(player.ClassJob.RowId))
                        hashesToHide.Add((uint)ek);
                }
            }

            SetGameHudElementsHidden(hashesToHide.ToArray(), false);
        }

        private static bool AnyHotbarEnabled()
        {
            var cfg = ConfigurationManager.Instance;
            if (cfg == null) return true;
            var bars = new HotbarBarConfig?[]
            {
                cfg.GetConfigObject<Hotbar1BarConfig>(),
                cfg.GetConfigObject<Hotbar2BarConfig>(),
                cfg.GetConfigObject<Hotbar3BarConfig>(),
                cfg.GetConfigObject<Hotbar4BarConfig>(),
                cfg.GetConfigObject<Hotbar5BarConfig>(),
                cfg.GetConfigObject<Hotbar6BarConfig>(),
                cfg.GetConfigObject<Hotbar7BarConfig>(),
                cfg.GetConfigObject<Hotbar8BarConfig>(),
                cfg.GetConfigObject<Hotbar9BarConfig>(),
                cfg.GetConfigObject<Hotbar10BarConfig>()
            };
            foreach (var bar in bars)
            {
                if (bar?.Enabled == true) return true;
            }
            return false;
        }

        /// <summary>Visibility (ByteValue2) of game HUD elements before we hid them. Restored on disable/unload.</summary>
        private readonly Dictionary<uint, byte> _gameHudVisibilityBeforeHide = new();

        private unsafe void SetGameHudElementsHidden(uint[] hashesToHide, bool forceRestore)
        {
            AddonConfig* config = AddonConfig.Instance();
            if (config == null || !config->IsLoaded || config->ActiveDataSet == null)
                return;

            var hashSet = new HashSet<uint>(hashesToHide);
            Span<AddonConfigEntry> entries = config->ActiveDataSet->HudLayoutConfigEntries;
            bool hasChanges = false;

            if (forceRestore || hashesToHide.Length == 0)
            {
                foreach (ref var entry in entries)
                {
                    if (!_gameHudVisibilityBeforeHide.TryGetValue(entry.AddonNameHash, out byte saved))
                        continue;
                    if (entry.ByteValue2 == saved)
                        continue;
                    entry.ByteValue2 = saved;
                    hasChanges = true;
                }
                _gameHudVisibilityBeforeHide.Clear();
            }
            else
            {
                foreach (ref var entry in entries)
                {
                    if (!hashSet.Contains(entry.AddonNameHash))
                        continue;
                    if (!_gameHudVisibilityBeforeHide.ContainsKey(entry.AddonNameHash))
                        _gameHudVisibilityBeforeHide[entry.AddonNameHash] = entry.ByteValue2;
                    if (entry.ByteValue2 == 0x0)
                        continue;
                    entry.ByteValue2 = 0x0;
                    hasChanges = true;
                }
            }

            if (hasChanges)
            {
                config->SaveFile(true);
                config->ApplyHudLayout();
            }
        }

        private unsafe void UpdateDefaultCastBar(bool forceVisible = false)
        {
            // Hide when explicit config says so, or when HSUI is replacing it (HideDefaultHudWhenReplaced + player castbar enabled)
            bool playerCastbarEnabled = ConfigurationManager.Instance?.GetConfigObject<PlayerCastbarConfig>()?.Enabled ?? false;
            bool shouldHide = Config.HideDefaultCastbar || (Config.HideDefaultHudWhenReplaced && playerCastbarEnabled);

            if (shouldHide && !_hidingCastBar)
            {
                Plugin.AddonLifecycle.RegisterListener(AddonEvent.PreDraw, "_CastBar", (addonEvent, args) =>
                {
                    AtkUnitBase* addon = (AtkUnitBase*)args.Addon.Address;

                    if (!_hidingCastBar)
                    {
                        _castBarPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
                    }

                    addon->RootNode->SetPositionFloat(-9999.0f, -9999.0f);
                });

                _hidingCastBar = true;
            }
            else if ((forceVisible || !shouldHide) && _hidingCastBar)
            {
                Plugin.AddonLifecycle.UnregisterListener(AddonEvent.PreDraw, "_CastBar");

                AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("_CastBar", 1).Address;
                if (addon != null)
                {
                    addon->RootNode->SetPositionFloat(_castBarPos.X, _castBarPos.Y);
                }

                _hidingCastBar = false;
            }

            return;
        }

        private unsafe void UpdateDefaultPulltimer(bool forceVisible = false)
        {
            if (Config.HideDefaultPulltimer && !_hidingPullTimer)
            {
                Plugin.AddonLifecycle.RegisterListener(AddonEvent.PreDraw, "ScreenInfo_CountDown", (addonEvent, args) =>
                {
                    AtkUnitBase* addon = (AtkUnitBase*)args.Addon.Address;

                    if (!_hidingPullTimer)
                    {
                        _pullTimerPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
                    }

                    addon->RootNode->SetPositionFloat(-9999.0f, -9999.0f);
                });

                AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("ScreenInfo_CountDown", 1).Address;
                if (addon != null)
                {
                    _pullTimerPos = new Vector2(addon->RootNode->GetXFloat(), addon->RootNode->GetYFloat());
                    addon->RootNode->SetPositionFloat(-9999, -9999);
                }

                _hidingPullTimer = true;
            }
            else if ((forceVisible || !Config.HideDefaultPulltimer) && _hidingPullTimer)
            {
                Plugin.AddonLifecycle.UnregisterListener(AddonEvent.PreDraw, "ScreenInfo_CountDown");

                AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("ScreenInfo_CountDown", 1).Address;
                if (addon != null)
                {
                    addon->RootNode->SetPositionFloat(_pullTimerPos.X, _pullTimerPos.Y);
                }

                _hidingPullTimer = false;
            }

            return;
        }

        private static readonly Dictionary<uint, Type> _jobIdToConfigType = new()
        {
            [JobIDs.PLD] = typeof(PaladinConfig), [JobIDs.WAR] = typeof(WarriorConfig),
            [JobIDs.DRK] = typeof(DarkKnightConfig), [JobIDs.GNB] = typeof(GunbreakerConfig),
            [JobIDs.WHM] = typeof(WhiteMageConfig), [JobIDs.SCH] = typeof(ScholarConfig),
            [JobIDs.AST] = typeof(AstrologianConfig), [JobIDs.SGE] = typeof(SageConfig),
            [JobIDs.MNK] = typeof(MonkConfig), [JobIDs.DRG] = typeof(DragoonConfig),
            [JobIDs.NIN] = typeof(NinjaConfig), [JobIDs.SAM] = typeof(SamuraiConfig),
            [JobIDs.RPR] = typeof(ReaperConfig), [JobIDs.VPR] = typeof(ViperConfig),
            [JobIDs.BRD] = typeof(BardConfig), [JobIDs.MCH] = typeof(MachinistConfig),
            [JobIDs.DNC] = typeof(DancerConfig), [JobIDs.BLM] = typeof(BlackMageConfig),
            [JobIDs.SMN] = typeof(SummonerConfig), [JobIDs.RDM] = typeof(RedMageConfig),
            [JobIDs.BLU] = typeof(BlueMageConfig), [JobIDs.PCT] = typeof(PictomancerConfig),
        };

        private static bool IsCurrentJobHudEnabled()
        {
            var player = Plugin.ObjectTable.LocalPlayer;
            if (player == null) return false;
            if (!_jobIdToConfigType.TryGetValue(player.ClassJob.RowId, out var configType))
                return false;
            var config = ConfigurationManager.Instance?.GetConfigObjectForType(configType) as JobConfig;
            return config?.Enabled ?? false;
        }

        private static IEnumerable<string> GetCurrentJobGaugeAddonNames()
        {
            var player = Plugin.ObjectTable.LocalPlayer;
            if (player == null) yield break;
            if (!JobsHelper.JobNames.TryGetValue(player.ClassJob.RowId, out var jobName))
                yield break;
            for (int i = 0; i < 4; i++)
            {
                string addonName = $"JobHud{jobName}{i}";
                if (_specialCases.TryGetValue(addonName, out var name) && name != null)
                    addonName = name;
                yield return addonName;
            }
        }

        private bool _hidingNameplates = false;
        private bool _nameplateVisibilityBeforeHide = true;

        private unsafe void UpdateDefaultNameplates(bool forceVisible = false)
        {
            var nameplatesConfig = ConfigurationManager.Instance?.GetConfigObject<NameplatesGeneralConfig>();
            bool shouldHide = !forceVisible && Config.HideDefaultHudWhenReplaced && (nameplatesConfig?.Enabled ?? false);

            var addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName("NamePlate", 1).Address;
            if (addon == null) return;

            if (shouldHide)
            {
                if (!_hidingNameplates)
                {
                    _nameplateVisibilityBeforeHide = addon->IsVisible;
                    _hidingNameplates = true;
                }
                addon->IsVisible = false;
            }
            else
            {
                if (_hidingNameplates)
                {
                    addon->IsVisible = _nameplateVisibilityBeforeHide;
                    _hidingNameplates = false;
                }
            }
        }

        private static Dictionary<string, string> _specialCases = new()
        {
            ["JobHudPCT0"] = "JobHudRPM0",
            ["JobHudPCT1"] = "JobHudRPM1",

            ["JobHudNIN1"] = "JobHudNIN1v70",

            ["JobHudVPR0"] = "JobHudRDB0",
            ["JobHudVPR1"] = "JobHudRDB1"
        };

        private unsafe void UpdateJobGauges(bool forceVisible = false)
        {
            IPlayerCharacter? player = Plugin.ObjectTable.LocalPlayer;
            if (player == null) { return; }

            string jobName = JobsHelper.JobNames[player.ClassJob.RowId];
            int i = 0;
            bool stop = false;

            do
            {
                string addonName = $"JobHud{jobName}{i}";
                if (_specialCases.TryGetValue(addonName, out string? name) && name != null)
                {
                    addonName = name;
                }

                AtkUnitBase* addon = (AtkUnitBase*)Plugin.GameGui.GetAddonByName(addonName, 1).Address;
                if (addon == null)
                {
                    stop = true;
                }
                else
                {
                    addon->IsVisible = forceVisible || !Config.HideDefaultJobGauges;
                }

                i++;
            } while (!stop);
        }
    }

    internal class StrataLevelComparer<TKey> : IComparer<TKey> where TKey : PluginConfigObject
    {
        public int Compare(TKey? a, TKey? b)
        {
            MovablePluginConfigObject? configA = a is MovablePluginConfigObject ? a as MovablePluginConfigObject : null;
            MovablePluginConfigObject? configB = b is MovablePluginConfigObject ? b as MovablePluginConfigObject : null;

            if (configA == null && configB == null) { return 0; }
            if (configA == null && configB != null) { return -1; }
            if (configA != null && configB == null) { return 1; }

            if (configA!.StrataLevel == configB!.StrataLevel)
            {
                return configA.ID.CompareTo(configB.ID);
            }

            if (configA.StrataLevel < configB.StrataLevel)
            {
                return -1;
            }

            return 1;
        }
    }
}