using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

namespace Jovian.Utilities {
    public static class SceneUtility {

        public static List<GameObject> FindGameObjectsInScene(this Scene scene, bool includeInactive = false, Func<GameObject, bool> predicate = null) {
            if (scene.IsValid() == false)
            {
                Debug.LogError("Scene is invalid");
                return null;
            }

            var list = new List<GameObject>();
            GameObject[] sceneObjects = scene.GetRootGameObjects();
            GameObject[] loadedObjects = FindDontDestroyOnLoadObjects();
            GameObject[] combinedGameobjects = new GameObject[sceneObjects.Length + loadedObjects.Length];
            sceneObjects.CopyTo(combinedGameobjects, 0);
            loadedObjects.CopyTo(combinedGameobjects, sceneObjects.Length);
            foreach (var rootObject in combinedGameobjects)
            {
                var allChildTransforms = rootObject.GetComponentsInChildren<Transform>(includeInactive);
                var allGameObjects = allChildTransforms.Select(transform => transform.gameObject);
                if (predicate != null)
                {
                    allGameObjects = allGameObjects.Where(predicate);
                }

                list.AddRange(allGameObjects);
            }

            return list;
        }

        public static List<GameObject> FindGameObjectsInActiveScene(bool includeInactive = false, Func<GameObject, bool> predicate = null)
            => FindGameObjectsInScene(SceneManager.GetActiveScene(), includeInactive, predicate);

        // Adding support for finding objects not in the main scenes but in DontDestroyOnLoad
        // From https://forum.unity.com/threads/editor-script-how-to-access-objects-under-dontdestroyonload-while-in-play-mode.442014/#post-3570916
        private static GameObject[] FindDontDestroyOnLoadObjects() {
            if (!Application.isPlaying)
            {
                return new GameObject[] { }; // return an empty array as this method creates issues in edit mode
            }

            GameObject temp = null;
            try
            {
                temp = new GameObject();
                Object.DontDestroyOnLoad(temp);
                UnityEngine.SceneManagement.Scene dontDestroyOnLoad = temp.scene;
                Object.DestroyImmediate(temp);
                temp = null;

                return dontDestroyOnLoad.GetRootGameObjects();
            }
            finally
            {
                if (temp != null)
                    Object.DestroyImmediate(temp);
            }
        }

        public static List<TComponent> FindComponentsInScene<TComponent>(this Scene scene, bool includeInactive = false) where TComponent : Component {
            if (scene.IsValid() == false)
            {
                Debug.LogError("Scene is invalid");
                return null;
            }

            var list = new List<TComponent>();

            foreach (var rootObject in scene.GetRootGameObjects())
            {
                list.AddRange(rootObject.GetComponentsInChildren<TComponent>(includeInactive));
            }

            return list;
        }

        public static List<Component> FindComponentsInScene(this Scene scene, Type componentType, bool includeInactive = false) {
            if (scene.IsValid() == false)
            {
                Debug.LogError("Scene is invalid");
                return null;
            }

            var list = new List<Component>();
            foreach (GameObject rootObject in scene.GetRootGameObjects())
            {
                list.AddRange(rootObject.GetComponentsInChildren(componentType, includeInactive));
            }

            return list;
        }

        public static List<TComponent> FindComponentsInActiveScene<TComponent>(bool includeInactive = false) where TComponent : Component
            => FindComponentsInScene<TComponent>(SceneManager.GetActiveScene(), includeInactive);

        public static List<Component> FindComponentsInActiveScene(Type componentType, bool includeInactive = false)
            => FindComponentsInScene(SceneManager.GetActiveScene(), componentType, includeInactive);

        public static List<TComponent> FindComponentsInAllScenes<TComponent>(bool includeInactive = false) where TComponent : Component {
            List<TComponent> allComponents = new();
            for (int i = 0, c = SceneManager.sceneCount; i < c; i++)
            {
                allComponents.AddRange(FindComponentsInScene<TComponent>(SceneManager.GetSceneAt(i), includeInactive));
            }

            return allComponents;
        }

        public static TComponent FindComponentInScene<TComponent>(this Scene scene, bool includeInactive = false) where TComponent : Component {
            if (scene.IsValid() == false)
            {
                Debug.LogError("Scene is invalid");
                return null;
            }

            var list = new List<TComponent>();

            foreach (var rootObject in scene.GetRootGameObjects())
            {
                list.AddRange(rootObject.GetComponentsInChildren<TComponent>(includeInactive));
            }

            return list.FirstOrDefault();
        }

        public static Component FindComponentInScene(this Scene scene, Type componentType, bool includeInactive = false) {
            if (scene.IsValid() == false)
            {
                Debug.LogError("Scene is invalid");
                return null;
            }

            var list = new List<Component>();

            foreach (GameObject rootObject in scene.GetRootGameObjects())
            {
                list.AddRange(rootObject.GetComponentsInChildren(componentType, includeInactive));
            }

            return list.FirstOrDefault();
        }

        public static TComponent FindComponentInActiveScene<TComponent>(bool includeInactive = false) where TComponent : Component
            => FindComponentInScene<TComponent>(SceneManager.GetActiveScene(), includeInactive);

        public static Component FindComponentInActiveScene(Type componentType, bool includeInactive = false)
            => FindComponentInScene(SceneManager.GetActiveScene(), componentType, includeInactive);

        public static TComponent FindComponentInAllScenes<TComponent>(bool includeInactive = false) where TComponent : Component
            => FindComponentsInAllScenes<TComponent>(includeInactive).FirstOrDefault();


        public static GameObject FindGameObject(string nameQuery) {
            char delimiter = '/';
            string[] querySegments = nameQuery.Split(delimiter);

            return FindGameObjectsInActiveScene(true, (gameObject) => {
                int queryIndex = querySegments.Length - 1;
                Transform compareTransform = gameObject.transform;
                do
                {
                    if (compareTransform != null && compareTransform.name.Equals(querySegments[queryIndex]))
                    {
                        compareTransform = compareTransform.parent;
                        queryIndex--;
                    }
                    // if compare is null and string is null that means the first query segment was /, so that's the root element - then our parent MUST be null
                    else if (compareTransform == null && string.IsNullOrEmpty(querySegments[queryIndex]))
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                } while (queryIndex >= 0);

                return true;
            }).FirstOrDefault();
        }

        private static Transform GetChildWithName(this Transform transform, string childName) {
            foreach (Transform child in transform.transform)
            {
                if (child.name.Equals(childName))
                {
                    return child;
                }
            }

            return null;
        }

    }
}