using Streamingle.Utils.RoughConstraints;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

namespace Streamingle.Utils.RoughConstraintsEditor
{
    [CustomEditor(typeof(RoughConstraintBase), true)]
    [CanEditMultipleObjects]
    public sealed class RoughConstraintEditor : UnityEditor.Editor
    {
        private SerializedProperty constraintActive;
        private SerializedProperty locked;
        private SerializedProperty weight;
        private SerializedProperty sources;
        private SerializedProperty damping;
        private SerializedProperty updateMode;
        private SerializedProperty useUnscaledTime;
        private SerializedProperty snapOnEnable;
        private ReorderableList sourceList;
        private bool roughFollowFoldout = true;
        private bool settingsFoldout = true;

        private void OnEnable()
        {
            constraintActive = serializedObject.FindProperty("constraintActive");
            locked = serializedObject.FindProperty("locked");
            weight = serializedObject.FindProperty("weight");
            sources = serializedObject.FindProperty("sources");
            damping = serializedObject.FindProperty("damping");
            updateMode = serializedObject.FindProperty("updateMode");
            useUnscaledTime = serializedObject.FindProperty("useUnscaledTime");
            snapOnEnable = serializedObject.FindProperty("snapOnEnable");

            sourceList = new ReorderableList(serializedObject, sources, true, true, true, true)
            {
                drawHeaderCallback = DrawSourceHeader,
                drawElementCallback = DrawSourceElement,
                elementHeightCallback = _ => EditorGUIUtility.singleLineHeight + 6f,
                onAddCallback = OnAddSource
            };
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            DrawCommonSettings();
            DrawSourceList();
            DrawTypeSettings();

            serializedObject.ApplyModifiedProperties();
        }

        private void DrawCommonSettings()
        {
            EditorGUILayout.PropertyField(constraintActive, new GUIContent("Constraint Active"));
            DrawLockField();
            EditorGUILayout.Slider(weight, 0f, 1f, new GUIContent("Weight"));

            EditorGUILayout.BeginHorizontal();
            GUILayout.FlexibleSpace();
            if (GUILayout.Button("Activate", EditorStyles.miniButton, GUILayout.Width(72f)))
            {
                ApplyAction("Activate Rough Constraint", ActivateConstraint);
            }

            if (GUILayout.Button("Zero", EditorStyles.miniButton, GUILayout.Width(56f)))
            {
                ZeroOffsets();
            }
            EditorGUILayout.EndHorizontal();
        }

        private void DrawLockField()
        {
            EditorGUI.BeginChangeCheck();
            var newValue = EditorGUILayout.Toggle(new GUIContent("Lock"), locked.boolValue);
            if (!EditorGUI.EndChangeCheck())
            {
                return;
            }

            serializedObject.ApplyModifiedProperties();
            foreach (var selectedTarget in targets)
            {
                var constraint = (RoughConstraintBase)selectedTarget;
                Undo.RecordObject(constraint, "Change Rough Constraint Lock");
                constraint.SetLocked(newValue);
                EditorUtility.SetDirty(constraint);
            }

            serializedObject.Update();
        }

        private void DrawSourceList()
        {
            EditorGUILayout.Space(2f);
            sourceList.DoLayoutList();
            EditorGUILayout.Space(4f);
        }

        private void DrawTypeSettings()
        {
            roughFollowFoldout = EditorGUILayout.Foldout(roughFollowFoldout, "Rough Follow", true);
            if (roughFollowFoldout)
            {
                using (new EditorGUI.IndentLevelScope())
                {
                    EditorGUILayout.PropertyField(damping, new GUIContent("Damping"));
                    EditorGUILayout.PropertyField(updateMode, new GUIContent("Update Mode"));
                    EditorGUILayout.PropertyField(useUnscaledTime, new GUIContent("Use Unscaled Time"));
                    EditorGUILayout.PropertyField(snapOnEnable, new GUIContent("Snap On Enable"));
                }
            }

            settingsFoldout = EditorGUILayout.Foldout(settingsFoldout, GetSettingsTitle(), true);
            if (!settingsFoldout)
            {
                return;
            }

            using (new EditorGUI.IndentLevelScope())
            {
                DrawTypeProperties();
            }
        }

        private void DrawTypeProperties()
        {
            if (target is RoughPositionConstraint)
            {
                DrawProperties("constrainedAxis", "positionOffset", "offsetInSourceSpace");
            }
            else if (target is RoughRotationConstraint)
            {
                DrawProperties("constrainedAxis", "rotationOffset");
            }
            else if (target is RoughScaleConstraint)
            {
                DrawProperties("constrainedAxis", "scaleOffset");
            }
            else if (target is RoughParentConstraint)
            {
                DrawProperties("constrainedPositionAxis", "positionOffset", "positionOffsetInSourceSpace", "constrainedRotationAxis", "rotationOffset");
            }
            else if (target is RoughAimConstraint)
            {
                DrawProperties("aimAxis", "upAxis", "worldUpVector", "rotationOffset");
            }
            else if (target is RoughLookAtConstraint)
            {
                DrawProperties("forwardAxis", "worldUpVector", "targetPositionOffset", "rotationOffset");
            }
        }

        private void DrawProperties(params string[] propertyNames)
        {
            for (var i = 0; i < propertyNames.Length; i++)
            {
                var property = serializedObject.FindProperty(propertyNames[i]);
                if (property != null)
                {
                    EditorGUILayout.PropertyField(property, new GUIContent(ObjectNames.NicifyVariableName(property.name)));
                }
            }
        }

        private void ApplyAction(string undoName, System.Action<RoughConstraintBase> action)
        {
            serializedObject.ApplyModifiedProperties();

            foreach (var selectedTarget in targets)
            {
                var constraint = (RoughConstraintBase)selectedTarget;
                Undo.RecordObject(constraint.transform, undoName);
                action(constraint);
                EditorUtility.SetDirty(constraint.transform);
            }
        }

        private void ActivateConstraint(RoughConstraintBase constraint)
        {
            Undo.RecordObject(constraint, "Activate Rough Constraint");
            constraint.ActivateConstraint();
            EditorUtility.SetDirty(constraint);
        }

        private void ZeroOffsets()
        {
            serializedObject.ApplyModifiedProperties();
            Undo.RecordObjects(targets, "Zero Rough Constraint Offsets");

            SetVector3IfExists("positionOffset", Vector3.zero);
            SetVector3IfExists("rotationOffset", Vector3.zero);
            SetVector3IfExists("scaleOffset", Vector3.zero);
            SetVector3IfExists("targetPositionOffset", Vector3.zero);

            serializedObject.ApplyModifiedProperties();
        }

        private void SetVector3IfExists(string propertyName, Vector3 value)
        {
            var property = serializedObject.FindProperty(propertyName);
            if (property != null)
            {
                property.vector3Value = value;
            }
        }

        private void DrawSourceHeader(Rect rect)
        {
            var objectRect = new Rect(rect.x + 14f, rect.y, rect.width - 88f, rect.height);
            var weightRect = new Rect(rect.xMax - 70f, rect.y, 68f, rect.height);

            EditorGUI.LabelField(objectRect, "Sources");
            EditorGUI.LabelField(weightRect, "Weight");
        }

        private void DrawSourceElement(Rect rect, int index, bool isActive, bool isFocused)
        {
            var element = sources.GetArrayElementAtIndex(index);
            var sourceTransform = element.FindPropertyRelative("sourceTransform");
            var sourceWeight = element.FindPropertyRelative("weight");

            rect.y += 3f;
            rect.height = EditorGUIUtility.singleLineHeight;

            if (sourceTransform == null || sourceWeight == null)
            {
                EditorGUI.PropertyField(rect, element, GUIContent.none);
                return;
            }

            var weightWidth = 70f;
            var gap = 6f;
            var targetRect = new Rect(rect.x, rect.y, rect.width - weightWidth - gap, rect.height);
            var weightRect = new Rect(targetRect.xMax + gap, rect.y, weightWidth, rect.height);

            EditorGUI.PropertyField(targetRect, sourceTransform, GUIContent.none);
            sourceWeight.floatValue = Mathf.Clamp01(EditorGUI.FloatField(weightRect, sourceWeight.floatValue));
        }

        private void OnAddSource(ReorderableList list)
        {
            sources.arraySize++;
            var element = sources.GetArrayElementAtIndex(sources.arraySize - 1);
            var sourceTransform = element.FindPropertyRelative("sourceTransform");
            var sourceWeight = element.FindPropertyRelative("weight");

            if (sourceTransform != null)
            {
                sourceTransform.objectReferenceValue = null;
            }

            if (sourceWeight != null)
            {
                sourceWeight.floatValue = 1f;
            }
        }

        private string GetSettingsTitle()
        {
            if (target is RoughLookAtConstraint)
            {
                return "Look At";
            }

            var name = target.GetType().Name;
            name = name.Replace("Rough", string.Empty).Replace("Constraint", string.Empty);
            return ObjectNames.NicifyVariableName(name);
        }
    }
}
