using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEditor.Compilation;
using System.Collections.Generic;
using IntelligentProjectAnalyzer.Analyzer;
using UnityEditor.SceneManagement;

// All using statements now refer to the new data-only class
using static IntelligentProjectAnalyzer.Editor.DependencyBuilderData;

namespace IntelligentProjectAnalyzer.Editor
{
    /// <summary>
    /// Handles all communication with the Unity API that must occur on the main thread
    /// to gather data for the dependency analysis.
    /// </summary>
    public static class AssetDataFetcher
    {
        /// <summary>
        /// Finds all relevant asset GUIDs for scripts, prefabs, and ScriptableObjects.
        /// </summary>
        /// <returns>A tuple containing queues of GUIDs for each asset type.</returns>
        public static (Queue<string> scriptGuids, Queue<string> prefabGuids, Queue<string> soGuids, Queue<string> sceneGuids, Queue<string> miscGuids) FindAssetGuids()
        {
            var scripts = new Queue<string>(AssetDatabase.FindAssets("t:MonoScript"));
            var prefabs = new Queue<string>(AssetDatabase.FindAssets("t:Prefab"));
            var scriptableObjects = new Queue<string>(AssetDatabase.FindAssets("t:ScriptableObject"));
            var scenes = new Queue<string>(AssetDatabase.FindAssets("t:Scene"));
            var allGuids = new Queue<string>(AssetDatabase.FindAssets(""));
            
            var knownGuids = new HashSet<string>();
            knownGuids.UnionWith(scriptableObjects);
            knownGuids.UnionWith(prefabs);
            knownGuids.UnionWith(scripts);
            knownGuids.UnionWith(scenes);
            
            var miscGuids = new Queue<string>(allGuids.Except(knownGuids));

            return (scripts, prefabs, scriptableObjects, scenes, miscGuids);
        }

        /// <summary>
        /// Gathers metadata for a single script asset. Its dependencies will be filled in later.
        /// </summary>
        public static ScriptMetadata PreFetchScriptMetadata(string guid)
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/")) return null;
            return new ScriptMetadata { Guid = guid, FullPath = Path.GetFullPath(assetPath).Replace('\\', '/') };
        }

        /// <summary>
        /// Gathers metadata for a single prefab asset, including its direct script dependencies.
        /// </summary>
        public static PrefabMetadata PreFetchPrefabMetadata(string guid)
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/")) return null;
    
            var go = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
            if (go == null) return null;

            var prefabMetadata = new PrefabMetadata { Guid = guid };
            
            var hash = new HashSet<string>();

            // Find all components on the prefab and its children to get their script GUIDs.
            var components = go.GetComponentsInChildren<Component>(true);
            foreach (var component in components)
            {
                if (component == null) continue;
                
                var script = MonoScript.FromMonoBehaviour(component as MonoBehaviour);
                if (script == null) continue;

                var scriptAssetPath = AssetDatabase.GetAssetPath(script);
                if (string.IsNullOrEmpty(scriptAssetPath)) continue;

                var scriptGuid = AssetDatabase.AssetPathToGUID(scriptAssetPath);
                if (!string.IsNullOrEmpty(scriptGuid) && scriptAssetPath.StartsWith("Assets/"))
                {
                    hash.Add(scriptGuid);
                }
            }
            
            var assetDependencies = AssetDatabase.GetDependencies(assetPath);
            foreach (var dependency in assetDependencies)
            {
                if (string.IsNullOrEmpty(dependency) || !dependency.StartsWith("Assets/") || dependency == assetPath)
                {
                    continue;
                }
                var dependencyGuid = AssetDatabase.AssetPathToGUID(dependency);
                if (!string.IsNullOrEmpty(dependencyGuid))
                {
                    hash.Add(dependencyGuid);
                }
            }
            
            prefabMetadata.DependencyGuids = hash.ToList();
            return prefabMetadata;
        }

        /// <summary>
        /// Gathers metadata for a single ScriptableObject asset, including its direct script dependency.
        /// </summary>
        public static ScriptableObjectMetadata PreFetchScriptableObjectMetadata(string guid)
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/")) return null;

            var so = AssetDatabase.LoadAssetAtPath<ScriptableObject>(assetPath);
            if (so == null) return null;

            var script = MonoScript.FromScriptableObject(so);
            if (script == null) return null;

            var scriptAssetPath = AssetDatabase.GetAssetPath(script);
            if (string.IsNullOrEmpty(scriptAssetPath)) return null;

            var hash = new HashSet<string>();

            var scriptGuid = AssetDatabase.AssetPathToGUID(scriptAssetPath);
            var soMetadata = new ScriptableObjectMetadata { Guid = guid };
            if (!string.IsNullOrEmpty(scriptGuid) && scriptAssetPath.StartsWith("Assets/"))
            {
                hash.Add(scriptGuid);
            }
            
            var assetDependencies = AssetDatabase.GetDependencies(assetPath);
            foreach (var dependency in assetDependencies)
            {
                if (string.IsNullOrEmpty(dependency) || !dependency.StartsWith("Assets/") || dependency == assetPath)
                {
                    continue;
                }
                var dependencyGuid = AssetDatabase.AssetPathToGUID(dependency);
                if (!string.IsNullOrEmpty(dependencyGuid))
                {
                    hash.Add(dependencyGuid);
                }
            }
            
            soMetadata.DependencyGuids = hash.ToList();
            return soMetadata;
        }

        /// <summary>
        /// Gathers all data required to initialize the Roslyn C# analysis.
        /// </summary>
        public static RoslynSetupData PreFetchRoslynSetupData(List<ScriptMetadata> scriptMetadata)
        {
            var setupData = new RoslynSetupData
            {
                SourceFiles = scriptMetadata.Select(s => s.FullPath).ToArray(),
                PreprocessorSymbols = EditorUserBuildSettings.activeScriptCompilationDefines
            };

            var references = new List<string>();
            foreach (var assembly in CompilationPipeline.GetAssemblies())
            {
                references.AddRange(assembly.compiledAssemblyReferences.Select(Path.GetFullPath));
            }
            setupData.References = references.Distinct().ToArray();

            var prefixes = new List<string> { "System", "UnityEngine", "UnityEditor" };
            var settings = AnalyzerSettingsCrud.GetOrCreateSettings();
            if (settings != null && settings.CustomSystemTypes != null)
            {
                prefixes.AddRange(settings.CustomSystemTypes.Where(p => !string.IsNullOrEmpty(p) && !prefixes.Contains(p)));
            }
            setupData.SystemTypePrefixes = prefixes;

            setupData.TypeToGuidMap = new Dictionary<string, string>();
            var tempAnalyzer = new RoslynTypeDependencyAnalyzer(setupData.SourceFiles, setupData.References, setupData.PreprocessorSymbols, setupData.SystemTypePrefixes);
            foreach (var pair in tempAnalyzer.TypeToPathMap)
            {
                var projectPath = Directory.GetParent(Application.dataPath)?.FullName.Replace('\\', '/');
                var relativePath = pair.Value.Replace('\\', '/').Replace(projectPath + "/", "");

                var guid = AssetDatabase.AssetPathToGUID(relativePath);
                if (!string.IsNullOrEmpty(guid)) { setupData.TypeToGuidMap[pair.Key] = guid; }
            }
            return setupData;
        }

        public static MiscAssetMetadata PreFetchMiscAssetMetadata(string guid)
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/")) return null;
            
            var miscMetaData = new MiscAssetMetadata() { Guid = guid };
            var hash = new HashSet<string>();

            var assetDependencies = AssetDatabase.GetDependencies(assetPath);
            foreach (var dependency in assetDependencies)
            {
                if (string.IsNullOrEmpty(dependency) || !dependency.StartsWith("Assets/") || dependency == assetPath)
                {
                    continue;
                }
                var dependencyGuid = AssetDatabase.AssetPathToGUID(dependency);
                if (!string.IsNullOrEmpty(dependencyGuid))
                {
                    hash.Add(dependencyGuid);
                }
            }
            miscMetaData.DependencyGuids = hash.ToList();
            return miscMetaData;
        }

        public static SceneMetadata PreFetchSceneMetadata(string guid)
        {
            var assetPath = AssetDatabase.GUIDToAssetPath(guid);
            if (string.IsNullOrEmpty(assetPath) || !assetPath.StartsWith("Assets/")) return null;
            
            var scene = EditorSceneManager.OpenScene(assetPath, OpenSceneMode.Single);

            var sceneMetaData = new SceneMetadata() { Guid = guid };
            var hash = new HashSet<string>();

            // Find all components on the scene and its children to get their script GUIDs.
            foreach (var root in scene.GetRootGameObjects())
            {
                var components = root.GetComponentsInChildren<Component>(true);
                foreach (var component in components)
                {
                    if (component == null) continue;
                
                    var script = MonoScript.FromMonoBehaviour(component as MonoBehaviour);
                    if (script == null) continue;

                    var scriptAssetPath = AssetDatabase.GetAssetPath(script);
                    if (string.IsNullOrEmpty(scriptAssetPath)) continue;

                    var scriptGuid = AssetDatabase.AssetPathToGUID(scriptAssetPath);
                    if (!string.IsNullOrEmpty(scriptGuid) && scriptAssetPath.StartsWith("Assets/"))
                    {
                        hash.Add(scriptGuid);
                    }
                }
            }
            
            var assetDependencies = AssetDatabase.GetDependencies(assetPath);
            foreach (var dependency in assetDependencies)
            {
                if (string.IsNullOrEmpty(dependency) || !dependency.StartsWith("Assets/") || dependency == assetPath)
                {
                    continue;
                }
                var dependencyGuid = AssetDatabase.AssetPathToGUID(dependency);
                if (!string.IsNullOrEmpty(dependencyGuid))
                {
                    hash.Add(dependencyGuid);
                }
            }
            sceneMetaData.DependencyGuids = hash.ToList();
            return sceneMetaData;
        }
    }
}
