using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;

using AIRuntime.Runtime.Capabilities;
using AIRuntime.Runtime.Capabilities.Attributes;
using AIRuntime.Runtime.Capabilities.Permissions;

using UnityEditor;
using UnityEngine;

using Object =
    UnityEngine.Object;


namespace AIRuntime.Editor
{
    [Serializable]
    public sealed class InspectEditorComponentArguments
    {
        [Description("GameObjectInstanceId from a fresh get_editor_selection result. Omit only when exactly one GameObject is selected.")]
        public int GameObjectInstanceId { get; set; }

        [Description("Optional exact ComponentDetails[].InstanceId to inspect. Omit to inspect all components on the target GameObject.")]
        public int ComponentInstanceId { get; set; }

        [Description("Optional exact component type name to inspect, for example UnityEngine.MeshCollider. Use only when ComponentInstanceId is unavailable.")]
        public string ComponentType { get; set; }

        [DefaultValue(true)]
        [Description("Include the component's visible serialized Inspector properties.")]
        public bool IncludeSerializedProperties { get; set; } =
            true;
    }


    [Serializable]
    internal sealed class EditorComponentInspectionResult
    {
        public int GameObjectInstanceId;

        public string GameObjectName;

        public string HierarchyPath;

        public string ScenePath;

        public bool ActiveSelf;

        public bool ActiveInHierarchy;

        public string Tag;

        public int Layer;

        public string LayerName;

        public bool IsStatic;

        public string PrefabInstanceStatus;

        public string PrefabAssetType;

        public List<EditorInspectedComponent> Components =
            new List<EditorInspectedComponent>();

        public List<EditorComponentDiagnostic> Diagnostics =
            new List<EditorComponentDiagnostic>();

        public string ResponseInstruction =
            "Base troubleshooting only on these inspected values. State each Error or Warning explicitly. Do not claim a component was fixed because this capability is read-only. If a safe available mutation capability can implement the user's requested fix, call it and verify its result; otherwise give options tied to the returned diagnostics.";
    }


    [Serializable]
    internal sealed class EditorInspectedComponent
    {
        public int InstanceId;

        public string Type;

        public string ScriptAssetPath;

        public bool IsMissing;

        public bool HasEnabledState;

        public bool Enabled;

        public List<EditorSerializedPropertyValue> Properties =
            new List<EditorSerializedPropertyValue>();

        public EditorColliderInspection Collider;

        public EditorRendererInspection Renderer;

        public EditorRigidbodyInspection Rigidbody;

        public List<EditorComponentDiagnostic> Diagnostics =
            new List<EditorComponentDiagnostic>();
    }


    [Serializable]
    internal sealed class EditorObjectReferenceInspection
    {
        public int InstanceId;

        public string Name;

        public string Type;

        public string AssetPath;
    }


    [Serializable]
    internal sealed class EditorRendererInspection
    {
        public bool Enabled;

        public int MaterialCount;

        public List<EditorObjectReferenceInspection> Materials =
            new List<EditorObjectReferenceInspection>();
    }


    [Serializable]
    internal sealed class EditorRigidbodyInspection
    {
        public float Mass;

        public bool UseGravity;

        public bool IsKinematic;

        public bool DetectCollisions;

        public string Interpolation;

        public string CollisionDetection;

        public int ConstraintsValue;

        public string Constraints;

        public bool FreezePositionX;

        public bool FreezePositionY;

        public bool FreezePositionZ;

        public bool FreezeRotationX;

        public bool FreezeRotationY;

        public bool FreezeRotationZ;
    }


    [Serializable]
    internal sealed class EditorSerializedPropertyValue
    {
        public string Path;

        public string DisplayName;

        public string Type;

        public string Value;

        public int ObjectInstanceId;

        public string ObjectType;

        public string AssetPath;
    }


    [Serializable]
    internal sealed class EditorColliderInspection
    {
        public bool Enabled;

        public bool IsTrigger;

        public string SharedMaterial;

        public float ContactOffset;

        public string BoundsCenter;

        public string BoundsSize;

        public bool HasAttachedRigidbody;

        public string AttachedRigidbodyType;

        public bool RigidbodyIsKinematic;

        public bool RigidbodyDetectCollisions;

        public bool RigidbodyUseGravity;

        public bool IsMeshCollider;

        public bool HasSharedMesh;

        public string SharedMesh;

        public string SharedMeshAssetPath;

        public bool SharedMeshReadable;

        public int SharedMeshVertexCount;

        public bool Convex;

        public string CookingOptions;

        public string MeshFilterMesh;

        public bool MeshMatchesMeshFilter;

        public List<string> IgnoredCollisionLayers =
            new List<string>();
    }


    [Serializable]
    internal sealed class EditorComponentDiagnostic
    {
        public string Severity;

        public string Code;

        public string Message;
    }


    /// <summary>
    /// Inspects live Unity component values for the selected GameObject.
    /// Collider-specific diagnostics are derived from authoritative Editor
    /// state so an agent does not need to guess from component type names.
    /// </summary>
    public sealed class InspectEditorComponentCapability :
        CapabilityBase<InspectEditorComponentArguments>
    {
        private const int MaximumSerializedProperties =
            256;


        public override string Name =>
            "inspect_editor_component";


        public override string Description =>
            "Inspects the real settings of one or all components on the selected Unity GameObject. Returns visible serialized Inspector properties and collider diagnostics including enabled, trigger, material, mesh, convex, Rigidbody, scale, layer-collision, and mesh-filter state. Call get_editor_selection first.";


        public override CapabilityPermission Permission =>
            CapabilityPermission.ReadOnly;


        protected override Task<object> ExecuteAsync(
            InspectEditorComponentArguments arguments,
            CapabilityContext context)
        {
            context.CancellationToken
                .ThrowIfCancellationRequested();

            InspectEditorComponentArguments request =
                arguments ??
                new InspectEditorComponentArguments();

            GameObject gameObject =
                ResolveTargetGameObject(
                    request.GameObjectInstanceId);

            Component[] components =
                ResolveComponents(
                    gameObject,
                    request.ComponentInstanceId,
                    request.ComponentType);

            EditorComponentInspectionResult result =
                CreateGameObjectResult(
                    gameObject);


            foreach (
                Component component
                in components)
            {
                context.CancellationToken
                    .ThrowIfCancellationRequested();

                EditorInspectedComponent inspected =
                    InspectComponent(
                        gameObject,
                        component,
                        request.IncludeSerializedProperties);

                result.Components.Add(
                    inspected);

                result.Diagnostics.AddRange(
                    inspected.Diagnostics);
            }


            return Task.FromResult<object>(
                result);
        }


        private static GameObject ResolveTargetGameObject(
            int requestedInstanceId)
        {
            if (requestedInstanceId !=
                0)
            {
                GameObject requested =
                    EditorUtility.InstanceIDToObject(
                        requestedInstanceId) as GameObject;

                if (requested ==
                    null)
                {
                    throw new InvalidOperationException(
                        $"No live GameObject exists with instance ID {requestedInstanceId}. Call get_editor_selection again.");
                }

                return requested;
            }

            Component activeComponent =
                Selection.activeObject as Component;

            GameObject active =
                Selection.activeGameObject ??
                (activeComponent !=
                    null
                    ? activeComponent.gameObject
                    : null);

            GameObject[] selected =
                (Selection.gameObjects ??
                 Array.Empty<GameObject>())
                .Where(
                    value =>
                        value != null)
                .Distinct()
                .ToArray();


            if (active !=
                    null &&
                selected.Length <=
                1)
            {
                return active;
            }

            if (selected.Length ==
                1)
            {
                return selected[0];
            }

            if (selected.Length ==
                0)
            {
                throw new InvalidOperationException(
                    "No GameObject is selected. Select one GameObject or provide GameObjectInstanceId from get_editor_selection.");
            }

            throw new InvalidOperationException(
                $"Exactly one GameObject must be selected when GameObjectInstanceId is omitted; currently selected: {string.Join(", ", selected.Select(value => value.name))}.");
        }


        private static Component[] ResolveComponents(
            GameObject gameObject,
            int componentInstanceId,
            string componentType)
        {
            if (componentInstanceId !=
                0)
            {
                Component component =
                    EditorUtility.InstanceIDToObject(
                        componentInstanceId) as Component;

                if (component ==
                        null ||
                    component.gameObject !=
                    gameObject)
                {
                    throw new InvalidOperationException(
                        $"No component on '{gameObject.name}' has instance ID {componentInstanceId}. Refresh get_editor_selection and use ComponentDetails[].InstanceId.");
                }

                return new[]
                {
                    component
                };
            }

            Component[] components =
                gameObject.GetComponents<Component>();

            if (string.IsNullOrWhiteSpace(
                    componentType))
            {
                return components;
            }

            string normalizedType =
                componentType.Trim();

            Component[] matches =
                components
                    .Where(
                        component =>
                            component !=
                                null &&
                            (string.Equals(
                                 component.GetType().FullName,
                                 normalizedType,
                                 StringComparison.Ordinal) ||
                             string.Equals(
                                 component.GetType().Name,
                                 normalizedType,
                                 StringComparison.Ordinal)))
                    .ToArray();


            if (matches.Length ==
                1)
            {
                return matches;
            }

            if (matches.Length >
                1)
            {
                throw new InvalidOperationException(
                    $"GameObject '{gameObject.name}' has {matches.Length} components matching '{normalizedType}'. Use ComponentDetails[].InstanceId.");
            }

            throw new InvalidOperationException(
                $"GameObject '{gameObject.name}' has no component matching '{normalizedType}'.");
        }


        private static EditorComponentInspectionResult
            CreateGameObjectResult(
                GameObject gameObject)
        {
            string scenePath =
                gameObject.scene.IsValid()
                    ? string.IsNullOrWhiteSpace(
                        gameObject.scene.path)
                        ? gameObject.scene.name
                        : NormalizePath(
                            gameObject.scene.path)
                    : string.Empty;

            return new EditorComponentInspectionResult
            {
                GameObjectInstanceId =
                    gameObject.GetInstanceID(),

                GameObjectName =
                    gameObject.name,

                HierarchyPath =
                    GetHierarchyPath(
                        gameObject),

                ScenePath =
                    scenePath,

                ActiveSelf =
                    gameObject.activeSelf,

                ActiveInHierarchy =
                    gameObject.activeInHierarchy,

                Tag =
                    gameObject.tag,

                Layer =
                    gameObject.layer,

                LayerName =
                    LayerMask.LayerToName(
                        gameObject.layer),

                IsStatic =
                    gameObject.isStatic,

                PrefabInstanceStatus =
                    PrefabUtility
                        .GetPrefabInstanceStatus(
                            gameObject)
                        .ToString(),

                PrefabAssetType =
                    PrefabUtility
                        .GetPrefabAssetType(
                            gameObject)
                        .ToString()
            };
        }


        private static EditorInspectedComponent InspectComponent(
            GameObject gameObject,
            Component component,
            bool includeSerializedProperties)
        {
            if (component ==
                null)
            {
                EditorInspectedComponent missing =
                    new EditorInspectedComponent
                    {
                        InstanceId =
                            0,

                        Type =
                            "[Missing Script]",

                        IsMissing =
                            true
                    };

                AddDiagnostic(
                    missing.Diagnostics,
                    "Error",
                    "MissingScript",
                    $"'{gameObject.name}' contains a missing script component.");

                return missing;
            }

            EditorInspectedComponent inspected =
                new EditorInspectedComponent
                {
                    InstanceId =
                        component.GetInstanceID(),

                    Type =
                        component.GetType().FullName
                };

            MonoBehaviour monoBehaviour =
                component as MonoBehaviour;

            if (monoBehaviour !=
                null)
            {
                MonoScript script =
                    MonoScript.FromMonoBehaviour(
                        monoBehaviour);

                inspected.ScriptAssetPath =
                    script == null
                        ? null
                        : AssetDatabase.GetAssetPath(
                            script);
            }

            if (TryGetEnabled(
                    component,
                    out bool enabled))
            {
                inspected.HasEnabledState =
                    true;

                inspected.Enabled =
                    enabled;

                if (!enabled)
                {
                    AddDiagnostic(
                        inspected.Diagnostics,
                        "Error",
                        "ComponentDisabled",
                        $"Component '{inspected.Type}' is disabled.");
                }
            }

            if (includeSerializedProperties)
            {
                inspected.Properties =
                    ReadSerializedProperties(
                        component);
            }

            Collider collider =
                component as Collider;

            if (collider !=
                null)
            {
                inspected.Collider =
                    InspectCollider(
                        gameObject,
                        collider,
                    inspected.Diagnostics);
            }

            Renderer renderer =
                component as Renderer;

            if (renderer !=
                null)
            {
                inspected.Renderer =
                    InspectRenderer(
                        renderer);
            }

            Rigidbody rigidbody =
                component as Rigidbody;

            if (rigidbody !=
                null)
            {
                inspected.Rigidbody =
                    InspectRigidbody(
                        rigidbody);
            }

            return inspected;
        }


        private static List<EditorSerializedPropertyValue>
            ReadSerializedProperties(
                Component component)
        {
            List<EditorSerializedPropertyValue> values =
                new List<EditorSerializedPropertyValue>();

            SerializedObject serializedObject =
                new SerializedObject(
                    component);

            SerializedProperty property =
                serializedObject.GetIterator();

            bool enterChildren =
                true;


            while (values.Count <
                       MaximumSerializedProperties &&
                   property.NextVisible(
                       enterChildren))
            {
                enterChildren =
                    false;

                values.Add(
                    DescribeProperty(
                        property));
            }

            Renderer renderer =
                component as Renderer;

            if (renderer !=
                null)
            {
                SerializedProperty materials =
                    serializedObject.FindProperty(
                        "m_Materials");

                if (materials !=
                    null)
                {
                    for (
                        int index = 0;
                        index < materials.arraySize &&
                        values.Count < MaximumSerializedProperties;
                        index++)
                    {
                        AddPropertyIfMissing(
                            values,
                            materials.GetArrayElementAtIndex(
                                index));
                    }
                }
            }

            if (component is Rigidbody)
            {
                AddPropertyIfMissing(
                    values,
                    serializedObject.FindProperty(
                        "m_Constraints"));
            }


            return values;
        }


        private static void AddPropertyIfMissing(
            ICollection<EditorSerializedPropertyValue> values,
            SerializedProperty property)
        {
            if (property ==
                    null ||
                values.Any(
                    value =>
                        string.Equals(
                            value.Path,
                            property.propertyPath,
                            StringComparison.Ordinal)))
            {
                return;
            }

            values.Add(
                DescribeProperty(
                    property));
        }


        private static EditorRendererInspection InspectRenderer(
            Renderer renderer)
        {
            EditorRendererInspection result =
                new EditorRendererInspection
                {
                    Enabled =
                        renderer.enabled
                };

            Material[] materials =
                renderer.sharedMaterials ??
                Array.Empty<Material>();

            result.MaterialCount =
                materials.Length;

            foreach (Material material in materials)
            {
                result.Materials.Add(
                    material ==
                        null
                        ? new EditorObjectReferenceInspection
                        {
                            Name = "null"
                        }
                        : new EditorObjectReferenceInspection
                        {
                            InstanceId =
                                material.GetInstanceID(),

                            Name =
                                material.name,

                            Type =
                                material.GetType().FullName,

                            AssetPath =
                                NormalizePath(
                                    AssetDatabase.GetAssetPath(
                                        material))
                        });
            }

            return result;
        }


        private static EditorRigidbodyInspection InspectRigidbody(
            Rigidbody rigidbody)
        {
            RigidbodyConstraints constraints =
                rigidbody.constraints;

            return new EditorRigidbodyInspection
            {
                Mass =
                    rigidbody.mass,

                UseGravity =
                    rigidbody.useGravity,

                IsKinematic =
                    rigidbody.isKinematic,

                DetectCollisions =
                    rigidbody.detectCollisions,

                Interpolation =
                    rigidbody.interpolation.ToString(),

                CollisionDetection =
                    rigidbody.collisionDetectionMode.ToString(),

                ConstraintsValue =
                    (int)constraints,

                Constraints =
                    constraints.ToString(),

                FreezePositionX =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezePositionX),

                FreezePositionY =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezePositionY),

                FreezePositionZ =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezePositionZ),

                FreezeRotationX =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezeRotationX),

                FreezeRotationY =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezeRotationY),

                FreezeRotationZ =
                    HasConstraint(
                        constraints,
                        RigidbodyConstraints.FreezeRotationZ)
            };
        }


        private static bool HasConstraint(
            RigidbodyConstraints value,
            RigidbodyConstraints constraint)
        {
            return (value & constraint) ==
                   constraint;
        }


        private static EditorSerializedPropertyValue DescribeProperty(
            SerializedProperty property)
        {
            EditorSerializedPropertyValue result =
                new EditorSerializedPropertyValue
                {
                    Path =
                        property.propertyPath,

                    DisplayName =
                        property.displayName,

                    Type =
                        property.propertyType.ToString()
                };

            Object objectReference =
                property.propertyType ==
                SerializedPropertyType.ObjectReference
                    ? property.objectReferenceValue
                    : null;

            result.Value =
                GetSerializedValue(
                    property,
                    objectReference);


            if (objectReference !=
                null)
            {
                result.ObjectInstanceId =
                    objectReference.GetInstanceID();

                result.ObjectType =
                    objectReference
                        .GetType()
                        .FullName;

                result.AssetPath =
                    NormalizePath(
                        AssetDatabase.GetAssetPath(
                            objectReference));
            }


            return result;
        }


        private static string GetSerializedValue(
            SerializedProperty property,
            Object objectReference)
        {
            switch (property.propertyType)
            {
                case SerializedPropertyType.Integer:
                    return property.longValue.ToString(
                        CultureInfo.InvariantCulture);

                case SerializedPropertyType.Boolean:
                    return property.boolValue.ToString();

                case SerializedPropertyType.Float:
                    return property.doubleValue.ToString(
                        "R",
                        CultureInfo.InvariantCulture);

                case SerializedPropertyType.String:
                    return property.stringValue ??
                           string.Empty;

                case SerializedPropertyType.Color:
                    return property.colorValue.ToString();

                case SerializedPropertyType.ObjectReference:
                    return objectReference !=
                           null
                        ? objectReference.name
                        : "null";

                case SerializedPropertyType.LayerMask:
                    return property.intValue.ToString(
                        CultureInfo.InvariantCulture);

                case SerializedPropertyType.Enum:
                    return property.enumDisplayNames.Length >
                           property.enumValueIndex &&
                           property.enumValueIndex >=
                           0
                        ? property.enumDisplayNames[
                            property.enumValueIndex]
                        : property.enumValueIndex.ToString(
                            CultureInfo.InvariantCulture);

                case SerializedPropertyType.Vector2:
                    return property.vector2Value.ToString();

                case SerializedPropertyType.Vector3:
                    return property.vector3Value.ToString();

                case SerializedPropertyType.Vector4:
                    return property.vector4Value.ToString();

                case SerializedPropertyType.Rect:
                    return property.rectValue.ToString();

                case SerializedPropertyType.ArraySize:
                    return property.intValue.ToString(
                        CultureInfo.InvariantCulture);

                case SerializedPropertyType.Character:
                    return ((char)property.intValue).ToString();

                case SerializedPropertyType.Bounds:
                    return property.boundsValue.ToString();

                case SerializedPropertyType.Quaternion:
                    return property.quaternionValue.ToString();

                case SerializedPropertyType.Vector2Int:
                    return property.vector2IntValue.ToString();

                case SerializedPropertyType.Vector3Int:
                    return property.vector3IntValue.ToString();

                case SerializedPropertyType.RectInt:
                    return property.rectIntValue.ToString();

                case SerializedPropertyType.BoundsInt:
                    return property.boundsIntValue.ToString();

                case SerializedPropertyType.AnimationCurve:
                    return property.animationCurveValue !=
                           null
                        ? $"AnimationCurve({property.animationCurveValue.length} keys)"
                        : "null";

                default:
                    return property.isArray
                        ? $"Array({property.arraySize})"
                        : property.type;
            }
        }


        private static EditorColliderInspection InspectCollider(
            GameObject gameObject,
            Collider collider,
            ICollection<EditorComponentDiagnostic> diagnostics)
        {
            Rigidbody rigidbody =
                collider.attachedRigidbody;

            EditorColliderInspection result =
                new EditorColliderInspection
                {
                    Enabled =
                        collider.enabled,

                    IsTrigger =
                        collider.isTrigger,

                    SharedMaterial =
                        collider.sharedMaterial !=
                        null
                            ? collider.sharedMaterial.name
                            : string.Empty,

                    ContactOffset =
                        collider.contactOffset,

                    BoundsCenter =
                        collider.bounds.center.ToString(),

                    BoundsSize =
                        collider.bounds.size.ToString(),

                    HasAttachedRigidbody =
                        rigidbody !=
                        null,

                    AttachedRigidbodyType =
                        rigidbody !=
                        null
                            ? rigidbody
                                .GetType()
                                .FullName
                            : string.Empty,

                    RigidbodyIsKinematic =
                        rigidbody !=
                            null &&
                        rigidbody.isKinematic,

                    RigidbodyDetectCollisions =
                        rigidbody ==
                            null ||
                        rigidbody.detectCollisions,

                    RigidbodyUseGravity =
                        rigidbody !=
                            null &&
                        rigidbody.useGravity,

                    IgnoredCollisionLayers =
                        GetIgnoredCollisionLayers(
                            gameObject.layer)
                };


            if (!gameObject.activeInHierarchy)
            {
                AddDiagnostic(
                    diagnostics,
                    "Error",
                    "GameObjectInactive",
                    $"GameObject '{gameObject.name}' is inactive in the hierarchy, so its collider does not participate in physics.");
            }

            Vector3 scale =
                gameObject.transform.lossyScale;

            if (Mathf.Approximately(
                    scale.x,
                    0) ||
                Mathf.Approximately(
                    scale.y,
                    0) ||
                Mathf.Approximately(
                    scale.z,
                    0))
            {
                AddDiagnostic(
                    diagnostics,
                    "Error",
                    "ZeroScale",
                    $"Lossy scale {scale} contains a zero axis, which collapses the collider.");
            }
            else if (scale.x *
                     scale.y *
                     scale.z <
                     0)
            {
                AddDiagnostic(
                    diagnostics,
                    "Warning",
                    "NegativeScale",
                    $"Lossy scale {scale} has a negative determinant and may produce unexpected MeshCollider behavior.");
            }

            if (rigidbody ==
                null)
            {
                AddDiagnostic(
                    diagnostics,
                    "Info",
                    "NoAttachedRigidbody",
                    "No Rigidbody is attached. A static collider can still block physics, but collision or trigger callbacks require a Rigidbody on at least one of the two interacting objects.");
            }
            else if (!rigidbody.detectCollisions)
            {
                AddDiagnostic(
                    diagnostics,
                    "Error",
                    "RigidbodyCollisionDisabled",
                    "The attached Rigidbody has Detect Collisions disabled.");
            }

            MeshCollider meshCollider =
                collider as MeshCollider;

            if (meshCollider ==
                null)
            {
                return result;
            }

            Mesh mesh =
                meshCollider.sharedMesh;

            MeshFilter meshFilter =
                gameObject.GetComponent<MeshFilter>();

            Mesh meshFilterMesh =
                meshFilter !=
                    null
                    ? meshFilter.sharedMesh
                    : null;

            result.IsMeshCollider =
                true;

            result.HasSharedMesh =
                mesh !=
                null;

            result.SharedMesh =
                mesh !=
                    null
                    ? mesh.name
                    : string.Empty;

            result.SharedMeshAssetPath =
                mesh !=
                    null
                    ? NormalizePath(
                        AssetDatabase.GetAssetPath(
                            mesh))
                    : string.Empty;

            result.SharedMeshReadable =
                mesh !=
                    null &&
                mesh.isReadable;

            result.SharedMeshVertexCount =
                mesh !=
                    null
                    ? mesh.vertexCount
                    : 0;

            result.Convex =
                meshCollider.convex;

            result.CookingOptions =
                meshCollider.cookingOptions.ToString();

            result.MeshFilterMesh =
                meshFilterMesh !=
                    null
                    ? meshFilterMesh.name
                    : string.Empty;

            result.MeshMatchesMeshFilter =
                mesh !=
                    null &&
                meshFilterMesh ==
                mesh;


            if (mesh ==
                null)
            {
                AddDiagnostic(
                    diagnostics,
                    "Error",
                    "MeshColliderMissingMesh",
                    "MeshCollider has no Shared Mesh assigned and therefore has no collision shape.");
            }

            if (meshFilterMesh !=
                    null &&
                mesh !=
                    meshFilterMesh)
            {
                AddDiagnostic(
                    diagnostics,
                    "Warning",
                    "ColliderMeshMismatch",
                    $"MeshCollider uses '{result.SharedMesh}', while MeshFilter uses '{result.MeshFilterMesh}'.");
            }

            if (rigidbody !=
                    null &&
                !rigidbody.isKinematic &&
                !meshCollider.convex)
            {
                AddDiagnostic(
                    diagnostics,
                    "Error",
                    "DynamicMeshColliderNotConvex",
                    "A non-kinematic Rigidbody cannot use a non-convex MeshCollider. Enable Convex, make the Rigidbody kinematic, or replace it with primitive/compound colliders.");
            }

            return result;
        }


        private static List<string> GetIgnoredCollisionLayers(
            int layer)
        {
            List<string> ignored =
                new List<string>();


            for (int otherLayer = 0;
                 otherLayer <
                 32;
                 otherLayer++)
            {
                if (!Physics.GetIgnoreLayerCollision(
                        layer,
                        otherLayer))
                {
                    continue;
                }

                string name =
                    LayerMask.LayerToName(
                        otherLayer);

                ignored.Add(
                    string.IsNullOrWhiteSpace(
                        name)
                        ? otherLayer.ToString(
                            CultureInfo.InvariantCulture)
                        : $"{otherLayer}:{name}");
            }


            return ignored;
        }


        private static bool TryGetEnabled(
            Component component,
            out bool enabled)
        {
            Behaviour behaviour =
                component as Behaviour;

            if (behaviour !=
                null)
            {
                enabled =
                    behaviour.enabled;

                return true;
            }

            Collider collider =
                component as Collider;

            if (collider !=
                null)
            {
                enabled =
                    collider.enabled;

                return true;
            }

            Renderer renderer =
                component as Renderer;

            if (renderer !=
                null)
            {
                enabled =
                    renderer.enabled;

                return true;
            }

            enabled =
                true;

            return false;
        }


        private static void AddDiagnostic(
            ICollection<EditorComponentDiagnostic> diagnostics,
            string severity,
            string code,
            string message)
        {
            diagnostics.Add(
                new EditorComponentDiagnostic
                {
                    Severity =
                        severity,

                    Code =
                        code,

                    Message =
                        message
                });
        }


        private static string GetHierarchyPath(
            GameObject gameObject)
        {
            Stack<string> names =
                new Stack<string>();

            Transform current =
                gameObject.transform;


            while (current !=
                   null)
            {
                names.Push(
                    current.name);

                current =
                    current.parent;
            }


            return string.Join(
                "/",
                names);
        }


        private static string NormalizePath(
            string path)
        {
            return string.IsNullOrWhiteSpace(
                path)
                ? string.Empty
                : path.Replace(
                    '\\',
                    '/');
        }
    }
}
