using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Utility;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.Bars;
using HSUI.Interface.EnemyList;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using System;
using System.Collections.Generic;
using System.Numerics;
using LuminaAction = Lumina.Excel.Sheets.Action;
using StructsBattleChara = FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara;

namespace HSUI.Interface.GeneralElements
{
    public class CastbarHud : ParentAnchoredDraggableHudElement, IHudElementWithActor, IHudElementWithAnchorableParent, IHudElementWithPreview
    {
        private CastbarConfig Config => (CastbarConfig)_config;
        private readonly LabelHud _castNameLabel;
        private readonly LabelHud _castTimeLabel;

        protected LastUsedCast? LastUsedCast;

        public IGameObject? Actor { get; set; }

        protected override bool AnchorToParent => Config is UnitFrameCastbarConfig { AnchorToUnitFrame: true };
        protected override DrawAnchor ParentAnchor => Config is UnitFrameCastbarConfig config ? config.UnitFrameAnchor : DrawAnchor.Center;

        public CastbarHud(CastbarConfig config, string? displayName = null) : base(config, displayName)
        {
            _castNameLabel = new LabelHud(config.CastNameLabel);
            _castTimeLabel = new LabelHud(config.CastTimeLabel);
        }

        public void StopPreview()
        {
            Config.Preview = false;
        }

        protected override (List<Vector2>, List<Vector2>) ChildrenPositionsAndSizes() => (new List<Vector2> { Config.Position }, new List<Vector2> { Config.Size });

        public override void DrawChildren(Vector2 origin)
        {
            if (!Config.Enabled)
            {
                return;
            }

            if (!Config.Preview &&
                (Actor == null || Actor is not ICharacter || Actor.ObjectKind != ObjectKind.Player && Actor.ObjectKind != ObjectKind.BattleNpc))
            {
                return;
            }

            UpdateCurrentCast(out float currentCastTime, out float totalCastTime);
            if (totalCastTime == 0 || currentCastTime >= totalCastTime)
            {
                return;
            }

            if (!ShouldShow() && !Config.Preview)
            {
                return;
            }

            Vector2 size = GetSize();
            IDalamudTextureWrap? iconTexture = LastUsedCast?.GetIconTexture();
            bool validIcon = Config.Preview ? true : iconTexture is not null;
            Vector2 iconSize = Config.ShowIcon && validIcon && !Config.SeparateIcon ? new Vector2(size.Y, size.Y) : Vector2.Zero;

            PluginConfigColor fillColor = GetColor();
            Rect background = new(Config.Position, size, Config.BackgroundColor);
            Rect progress = BarUtilities.GetFillRect(Config.Position, size, Config.FillDirection, fillColor, currentCastTime, totalCastTime);

            BarHud bar = new(Config, Actor);/**/
            bar.SetBackground(background);

            if (Config.UseReverseFill)
            {
                Vector2 reverseFillSize = size - BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);
                Vector2 reverseFillPos = Config.FillDirection.IsInverted()
                    ? Config.Position
                    : Config.Position + BarUtilities.GetFillDirectionOffset(progress.Size, Config.FillDirection);

                PluginConfigColor reverseFillColor = Config.ReverseFillColor;
                bar.AddForegrounds(new Rect(reverseFillPos, reverseFillSize, reverseFillColor));
            }

            AddExtras(bar, totalCastTime, iconTexture);

            bar.AddForegrounds(progress);

            Vector2 pos = origin + ParentPos();
            AddDrawActions(bar.GetDrawActions(pos, Config.StrataLevel));

            // icon
            Vector2 startPos = Config.Position + Utils.GetAnchoredPosition(pos, size, Config.Anchor);
            if (Config.ShowIcon && validIcon)
            {
                Vector2 finalIconPos = Config.SeparateIcon ? startPos + Config.CustomIconPosition : startPos;
                Vector2 finalIconSize = Config.SeparateIcon ? Config.CustomIconSize : iconSize;

                AddDrawAction(Config.StrataLevel, () =>
                {
                    DrawHelper.DrawInWindow(ID + "_icon", finalIconPos, finalIconSize, false, (drawList) =>
                    {
                        ImGui.SetCursorPos(finalIconPos);

                        IDalamudTextureWrap? texture = Config.Preview ? TexturesHelper.GetTexture<LuminaAction>(3577) : iconTexture;
                        if (texture != null)
                        {
                            ImGui.Image(texture.Handle, finalIconSize);
                        }

                        if (Config.DrawBorder)
                        {
                            drawList.AddRect(finalIconPos, finalIconPos + finalIconSize, Config.BorderColor.Base, 0, ImDrawFlags.None, Config.BorderThickness);
                        }
                    });
                });
            }

            // cast time
            bool isTimeLeftAnchored = Config.CastTimeLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
            Vector2 timePos = Config.ShowIcon && isTimeLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;
            float value = Config.Preview ? 0.5f : totalCastTime - currentCastTime;

            if (Config.ShowMaxCastTime)
            {
                string format = Config.CastTimeLabel.NumberFormat.ToString();
                Config.CastTimeLabel.SetText(
                    value.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo) +
                    " / " +
                    totalCastTime.ToString("N" + format, ConfigurationManager.Instance.ActiveCultreInfo)
                );
            }
            else
            {
                Config.CastTimeLabel.SetValue(value);
            }

            AddDrawAction(Config.CastTimeLabel.StrataLevel, () =>
            {
                _castTimeLabel.Draw(timePos, size, Actor);
            });

            // cast name
            bool isNameLeftAnchored = Config.CastNameLabel.TextAnchor is DrawAnchor.Left or DrawAnchor.TopLeft or DrawAnchor.BottomLeft;
            Vector2 namePos = Config.ShowIcon && isNameLeftAnchored ? startPos + new Vector2(iconSize.X, 0) : startPos;

            string original = CustomCastName() ?? (LastUsedCast?.ActionText ?? "");
            string castName = EncryptedStringsHelper.GetString(original).CheckForUpperCase() ?? original;

            if (Config.TruncateCastName)
            {
                castName = TruncatedCastName(castName) ?? castName;
            }

            Config.CastNameLabel.SetText(Config.Preview ? "Cast Name" : castName);

            AddDrawAction(Config.CastNameLabel.StrataLevel, () =>
            {
                _castNameLabel.Draw(namePos, size, Actor);
            });
        }

        private unsafe void UpdateCurrentCast(out float currentCastTime, out float totalCastTime)
        {
            if (Config.Preview || Actor is not IBattleChara battleChara)
            {
                currentCastTime = Config.Preview ? 0.5f : 0f;
                totalCastTime = 1f;
                return;
            }

            float current = 0;
            float total = 0;

            try
            {
                current = battleChara.CurrentCastTime;
                StructsBattleChara* chara = (StructsBattleChara*)battleChara.Address;
                CastInfo* castInfo = chara->GetCastInfo();

                if (castInfo != null)
                {
                    total = castInfo->TotalCastTime;
                }
            }
            catch
            {
                currentCastTime = 0;
                totalCastTime = 0;
                return;
            }

            if (!Utils.IsActorCasting(battleChara) && current <= 0)
            {
                currentCastTime = 0;
                totalCastTime = 0;
                return;
            }

            currentCastTime = current;
            totalCastTime = total;

            uint currentCastId = battleChara.CastActionId;
            ActionType currentCastType = (ActionType)battleChara.CastActionType;

            if (LastUsedCast == null || LastUsedCast.CastId != currentCastId || LastUsedCast.ActionType != currentCastType)
            {
                LastUsedCast = new LastUsedCast(currentCastId, currentCastType, battleChara.IsCastInterruptible);
            }
        }

        private string? TruncatedCastName(string text)
        {
            if (text.Length <= 5)
            {
                return null;
            }

            LabelConfig castNamelabel = Config.CastNameLabel;
            LabelConfig castTimeLabel = Config.CastTimeLabel;


            Vector2 size;

            using (FontsManager.Instance.PushFont(castNamelabel.FontID))
            {
                size = ImGui.CalcTextSize(text) * castNamelabel.GetFontScale();
            }

            float maxWidth = Config.Size.X;

            if (!Config.SeparateIcon)
            {
                maxWidth -= Config.Size.Y;
            }

            if (Config.CastTimeLabel.Enabled)
            {
                using (FontsManager.Instance.PushFont(Config.CastTimeLabel.FontID))
                {
                    maxWidth -= (ImGui.CalcTextSize("XX.X") * castTimeLabel.GetFontScale()).X;
                }
            }

            if (size.X > maxWidth)
            {
                return TruncatedCastName(text.Substring(0, text.Length - 5) + "...");
            }

            return text;
        }

        public virtual void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
        {
            // override
        }

        public virtual string? CustomCastName()
        {
            // override
            return null;
        }

        public virtual PluginConfigColor GetColor() => Config.FillColor;
        public virtual Vector2 GetSize() => Config.Size;

        public virtual bool ShouldShow() => true;
    }

    public class PlayerCastbarHud : CastbarHud
    {
        private PlayerCastbarConfig Config => (PlayerCastbarConfig)_config;

        public PlayerCastbarHud(PlayerCastbarConfig config, string displayName) : base(config, displayName)
        {

        }

        public override unsafe string? CustomCastName()
        {
            AddonCastBar* castBar = (AddonCastBar*)Plugin.GameGui.GetAddonByName("_CastBar", 1).Address;
            if (castBar == null) { return null; }

            AtkTextNode* node = castBar->GetTextNodeById(4);
            if (node == null) { return null; }

            return node->GetText().ExtractText();
        }

        public override void AddExtras(BarHud bar, float totalCastTime, IDalamudTextureWrap? iconTexture)
        {
            if (!Config.ShowSlideCast || Config.SlideCastTime <= 0 || Config.Preview)
            {
                return;
            }

            Rect slideCast;

            if (Config.FillDirection.IsHorizontal())
            {
                float slideCastWidth = Math.Min(Config.Size.X, Config.SlideCastTime / 1000f * Config.Size.X / totalCastTime);
                Vector2 size = new(slideCastWidth, Config.Size.Y);
                slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);

                if (Config.FillDirection is BarDirection.Left)
                {
                    bool validIcon = iconTexture is not null;
                    Vector2 iconSize = Config.ShowIcon && validIcon ? new Vector2(Config.Size.Y, Config.Size.Y) : Vector2.Zero;
                    slideCast = Config.ShowIcon ? new Rect(Config.Position, size + new Vector2(iconSize.X, 0), Config.SlideCastColor) : new Rect(Config.Position, size, Config.SlideCastColor);
                }
            }
            else
            {
                float slideCastHeight = Math.Min(Config.Size.Y, Config.SlideCastTime / 1000f * Config.Size.Y / totalCastTime);
                Vector2 size = new(Config.Size.X, slideCastHeight);
                slideCast = new(Config.Position + Config.Size - size, size, Config.SlideCastColor);

                if (Config.FillDirection is BarDirection.Up)
                {
                    slideCast = new(Config.Position, size, Config.SlideCastColor);
                }
            }

            bar.AddForegrounds(slideCast);
        }

        public override PluginConfigColor GetColor()
        {
            if (!Config.UseJobColor || Actor is not ICharacter)
            {
                return Config.FillColor;
            }

            ICharacter? chara = (ICharacter)Actor;
            PluginConfigColor? color = GlobalColors.Instance.ColorForJobId(chara.ClassJob.RowId);
            return color ?? Config.FillColor;
        }
    }

    public class TargetCastbarHud : CastbarHud
    {
        private TargetCastbarConfig Config => (TargetCastbarConfig)_config;

        public TargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
        {

        }

        public override PluginConfigColor GetColor()
        {
            if (Config.ShowInterruptableColor && LastUsedCast?.Interruptible == true)
            {
                return Config.InterruptableColor;
            }

            if (!Config.UseColorForDamageTypes)
            {
                return Config.FillColor;
            }

            if (LastUsedCast != null)
            {
                switch (LastUsedCast.DamageType)
                {
                    case DamageType.Physical:
                    case DamageType.Blunt:
                    case DamageType.Slashing:
                    case DamageType.Piercing:
                        return Config.PhysicalDamageColor;

                    case DamageType.Magic:
                        return Config.MagicalDamageColor;

                    case DamageType.Darkness:
                        return Config.DarknessDamageColor;
                }
            }

            return Config.FillColor;
        }

        public override unsafe bool ShouldShow()
        {
            bool? targetCasting = Utils.IsTargetCasting();
            if (targetCasting.HasValue)
            {
                return targetCasting.Value;
            }

            return true;
        }
    }

    public class FocusTargetCastbarHud : TargetCastbarHud
    {
        private TargetCastbarConfig Config => (TargetCastbarConfig)_config;

        public FocusTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
        {

        }

        public override unsafe bool ShouldShow()
        {
            bool? focusTargetCasting = Utils.IsFocusTargetCasting();
            if (focusTargetCasting.HasValue)
            {
                return focusTargetCasting.Value;
            }

            return true;
        }
    }

    public class TargetOfTargetCastbarHud : TargetCastbarHud
    {
        private TargetCastbarConfig Config => (TargetCastbarConfig)_config;

        public TargetOfTargetCastbarHud(TargetCastbarConfig config, string? displayName = null) : base(config, displayName)
        {

        }

        public override unsafe bool ShouldShow()
        {
            IGameObject? target = Plugin.TargetManager.SoftTarget ?? Plugin.TargetManager.Target;
            if (Actor == target)
            {
                bool? targetCasting = Utils.IsTargetCasting();
                if (targetCasting.HasValue)
                {
                    return targetCasting.Value;
                }
            }

            IGameObject? focusTarget = Plugin.TargetManager.FocusTarget;
            if (Actor == focusTarget)
            {
                bool? focusTargetCasting = Utils.IsFocusTargetCasting();
                if (focusTargetCasting.HasValue)
                {
                    return focusTargetCasting.Value;
                }
            }

            return true;
        }
    }

    public class EnemyListCastbarHud : TargetCastbarHud
    {
        private EnemyListCastbarConfig Config => (EnemyListCastbarConfig)_config;

        public int EnemyListIndex = 0;

        public EnemyListCastbarHud(EnemyListCastbarConfig config, string? displayName = null) : base(config, displayName)
        {

        }

        public override unsafe bool ShouldShow()
        {
            bool? casting = Utils.IsEnemyInListCasting(EnemyListIndex);
            if (casting.HasValue)
            {
                return casting.Value;
            }

            return true;
        }
    }

    public class NameplateCastbarHud : TargetOfTargetCastbarHud
    {
        private NameplateCastbarConfig Config => (NameplateCastbarConfig)_config;

        private Vector2 _customSize = new Vector2(0);
        public Vector2 ParentSize { get; set; } = new Vector2(0);

        public NameplateCastbarHud(NameplateCastbarConfig config, string? displayName = null) : base(config, displayName)
        {
            _customSize = Config.Size;
        }

        public override void DrawChildren(Vector2 origin)
        {
            // calculate size
            float x = Config.MatchWidth ? ParentSize.X : Config.Size.X;
            float y = Config.MatchHeight ? ParentSize.Y : Config.Size.Y;
            _customSize = new Vector2(x, y);

            // draw
            base.DrawChildren(origin);
        }

        public override Vector2 GetSize() => _customSize;
    }
}
