using System;
using System.Collections.Concurrent;
using QuestPDF.Fluent;

namespace QuestPDF.Infrastructure
{
    internal enum TextStyleProperty
    {
        Color,
        BackgroundColor,
        FontFamily,
        Size,
        LineHeight,
        FontWeight,
        FontPosition,
        IsItalic,
        HasStrikethrough,
        HasUnderline,
        WrapAnywhere,
        Fallback
    }
    
    internal static class TextStyleManager
    {
        private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
        private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new();
        private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new();

        public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
        {
            var cacheKey = (origin, property, value);
            return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value));
        }

        private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true)
        {
            if (overrideValue && value == null)
                return origin;
            
            if (property == TextStyleProperty.Color)
            {
                if (!overrideValue && origin.Color != null)
                    return origin;
                
                var castedValue = (string?)value;

                if (origin.Color == castedValue)
                    return origin;

                return origin with { Color = castedValue };
            }
            
            if (property == TextStyleProperty.BackgroundColor)
            {
                if (!overrideValue && origin.BackgroundColor != null)
                    return origin;
                
                var castedValue = (string?)value;
                
                if (origin.BackgroundColor == castedValue)
                    return origin;

                return origin with { BackgroundColor = castedValue };
            }
            
            if (property == TextStyleProperty.FontFamily)
            {
                if (!overrideValue && origin.FontFamily != null)
                    return origin;
                
                var castedValue = (string?)value;
                
                if (origin.FontFamily == castedValue)
                    return origin;

                return origin with { FontFamily = castedValue };
            }
            
            if (property == TextStyleProperty.Size)
            {
                if (!overrideValue && origin.Size != null)
                    return origin;
                
                var castedValue = (float?)value;
                
                if (origin.Size == castedValue)
                    return origin;

                return origin with { Size = castedValue };
            }
            
            if (property == TextStyleProperty.LineHeight)
            {
                if (!overrideValue && origin.LineHeight != null)
                    return origin;
                
                var castedValue = (float?)value;
                
                if (origin.LineHeight == castedValue)
                    return origin;

                return origin with { LineHeight = castedValue };
            }
            
            if (property == TextStyleProperty.FontWeight)
            {
                if (!overrideValue && origin.FontWeight != null)
                    return origin;
                
                var castedValue = (FontWeight?)value;
                
                if (origin.FontWeight == castedValue)
                    return origin;

                return origin with { FontWeight = castedValue };
            }
            
            if (property == TextStyleProperty.FontPosition)
            {
                if (!overrideValue && origin.FontPosition != null)
                    return origin;
                
                var castedValue = (FontPosition?)value;
                
                if (origin.FontPosition == castedValue)
                    return origin;

                return origin with { FontPosition = castedValue };
            }
            
            if (property == TextStyleProperty.IsItalic)
            {
                if (!overrideValue && origin.IsItalic != null)
                    return origin;
                
                var castedValue = (bool?)value;
                
                if (origin.IsItalic == castedValue)
                    return origin;

                return origin with { IsItalic = castedValue };
            }
            
            if (property == TextStyleProperty.HasStrikethrough)
            {
                if (!overrideValue && origin.HasStrikethrough != null)
                    return origin;
                
                var castedValue = (bool?)value;
                
                if (origin.HasStrikethrough == castedValue)
                    return origin;

                return origin with { HasStrikethrough = castedValue };
            }
            
            if (property == TextStyleProperty.HasUnderline)
            {
                if (!overrideValue && origin.HasUnderline != null)
                    return origin;
                
                var castedValue = (bool?)value;
                
                if (origin.HasUnderline == castedValue)
                    return origin;

                return origin with { HasUnderline = castedValue };
            }
            
            if (property == TextStyleProperty.WrapAnywhere)
            {
                if (!overrideValue && origin.WrapAnywhere != null)
                    return origin;

                var castedValue = (bool?)value;
                
                if (origin.WrapAnywhere == castedValue)
                    return origin;
                
                return origin with { WrapAnywhere = castedValue };
            }
            
            if (property == TextStyleProperty.Fallback)
            {
                if (!overrideValue && origin.Fallback != null)
                    return origin;

                var castedValue = (TextStyle?)value;
                
                if (origin.Fallback == castedValue)
                    return origin;
                
                return origin with { Fallback = castedValue };
            }

            throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
        }
        
        internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
        {
            var cacheKey = (style, parent);
            return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
        }
        
        private static TextStyle ApplyFontFallback(this TextStyle style)
        {
            var targetFallbackStyle = style
                ?.Fallback
                ?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
                ?.ApplyFontFallback();
            
            return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
        }
        
        internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
        {
            var cacheKey = (style, parent);
            
            return TextStyleOverrideCache.GetOrAdd(cacheKey, key =>
            {
                var result = ApplyStyle(key.origin, key.parent);
                return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback);
            });
        }
        
        private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true)
        {
            var result = style;

            result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
            result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
            
            if (applyFallback)
                result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);

            return result;
        }
    }
}