using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Statuses;
using HSUI.Config;
using HSUI.Enums;
using HSUI.Helpers;
using HSUI.Interface.GeneralElements;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace HSUI.Interface.Bars
{
    public class BarUtilities
    {
        public static BarHud GetProgressBar(ProgressBarConfig config, float current, float max, float min = 0f, IGameObject? actor = null, PluginConfigColor? fillColor = null, BarGlowConfig? barGlowConfig = null)
        {
            return GetProgressBar(config, config.ThresholdConfig, new LabelConfig[] { config.Label }, current, max, min, actor, fillColor, barGlowConfig);
        }

        public static BarHud GetProgressBar(
            BarConfig config,
            ThresholdConfig? thresholdConfig,
            LabelConfig[]? labelConfigs,
            float current,
            float max,
            float min = 0f,
            IGameObject? actor = null,
            PluginConfigColor? fillColor = null,
            BarGlowConfig? glowConfig = null,
            PluginConfigColor? backgroundColor = null
        )
        {
            BarHud bar = new(config, actor, glowConfig, current, max);

            PluginConfigColor color = fillColor ?? config.FillColor;
            if (thresholdConfig != null)
            {
                color = thresholdConfig.ChangeColor && thresholdConfig.IsActive(current) ? thresholdConfig.Color : color;
            }

            Rect foreground = GetFillRect(config.Position, config.Size, config.FillDirection, color, current, max, min);
            bar.AddForegrounds(foreground);
            bar.AddLabels(labelConfigs);

            if (backgroundColor != null)
            {
                Rect bg = new Rect(config.Position, config.Size, backgroundColor);
                bar.SetBackground(bg);
            }

            AddThresholdMarker(bar, config, thresholdConfig, max, min);

            return bar;
        }

        public static BarHud? GetProcBar(
            ProgressBarConfig config,
            IPlayerCharacter player,
            uint statusId,
            float maxDuration,
            bool trackDuration = true)
        {
            return GetProcBar(config, player, new List<uint> { statusId }, new List<float> { maxDuration }, trackDuration);
        }

        public static BarHud? GetProcBar(
            ProgressBarConfig config,
            IPlayerCharacter player,
            List<uint> statusIDs,
            List<float> maxDurations,
            bool trackDuration = true)
        {
            if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }

            IStatus? status = Utils.StatusListForBattleChara(player).FirstOrDefault(o => statusIDs.Contains(o.StatusId));
            if (status == null && config.HideWhenInactive)
            {
                return null;
            }

            float duration = Math.Abs(status?.RemainingTime ?? 0);

            if (trackDuration)
            {
                int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
                config.Label.SetValue(duration);
                return GetProgressBar(config, duration, maxDurations[index], 0, player);
            }

            config.Label.SetText("");
            return GetBar(config, duration <= 0 ? 0 : 1, 1, 0);
        }

        public static BarHud? GetDoTBar(
            ProgressBarConfig config,
            IPlayerCharacter player,
            IGameObject? target,
            uint statusId,
            float maxDuration)
        {
            return GetDoTBar(config, player, target, new List<uint> { statusId }, new List<float> { maxDuration });
        }

        public static BarHud? GetDoTBar(
            ProgressBarConfig config,
            IPlayerCharacter player,
            IGameObject? target,
            List<uint> statusIDs,
            List<float> maxDurations)
        {
            if (statusIDs.Count == 0 || maxDurations.Count == 0) { return null; }

            IStatus? status = null;

            if (target != null && target is IBattleChara targetChara)
            {
                status = Utils.StatusListForBattleChara(targetChara).FirstOrDefault(o => o.SourceId == player.GameObjectId && statusIDs.Contains(o.StatusId));
            }

            if (status == null && config.HideWhenInactive)
            {
                return null;
            }

            int index = status != null ? statusIDs.IndexOf(status.StatusId) : 0;
            float duration = Math.Abs(status?.RemainingTime ?? 0);
            float maxDuration = maxDurations[index];

            config.Label.SetValue(duration);
            return GetProgressBar(config, duration, maxDuration, 0, player);
        }

        private static void AddThresholdMarker(BarHud bar, BarConfig config, ThresholdConfig? thresholdConfig, float max, float min)
        {
            if (thresholdConfig == null || !thresholdConfig.Enabled || !thresholdConfig.ShowMarker)
            {
                return;
            }

            float thresholdPercent = Math.Clamp(thresholdConfig.Value / (max - min), 0f, 1f);
            Vector2 offset = GetFillDirectionOffset(
                new Vector2(config.Size.X * thresholdPercent, config.Size.Y * thresholdPercent),
                config.FillDirection
            );

            Vector2 markerSize = config.FillDirection.IsHorizontal() ?
                new Vector2(thresholdConfig.MarkerSize, config.Size.Y) :
                new Vector2(config.Size.X, thresholdConfig.MarkerSize);

            Vector2 markerPos = config.FillDirection.IsInverted() ?
                config.Position + GetFillDirectionOffset(config.Size, config.FillDirection) - offset :
                config.Position + offset;

            Vector2 anchoredPos = Utils.GetAnchoredPosition(markerPos, markerSize, config.FillDirection.IsHorizontal() ? DrawAnchor.Top : DrawAnchor.Left);
            Rect marker = new(anchoredPos, markerSize, thresholdConfig.MarkerColor);
            bar.AddForegrounds(marker);
        }

        // Tuple is <foregroundColor, percent fill, labels>
        public static BarHud[] GetChunkedBars(
            ChunkedBarConfig config,
            Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
            IGameObject? actor,
            BarGlowConfig glowConfig)
        {
            List<bool> chunksToGlowList = new();
            for (int i = 0; i < chunks.Length; i++)
            {
                chunksToGlowList.Add(chunks[i].Item2 >= 1f);
            }

            return GetChunkedBars(config, chunks, actor, glowConfig, chunksToGlowList.ToArray());
        }

        public static BarHud[] GetChunkedBars(
            ChunkedBarConfig config,
            Tuple<PluginConfigColor, float, LabelConfig?>[] chunks,
            IGameObject? actor,
            BarGlowConfig? glowConfig = null,
            bool[]? chunksToGlow = null)
        {
            BarHud[] bars = new BarHud[chunks.Length];
            Vector2 pos = Utils.GetAnchoredPosition(config.Position, config.Size, config.Anchor);

            for (int i = 0; i < chunks.Length; i++)
            {
                Vector2 chunkPos, chunkSize;
                if (config.FillDirection.IsHorizontal())
                {
                    chunkSize = new Vector2((config.Size.X - config.Padding * (chunks.Length - 1)) / chunks.Length, config.Size.Y);
                    chunkPos = pos + new Vector2((chunkSize.X + config.Padding) * i, 0);
                }
                else
                {
                    chunkSize = new Vector2(config.Size.X, (config.Size.Y - config.Padding * (chunks.Length - 1)) / chunks.Length);
                    chunkPos = pos + new Vector2(0, (chunkSize.Y + config.Padding) * i);
                }

                Rect background = new(chunkPos, chunkSize, config.BackgroundColor);
                Rect foreground = GetFillRect(chunkPos, chunkSize, config.FillDirection, chunks[i].Item1, chunks[i].Item2, 1f, 0f);
                BarGlowConfig? glow = (glowConfig?.Enabled == true && chunksToGlow?[i] == true) ? glowConfig : null;

                bars[i] = new BarHud(config.ID + i,
                    config.DrawBorder,
                    config.BorderColor,
                    config.BorderThickness,
                    actor: actor,
                    glowColor: glow?.Color,
                    glowSize: glow?.Size,
                    barTextureName: config.BarTextureName,
                    barTextureDrawMode: config.BarTextureDrawMode,
                    shadowConfig: config.ShadowConfig
                );
                bars[i].SetBackground(background);
                bars[i].AddForegrounds(foreground);

                LabelConfig? label = chunks[i].Item3;
                if (label is not null)
                {
                    bars[i].AddLabels(label);
                }
            }

            return bars;
        }

        public static BarHud[] GetChunkedBars(
            ChunkedBarConfig config,
            int chunks,
            float current,
            float max,
            float min = 0f,
            IGameObject? actor = null,
            LabelConfig?[]? labels = null,
            PluginConfigColor? fillColor = null,
            PluginConfigColor? partialFillColor = null,
            BarGlowConfig? glowConfig = null,
            bool[]? chunksToGlow = null)
        {
            float chunkRange = (max - min) / chunks;

            var barChunks = new Tuple<PluginConfigColor, float, LabelConfig?>[chunks];
            for (int i = 0; i < chunks; i++)
            {
                int barIndex = config.FillDirection.IsInverted() ? chunks - i - 1 : i;
                float chunkMin = min + chunkRange * i;
                float chunkMax = min + chunkRange * (i + 1);
                float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);

                PluginConfigColor chunkColor = partialFillColor != null && current < chunkMax ? partialFillColor : fillColor ?? config.FillColor;
                barChunks[barIndex] = new Tuple<PluginConfigColor, float, LabelConfig?>(chunkColor, chunkPercent, labels?[i]);
            }

            if (glowConfig != null && chunksToGlow == null)
            {
                return GetChunkedBars(config, barChunks, actor, glowConfig);
            }

            return GetChunkedBars(config, barChunks, actor, glowConfig, chunksToGlow);
        }

        public static BarHud[] GetChunkedProgressBars(
            ChunkedProgressBarConfig config,
            int chunks,
            float current,
            float max,
            float min = 0f,
            IGameObject? actor = null,
            BarGlowConfig? glowConfig = null,
            PluginConfigColor? fillColor = null,
            int thresholdChunk = 1,
            bool[]? chunksToGlow = null,
            int forceLabelIndex = -1)
        {
            var color = fillColor ?? config.FillColor;

            if (config.UseChunks)
            {
                NumericLabelConfig?[] labels = new NumericLabelConfig?[chunks];
                for (int i = 0; i < chunks; i++)
                {
                    float chunkRange = (max - min) / chunks;
                    float chunkMin = min + chunkRange * i;
                    float chunkMax = min + chunkRange * (i + 1);
                    float chunkPercent = Math.Clamp((current - chunkMin) / (chunkMax - chunkMin), 0f, 1f);

                    NumericLabelConfig? label = config.Label;
                    if (forceLabelIndex == -1)
                    {
                        switch (config.LabelMode)
                        {
                            case LabelMode.AllChunks:
                                label = config.Label.Clone(i);
                                label.SetValue(Math.Clamp(current - chunkMin, 0, chunkRange));
                                break;
                            case LabelMode.ActiveChunk:
                                label = chunkPercent < 1f && chunkPercent > 0f ? config.Label.Clone(i) : null;
                                break;
                        };
                    }
                    else
                    {
                        label = forceLabelIndex == i ? config.Label : null;
                    }

                    labels[i] = label;
                }

                var partialColor = config.UsePartialFillColor ? config.PartialFillColor : null;
                return GetChunkedBars(config, chunks, current, max, min, actor, labels, color, partialColor, glowConfig, chunksToGlow);
            }

            var threshold = GetThresholdConfigForChunk(config, thresholdChunk, chunks, min, max);
            BarHud bar = GetProgressBar(config, threshold, new LabelConfig[] { config.Label }, current, max, min, actor, color, glowConfig);
            return new BarHud[] { bar };
        }

        public static Rect[] GetShieldForeground(
            ShieldConfig shieldConfig,
            Vector2 pos,
            Vector2 size,
            Vector2 healthFillSize,
            BarDirection fillDirection,
            float shieldPercent,
            float currentHp,
            float maxHp,
            PluginConfigColor? color = null)
        {
            float shieldValue = shieldPercent * maxHp;
            float overshield = shieldConfig.FillHealthFirst ? Math.Max(shieldValue + currentHp - maxHp, 0f) : shieldValue;
            float shieldSize = shieldConfig.Height;
            PluginConfigColor c = color ?? shieldConfig.Color;

            if (!shieldConfig.HeightInPixels)
            {
                shieldSize = (fillDirection.IsHorizontal() ? size.Y : size.X) * shieldConfig.Height / 100f;
            }

            var overshieldSize = fillDirection.IsHorizontal()
                ? new Vector2(size.X, Math.Min(shieldSize, size.Y))
                : new Vector2(Math.Min(shieldSize, size.X), size.Y);

            Rect overshieldFill = GetFillRect(pos, overshieldSize, fillDirection, c, overshield, maxHp);

            if (shieldConfig.FillHealthFirst && currentHp < maxHp)
            {
                var shieldPos = fillDirection.IsInverted() ? pos : pos + GetFillDirectionOffset(healthFillSize, fillDirection);
                var shieldFillSize = size - GetFillDirectionOffset(healthFillSize, fillDirection);
                var healthFillShieldSize = fillDirection.IsHorizontal()
                    ? new Vector2(shieldFillSize.X, Math.Min(shieldSize, size.Y))
                    : new Vector2(Math.Min(shieldSize, size.X), shieldFillSize.Y);

                Rect shieldFill = GetFillRect(shieldPos, healthFillShieldSize, fillDirection, c, shieldValue - overshield, maxHp - currentHp, 0f);
                return new[] { overshieldFill, shieldFill };
            }

            return new[] { overshieldFill };
        }

        public static BarHud GetBar(
            BarConfig Config,
            float current,
            float max,
            float min = 0f,
            IGameObject? actor = null,
            PluginConfigColor? fillColor = null,
            BarGlowConfig? glowConfig = null,
            LabelConfig[]? labels = null)
        {
            Rect foreground = GetFillRect(Config.Position, Config.Size, Config.FillDirection, fillColor ?? Config.FillColor, current, max, min);

            BarHud bar = new BarHud(Config, actor, glowConfig);
            bar.AddForegrounds(foreground);
            bar.AddLabels(labels);

            return bar;
        }

        /// <summary>
        /// Gets the horizonal or vertical offset depending on the fill direction.
        /// </summary>
        public static Vector2 GetFillDirectionOffset(Vector2 size, BarDirection fillDirection)
        {
            return fillDirection.IsHorizontal() ? new(size.X, 0) : new(0, size.Y);
        }

        public static Rect GetFillRect(Vector2 pos, Vector2 size, BarDirection fillDirection, PluginConfigColor color, float current, float max, float min = 0f)
        {
            float fillPercent = max == 0 ? 1f : Math.Clamp((current - min) / (max - min), 0f, 1f);

            Vector2 fillPos = Vector2.Zero;
            Vector2 fillSize = fillDirection.IsHorizontal() ? new(size.X * fillPercent, size.Y) : new(size.X, size.Y * fillPercent);
            if (fillDirection == BarDirection.Left)
            {
                fillPos = Utils.GetAnchoredPosition(new(size.X, 0), fillSize, DrawAnchor.TopRight);
            }
            else if (fillDirection == BarDirection.Up)
            {
                fillPos = Utils.GetAnchoredPosition(new(0, size.Y), fillSize, DrawAnchor.BottomLeft);
            }

            return new Rect(pos + fillPos, fillSize, color);
        }

        public static ThresholdConfig GetThresholdConfigForChunk(ChunkedProgressBarConfig config, int chunk, int chunks, float min, float max) =>
            new ThresholdConfig
            {
                ThresholdType = ThresholdType.Below,
                Color = config.PartialFillColor,
                Enabled = config.UsePartialFillColor,
                Value = (max - min) / chunks * chunk,
                ChangeColor = true,
                ShowMarker = false
            };

        public static void AddShield(BarHud bar, BarConfig config, ShieldConfig shieldConfig, ICharacter character, Vector2 fillSize, PluginConfigColor? color = null)
        {
            if (shieldConfig.Enabled)
            {
                float shield = Utils.ActorShieldValue(character);
                if (shield > 0f)
                {
                    bar.AddForegrounds(
                        GetShieldForeground(
                            shieldConfig,
                            config.Position,
                            config.Size,
                            fillSize,
                            config.FillDirection,
                            shield,
                            character.CurrentHp,
                            character.MaxHp,
                            color)
                    );
                }
            }
        }
    }
}
