using System.IO;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Text;
using LLM.Editor.Helper;
using System.Reflection;
using System.Collections.Generic;
using IntelligentProjectAnalyzer.Editor.Graphing;

namespace LLM.Editor.Analysis
{
    /// <summary>
    /// Responsible for gathering and serializing context about staged assets
    /// into a detailed, human-readable, and referential format for the LLM.
    /// </summary>
    public static class ContextBuilder
    {
        /// <summary>
        /// Builds a detailed Tier 1 summary of the provided objects, including a pruned hierarchy for GameObjects.
        /// </summary>
        /// <param name="stagedObjects">The list of objects from the UI's staging area.</param>
        /// <returns>A formatted string summarizing the context for the LLM.</returns>
        public static string BuildTier1Summary(List<Object> stagedObjects)
        {
            var summaryBuilder = new StringBuilder();

            // 1. Always Prepend Project Info
            AppendProjectInfo(summaryBuilder);

            // 2. Process Staged Objects
            if (stagedObjects is { Count: > 0 } && stagedObjects.Any(o => o != null))
            {
                for (var i = 0; i < stagedObjects.Count; i++)
                {
                    var obj = stagedObjects[i];
                    if (!obj) continue;

                    var stableId = GetStableIdForObject(obj);
                    summaryBuilder.AppendLine($"\n## Subject {i + 1}: {obj.name} (ID: {stableId})");
                    summaryBuilder.AppendLine($"- Type: {obj.GetType().FullName}");

                    switch (obj)
                    {
                        case GameObject go:
                            AppendGameObjectContext(go, summaryBuilder);
                            break;
                        case MonoScript script:
                            AppendScriptContext(script, summaryBuilder);
                            break;
                        case ScriptableObject so:
                            AppendScriptableObjectContext(so, summaryBuilder);
                            break;
                    }
                }
            }

            // 3. Always Append Editor Context
            summaryBuilder.AppendLine("\n## Editor Context");
            
            // Add Editor State
            var editorStateProvider = new EditorStateProvider();
            var editorState = editorStateProvider.GetContext(null, null);
            summaryBuilder.AppendLine($"- Editor State: {editorState.ToJson()}");
            
            return summaryBuilder.ToString();
        }

        private static void AppendProjectInfo(StringBuilder builder)
        {
            builder.AppendLine("## Project Info");
            builder.AppendLine($"- Project Name: {Application.productName}");
            builder.AppendLine($"- Editor Mode: {(EditorSettings.defaultBehaviorMode == EditorBehaviorMode.Mode2D ? "2D" : "3D")}");

            var rpType = "Built-in";
            if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset)
            {
                rpType = UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().Name.Replace("Asset", "");
            }
            builder.AppendLine($"- Render Pipeline: {rpType}");
        }

        private static void AppendGameObjectContext(GameObject go, StringBuilder builder)
        {
            builder.AppendLine($"- Tag: {go.tag}");
            builder.AppendLine($"- Layer: {LayerMask.LayerToName(go.layer)}");

            var prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go);
            if (!string.IsNullOrEmpty(prefabPath))
            {
                builder.AppendLine($"- Prefab Source: {prefabPath}");
            }

            builder.AppendLine("- Hierarchy (pruned to show nodes with custom scripts):");
            AppendHierarchyNode(go.transform, builder, "  ");
        }

        /// <summary>
        /// Recursively builds a string representation of the hierarchy, intelligently pruning branches
        /// that do not contain any custom user scripts to keep the context concise.
        /// </summary>
        private static void AppendHierarchyNode(Transform transform, StringBuilder builder, string indent)
        {
            // Always include the root of the hierarchy being analyzed.
            builder.AppendLine($"{indent}- {transform.name} (ID: {transform.gameObject.GetInstanceID()})");

            // List components on the current GameObject, prioritizing custom scripts.
            var components = transform.GetComponents<Component>().Where(c => c).ToList();
            var customScripts = components.Where(IsCustomScript).ToList();
            var otherComponents = components.Except(customScripts);

            foreach (var script in customScripts)
            {
                var monoScript = MonoScript.FromMonoBehaviour(script as MonoBehaviour);
                var scriptGuid = monoScript ? AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(monoScript)) : "N/A";
                builder.AppendLine($"{indent}  - Component: {script.GetType().FullName} (Custom Script, GUID: {scriptGuid})");
            }
            foreach (var component in otherComponents)
            {
                builder.AppendLine($"{indent}  - Component: {component.GetType().FullName}");
            }

            // --- Pruning Logic ---
            var childrenWithScripts = new List<Transform>();
            var childrenWithoutScripts = new List<Transform>();

            foreach (Transform child in transform)
            {
                if (HasCustomScriptInChildren(child))
                {
                    childrenWithScripts.Add(child);
                }
                else
                {
                    childrenWithoutScripts.Add(child);
                }
            }

            // Recurse into children that have scripts.
            foreach (var child in childrenWithScripts)
            {
                AppendHierarchyNode(child, builder, indent + "  ");
            }

            // Summarize the pruned children.
            if (childrenWithoutScripts.Count > 0)
            {
                builder.AppendLine($"{indent}  [+ {childrenWithoutScripts.Count} other child object(s) with no custom scripts]");
            }
        }

        private static void AppendScriptContext(MonoScript script, StringBuilder builder)
        {
            var scriptClass = script.GetClass();
            if (scriptClass != null)
            {
                if (scriptClass.BaseType != null && scriptClass.BaseType != typeof(MonoBehaviour))
                {
                    builder.AppendLine($"- Inherits from: {scriptClass.BaseType.FullName}");
                }

                var interfaces = scriptClass.GetInterfaces();
                if (interfaces.Length > 0)
                {
                    builder.AppendLine($"- Implements: {string.Join(", ", interfaces.Select(i => i.Name))}");
                }

                var methods = scriptClass.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
                    .Where(m => !m.IsSpecialName); // Exclude property getters/setters, etc.

                var methodInfos = methods as MethodInfo[] ?? methods.ToArray();
                if (methodInfos.Any())
                {
                    builder.AppendLine("- Public API:");
                    foreach (var method in methodInfos)
                    {
                        var parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
                        builder.AppendLine($"  - {method.ReturnType.Name} {method.Name}({parameters})");
                    }
                }
            }

            var scriptPath = AssetDatabase.GetAssetPath(script);
            if (!File.Exists(scriptPath)) return;

            builder.AppendLine("- Script Content:");
            builder.AppendLine("```csharp");
            builder.AppendLine(File.ReadAllText(scriptPath));
            builder.AppendLine("```");

            // Append dependency graph if available
            var guid = AssetDatabase.AssetPathToGUID(scriptPath);
            _ = GraphJsonExporter.TryAppendHumanReadableGraph(guid, builder);
        }

        private static void AppendScriptableObjectContext(ScriptableObject so, StringBuilder builder)
        {
            builder.AppendLine("- Note: This is a ScriptableObject data asset.");
            var fields = so.GetType()
                .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(field => field.IsPublic || field.GetCustomAttribute<SerializeField>() != null);

            var fieldInfos = fields as FieldInfo[] ?? fields.ToArray();
            if (!fieldInfos.Any()) return;
            
            builder.AppendLine("  - Public Fields:");
            foreach (var field in fieldInfos)
            {
                builder.AppendLine($"    - {field.Name}: {field.GetValue(so)}");
            }
        }
        
        private static string GetStableIdForObject(Object obj)
        {
            if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long _)) return obj.GetInstanceID().ToString();
            if (!string.IsNullOrEmpty(guid) && guid != "00000000000000000000000000000000")
            {
                return guid;
            }
            return obj.GetInstanceID().ToString();
        }

        /// <summary>
        /// Checks if a component is a user-defined script (i.e., part of the Assets folder).
        /// </summary>
        private static bool IsCustomScript(Component component)
        {
            if (!component || component is not MonoBehaviour monoBehaviour) return false;
            
            var script = MonoScript.FromMonoBehaviour(monoBehaviour);
            if (!script) return false;
            
            var path = AssetDatabase.GetAssetPath(script);
            return !string.IsNullOrEmpty(path) && path.StartsWith("Assets/");
        }

        /// <summary>
        /// Recursively checks if a transform or any of its descendants contain a custom script.
        /// </summary>
        private static bool HasCustomScriptInChildren(Transform transform)
        {
            if (transform.GetComponents<Component>().Any(IsCustomScript))
            {
                return true;
            }

            foreach (Transform child in transform)
            {
                if (HasCustomScriptInChildren(child))
                {
                    return true;
                }
            }

            return false;
        }
    }
}