using System;
using UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Jovian.Utilities {
    public static class AssetUtility {

        public static TAsset FindAssetInProject<TAsset>(string assetName = "") where TAsset : Object {
#if UNITY_EDITOR
            var filter = $"t:{typeof(TAsset).Name} {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
                if (asset != null)
                {
                    return asset;
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return default(TAsset);
        }

        public static Object FindAssetInProject(Type assetType, string assetName = "") {
#if UNITY_EDITOR
            var filter = $"t:{assetType.Name} {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
                if (asset != null)
                {
                    return asset;
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return default(Object);
        }

        public static List<TAsset> FindAllAssetsInProject<TAsset>(string assetName = "") where TAsset : Object {
            var list = new List<TAsset>();
#if UNITY_EDITOR
            var filter = $"t:{typeof(TAsset).Name} {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
                if (asset != null)
                {
                    list.Add(asset);
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return list;
        }

        public static List<Object> FindAllAssetsInProject(Type assetType, string assetName = "") {
            var list = new List<Object>();
#if UNITY_EDITOR
            var filter = $"t:{assetType.Name} {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
                if (asset != null)
                {
                    list.Add(asset);
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return list;
        }

        public static List<Object> FindAllObjectsInProject(Type objectType, string filter) {
            List<Object> list = new List<Object>();
#if UNITY_EDITOR
            string[] guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                string path = AssetDatabase.GUIDToAssetPath(guid);
                Object asset = AssetDatabase.LoadAssetAtPath<Object>(path);
                
                if (typeof(Component).IsAssignableFrom(objectType) &&
                    TryGetTypeObjectWithPrefab(asset, objectType, out Component _))
                {
                    list.Add(asset);
                }
                else if (asset.GetType().IsAssignableFrom(objectType))
                {
                    list.Add(asset);
                }
                else if (ShouldUnloadAsset(asset))
                {
                    Resources.UnloadAsset(asset);
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return list;
        }

        // Prefabs
        public static TAsset FindPrefabInProject<TAsset>(string assetName = "") where TAsset : Component {
#if UNITY_EDITOR
            var filter = $"t:Prefab {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
                if (TryGetTypeObjectWithPrefab(asset, out TAsset component))
                {
                    return component;
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            Debug.LogError($"Failed to find asset '{assetName}' <{typeof(TAsset)}>");
            return default(TAsset);
        }

        public static Object FindPrefabInProject(Type assetType, string assetName = "") {
#if UNITY_EDITOR
            var filter = $"t:Prefab {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
                if (TryGetTypeObjectWithPrefab(asset, assetType, out Component component))
                {
                    return component;
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return default(Object);
        }

        public static List<TAsset> FindAllPrefabsInProject<TAsset>(string assetName = "") where TAsset : Component {
            var list = new List<TAsset>();
#if UNITY_EDITOR
            var filter = $"t:Prefab {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<TAsset>(path);
                if (TryGetTypeObjectWithPrefab(asset, out TAsset component))
                {
                    list.Add(component);
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return list;
        }

        public static List<Object> FindAllPrefabsInProject(Type assetType, string assetName = "") {
            var list = new List<Object>();
#if UNITY_EDITOR
            var filter = $"t:Prefab {assetName}";
            var guids = AssetDatabase.FindAssets(filter);
            foreach (var guid in guids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<Object>(path);
                if (TryGetTypeObjectWithPrefab(asset, assetType, out Component component))
                {
                    list.Add(component);
                }
                else
                {
                    if (ShouldUnloadAsset(asset))
                    {
                        Resources.UnloadAsset(asset);
                    }
                }
            }
#else
            Debug.LogError("AssetUtility should not be called in non-Editor mode");
#endif
            return list;
        }

        // Util

#if UNITY_EDITOR
        public static bool ShouldUnloadAsset(Object asset) {
            if (asset == null)
            {
                return false;
            }

            return !(asset is GameObject or Component or AssetBundle ||
                     PrefabUtility.GetPrefabAssetType(asset) != PrefabAssetType.NotAPrefab);
        }

        private static bool TryGetTypeObjectWithPrefab(Object prefab, Type type, out Component component) {
            if (prefab != null && prefab is GameObject gameObject && gameObject.TryGetComponent(type, out component))
            {
                return true;
            }

            component = default;
            return false;
        }

        private static bool TryGetTypeObjectWithPrefab<TComponent>(Object prefab, out TComponent component)
            where TComponent : Component {
            if (prefab != null && prefab is TComponent prefabAsComponent)
            {
                component = prefabAsComponent;
                return true;
            }

            if (prefab != null && prefab is GameObject gameObject && gameObject.TryGetComponent(out component))
            {
                return true;
            }

            component = default;
            return false;
        }
#endif

    }
}