using System;
using KitsuneCafe.Sys.Attributes;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using Debug = UnityEngine.Debug;

[CustomPropertyDrawer(typeof(ConditionalDisplayAttribute), true)]
public class ConditionalDisplayPropertyDrawer : PropertyDrawer
{
    public override VisualElement CreatePropertyGUI(SerializedProperty property)
    {
        var attribute = this.attribute as ConditionalDisplayAttribute;
        var properties = GetPropertiesFrom(property, attribute.Properties);
        return new SimpleConditionPropertyField(properties, attribute, property);
    }

    private SerializedProperty[] GetPropertiesFrom(SerializedProperty property, string[] propertyNames)
    {
        var len = propertyNames.Length;
        var properties = new SerializedProperty[len];
        var path = property.propertyPath;
        var index = path.LastIndexOf('.');
        var obj = property.serializedObject;

        if (index > -1)
        {
            var parentPath = path[..path.LastIndexOf('.')];
            var parent = obj.FindProperty(parentPath);
            for (int i = 0; i < len; i++)
            {
                properties[i] = parent.FindPropertyRelative(propertyNames[i]);
            }
        }
        else
        {
            for (int i = 0; i < len; i++)
            {
                properties[i] = obj.FindProperty(propertyNames[i]);
            }
        }

        return properties;
    }

    public abstract class ConditionalPropertyField : PropertyField
    {
        protected readonly SerializedProperty[] properties;
        protected readonly ConditionalDisplayAttribute attribute;

        public ConditionalPropertyField(SerializedProperty[] properties, ConditionalDisplayAttribute attribute, SerializedProperty property) : base(property)
        {
            this.properties = properties;
            this.attribute = attribute;
            TrackProperties(properties);
        }

        public ConditionalPropertyField(SerializedProperty[] properties, ConditionalDisplayAttribute attribute, SerializedProperty property, string label) : base(property, label)
        {
            this.properties = properties;
            this.attribute = attribute;
            TrackProperties(properties);
        }

        private void TrackProperties(SerializedProperty[] properties)
        {
            var len = properties.Length;
            for (int i = 0; i < len; i++)
            {
                var property = properties[i];
                OnTrackedValueChanged(property);
                this.TrackPropertyValue(property, OnTrackedValueChanged);
            }
        }

        protected abstract void OnTrackedValueChanged(SerializedProperty property);
    }

    public class SimpleConditionPropertyField : ConditionalPropertyField
    {
        public SimpleConditionPropertyField(SerializedProperty[] properties, ConditionalDisplayAttribute attribute, SerializedProperty property) : base(properties, attribute, property)
        {
        }

        public SimpleConditionPropertyField(SerializedProperty[] properties, ConditionalDisplayAttribute attribute, SerializedProperty property, string label) : base(properties, attribute, property, label)
        {
        }

        protected override void OnTrackedValueChanged(SerializedProperty property)
        {
            var value = (attribute.LogicalOperator, attribute.ComparisonOperator) switch
            {
                (LogicalOperator.Or, ComparisonOperator.Equal) => true,
                (LogicalOperator.And, ComparisonOperator.NotEqual) => true,
                _ => false
            };

            var display = attribute.LogicalOperator switch
            {
                LogicalOperator.And => All(properties, attribute.Value, attribute.ComparisonOperator),
                LogicalOperator.Or => Any(properties, attribute.Value, attribute.ComparisonOperator),
            };

            DisplayElement(display);
        }

        protected bool Compare<T>(T self, T other, ComparisonOperator op) where T : IComparable<T>
        {
            var value = self.CompareTo(other);

            return op switch
            {
                ComparisonOperator.Less => value < 0,
                ComparisonOperator.Equal => value == 0,
                ComparisonOperator.LessEqual => value <= 0,
                ComparisonOperator.Greater => value > 0,
                ComparisonOperator.NotEqual => value != 0,
                ComparisonOperator.GreaterEqual => value >= 0,
                _ => throw new NotImplementedException(),
            };
        }

        protected bool Compare<T>(T self, object other, ComparisonOperator op) where T : IComparable<T>
        {
            return other is T otherValue && Compare(self, otherValue, op);
        }

        protected bool Compare(SerializedProperty self, object other, ComparisonOperator op)
        {
            return self.propertyType switch
            {
                SerializedPropertyType.Generic => false,
                SerializedPropertyType.Float or SerializedPropertyType.Integer => self.numericType switch
                {
                    SerializedPropertyNumericType.Unknown => false,
                    SerializedPropertyNumericType.UInt8 or SerializedPropertyNumericType.UInt16 or SerializedPropertyNumericType.UInt32 => Compare(self.uintValue, other, op),
                    SerializedPropertyNumericType.Int8 or SerializedPropertyNumericType.Int16 or SerializedPropertyNumericType.Int32 => Compare(self.intValue, other, op),
                    SerializedPropertyNumericType.Int64 => Compare(self.longValue, other, op),
                    SerializedPropertyNumericType.UInt64 => Compare(self.ulongValue, other, op),
                    SerializedPropertyNumericType.Float => Compare(self.floatValue, other, op),
                    SerializedPropertyNumericType.Double => Compare(self.doubleValue, other, op),
                    _ => false,
                },
                SerializedPropertyType.Boolean => Compare(self.boolValue, other, op),
                SerializedPropertyType.String => Compare(self.stringValue, other, op),
                // SerializedPropertyType.Color => Compare(self.colorValue, other, op),
                SerializedPropertyType.ObjectReference => false,
                SerializedPropertyType.LayerMask => Compare(self.intValue, other, op),
                SerializedPropertyType.Enum => Compare(self.intValue, other, op),
                // SerializedPropertyType.Vector2 => Compare(self.vector2Value, other, op),
                // SerializedPropertyType.Vector3 => Compare(self.vector3Value, other, op),
                // SerializedPropertyType.Vector4 => Compare(self.vector4Value, other, op),
                // SerializedPropertyType.Rect => Compare(self.rectValue, other, op),
                SerializedPropertyType.ArraySize => Compare(self.arraySize, other, op),
                SerializedPropertyType.Character => Compare(self.stringValue, other, op),
                // SerializedPropertyType.AnimationCurve => Compare(self.animationCurveValue, other, op),
                // SerializedPropertyType.Bounds => Compare(self.boundsValue, other, op),
                // SerializedPropertyType.Gradient => Compare(self.gradientValue, other, op),
                // SerializedPropertyType.Quaternion => Compare(self.quaternionValue, other, op),
                SerializedPropertyType.ExposedReference => false,
                // SerializedPropertyType.FixedBufferSize => Compare(self.fixedBufferSize, other, op),
                // SerializedPropertyType.Vector2Int => Compare(self.vector2IntValue, other, op),
                // SerializedPropertyType.Vector3Int => Compare(self.vector3IntValue, other, op),
                // SerializedPropertyType.RectInt => Compare(self.rectIntValue, other, op),
                // SerializedPropertyType.BoundsInt => Compare(self.boundsIntValue, other, op),
                SerializedPropertyType.ManagedReference => false,
                SerializedPropertyType.Hash128 => Compare(self.hash128Value, other, op),
                SerializedPropertyType.RenderingLayerMask => Compare(self.intValue, other, op),
                _ => false,
            };
        }

        protected bool All(SerializedProperty[] properties, object value, ComparisonOperator op)
        {
            foreach (var prop in properties)
            {
                if (!Compare(prop, value, op))
                {
                    return false;
                }
            }

            return true;
        }

        protected bool Any(SerializedProperty[] properties, object value, ComparisonOperator op)
        {
            foreach (var prop in properties)
            {
                if (Compare(prop, value, op))
                {
                    return true;
                }
            }

            return false;
        }

        private void DisplayElement(bool displayed)
        {
            style.display = displayed ? DisplayStyle.Flex : DisplayStyle.None;
        }
    }
}


