#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Linq;
using Jovian.Utilities;
using Jovian.Utilities.Editor;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Jovian.InspectorTools {
    public static class AutoFindUtil {
        public static void Search(AutoFindAttribute autoFindAttribute, SerializedProperty property, bool autoAddIfMissing = false) {
            Type propertyType = EditorSerializationUtility.GetTypeFromProperty(property);

            foreach (Object targetObject in property.serializedObject.targetObjects) {
                if (IsScopeGameObject(autoFindAttribute.scope)) {
                    GameObject targetGameObject = ((Component)targetObject).gameObject;

                    if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) {
                        SerializedProperty arrayProperty =
                            EditorSerializationUtility.GetArrayPropertyWithElementProperty(property);
                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            arrayProperty.name,
                            GetComponents(autoFindAttribute.scope, targetGameObject, propertyType,
                                autoFindAttribute.filter));
                    }
                    else {
                        Component component = GetComponent(autoFindAttribute.scope, targetGameObject, propertyType,
                            autoFindAttribute.filter);

                        if (autoAddIfMissing && !component) {
                            component = Undo.AddComponent(targetGameObject, propertyType);
                        }

                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            property.name,
                            component);
                    }
                }
                else if (autoFindAttribute.scope == AutoFindScope.Scene) {
                    IEnumerable<Component> componentsInHierarchy =
                        FilterComponents(HierarchyUtility.FindComponentsInHierarchy(propertyType, true),
                            autoFindAttribute.filter);
                    if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) {
                        SerializedProperty arrayProperty =
                            EditorSerializationUtility.GetArrayPropertyWithElementProperty(property);
                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            arrayProperty.name,
                            componentsInHierarchy);
                    }
                    else {
                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            property.name,
                            componentsInHierarchy.FirstOrDefault());
                    }
                }
                else if (autoFindAttribute.scope == AutoFindScope.Project) {
                    if (EditorSerializationUtility.IsPropertyAnArrayElement(property)) {
                        SerializedProperty arrayProperty =
                            EditorSerializationUtility.GetArrayPropertyWithElementProperty(property);
                        List<Object> assets = autoFindAttribute.isPrefab
                            ? AssetUtility.FindAllPrefabsInProject(propertyType, autoFindAttribute.filter)
                            : AssetUtility.FindAllAssetsInProject(propertyType, autoFindAttribute.filter);
                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            arrayProperty.name,
                            assets
                        );
                    }
                    else {
                        Object asset = autoFindAttribute.isPrefab
                            ? AssetUtility.FindPrefabInProject(propertyType, autoFindAttribute.filter)
                            : AssetUtility.FindAssetInProject(propertyType, autoFindAttribute.filter);
                        EditorSerializationUtility.SaveObjectProperties(
                            targetObject,
                            property.name,
                            asset
                        );
                    }
                }
            }
        }

        private static bool IsScopeGameObject(AutoFindScope scope) {
            switch (scope) {
                case AutoFindScope.Self:
                case AutoFindScope.SelfAndChildren:
                case AutoFindScope.SelfAndParent:
                    return true;
            }

            return false;
        }

        private static Component GetComponent(AutoFindScope scope,
            GameObject gameObject,
            Type componentType,
            string filter = null) {
            return GetComponents(scope, gameObject, componentType, filter).FirstOrDefault();
        }

        private static IEnumerable<Component> GetComponents(AutoFindScope scope,
            GameObject gameObject,
            Type componentType,
            string filter = null) {
            switch (scope) {
                case AutoFindScope.Self:
                    List<Component> result = new();
                    Component component = gameObject.GetComponent(componentType);
                    if (component != null) {
                        result.Add(component);
                    }

                    return FilterComponents(result, filter);
                case AutoFindScope.SelfAndChildren:
                    return FilterComponents(gameObject.GetComponentsInChildren(componentType, true), filter);
                case AutoFindScope.SelfAndParent:
                    return FilterComponents(gameObject.GetComponentsInParent(componentType, true), filter);
                case AutoFindScope.Scene:
                case AutoFindScope.Project:
                    throw new NotSupportedException($"{scope} is not supported with GetComponents");
                default:
                    throw new ArgumentOutOfRangeException(nameof(scope), scope, null);
            }
        }

        private static IEnumerable<Component> FilterComponents(IEnumerable<Component> components, string filter) {
            return string.IsNullOrEmpty(filter) ? components : components.Where(c => FilterMatchesName(c.name, filter));
        }

        private static bool FilterMatchesName(string name, string filter) {
            return name.Contains(filter, StringComparison.InvariantCultureIgnoreCase);
        }
    }
}
#endif
