using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_2017_1_OR_NEWER
using UnityEngine.U2D;
using UnityEngine.Playables;
#endif
#if UNITY_2018_2_OR_NEWER
using UnityEditor.U2D;
#endif
#if UNITY_2017_3_OR_NEWER
using UnityEditor.Compilation;
#endif
#if UNITY_2017_2_OR_NEWER
using UnityEngine.Tilemaps;
#endif
#if ASSET_USAGE_ADDRESSABLES
using UnityEngine.AddressableAssets;
#endif
using Object = UnityEngine.Object;

namespace AssetUsageDetectorNamespace
{
	public partial class AssetUsageDetector
	{
		#region Helper Classes
#if UNITY_2017_3_OR_NEWER
#pragma warning disable 0649 // The fields' values are assigned via JsonUtility
		[Serializable]
		private struct AssemblyDefinitionReferences
		{
			public string reference; // Used by AssemblyDefinitionReferenceAssets
			public List<string> references; // Used by AssemblyDefinitionAssets
		}
#pragma warning restore 0649
#endif

#if UNITY_2018_1_OR_NEWER
#pragma warning disable 0649 // The fields' values are assigned via JsonUtility
		[Serializable]
		private struct ShaderGraphReferences // Used by old Shader Graph serialization format
		{
			[Serializable]
			public struct JSONHolder
			{
				public string JSONnodeData;
			}

			[Serializable]
			public class TextureHolder
			{
				public string m_SerializedTexture;
				public string m_SerializedCubemap;
				public string m_Guid;

				public string GetTexturePath()
				{
					string guid = ExtractGUIDFromString( !string.IsNullOrEmpty( m_SerializedTexture ) ? m_SerializedTexture : m_SerializedCubemap );
					if( string.IsNullOrEmpty( guid ) )
						guid = m_Guid;

					return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid );
				}
			}

			[Serializable]
			public struct PropertyData
			{
				public string m_Name;
				public string m_DefaultReferenceName;
				public string m_OverrideReferenceName;
				public TextureHolder m_Value;

				public string GetName()
				{
					if( !string.IsNullOrEmpty( m_OverrideReferenceName ) )
						return m_OverrideReferenceName;
					if( !string.IsNullOrEmpty( m_DefaultReferenceName ) )
						return m_DefaultReferenceName;
					if( !string.IsNullOrEmpty( m_Name ) )
						return m_Name;

					return "Property";
				}
			}

			[Serializable]
			public struct NodeData
			{
				public string m_Name;
				public string m_FunctionSource; // Custom Function node's Source field
				public string m_SerializedSubGraph; // Sub-graph node
				public List<JSONHolder> m_SerializableSlots;

				public string GetSubGraphPath()
				{
					string guid = ExtractGUIDFromString( m_SerializedSubGraph );
					return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid );
				}
			}

			[Serializable]
			public struct NodeSlotData
			{
				public TextureHolder m_Texture;
				public TextureHolder m_TextureArray;
				public TextureHolder m_Cubemap;

				public string GetTexturePath()
				{
					if( m_Texture != null )
						return m_Texture.GetTexturePath();
					if( m_Cubemap != null )
						return m_Cubemap.GetTexturePath();
					if( m_TextureArray != null )
						return m_TextureArray.GetTexturePath();

					return null;
				}
			}

			public List<JSONHolder> m_SerializedProperties;
			public List<JSONHolder> m_SerializableNodes;

			// String can be in one of the following formats:
			// "guid":"GUID_VALUE"
			// "guid": "GUID_VALUE"
			// "guid" : "GUID_VALUE"
			private static string ExtractGUIDFromString( string str )
			{
				if( !string.IsNullOrEmpty( str ) )
				{
					int guidStartIndex = str.IndexOf( "\"guid\"" );
					if( guidStartIndex >= 0 )
					{
						guidStartIndex += 6;
						guidStartIndex = str.IndexOf( '"', guidStartIndex );
						if( guidStartIndex > 0 )
						{
							guidStartIndex++;

							int guidEndIndex = str.IndexOf( '"', guidStartIndex );
							if( guidEndIndex > 0 )
								return str.Substring( guidStartIndex, guidEndIndex - guidStartIndex );
						}
					}
				}

				return null;
			}
		}
#pragma warning restore 0649
#endif
		#endregion

		// Dictionary to quickly find the function to search a specific type with
		private Dictionary<Type, Func<object, ReferenceNode>> typeToSearchFunction;
		// Dictionary to associate special file extensions with their search functions
		private Dictionary<string, Func<object, ReferenceNode>> extensionToSearchFunction;

		// An optimization to fetch & filter fields and properties of a class only once
		private readonly Dictionary<Type, VariableGetterHolder[]> typeToVariables = new Dictionary<Type, VariableGetterHolder[]>( 4096 );
		private readonly List<VariableGetterHolder> validVariables = new List<VariableGetterHolder>( 32 );

		// All MonoScripts in objectsToSearchSet
		private readonly List<MonoScript> monoScriptsToSearch = new List<MonoScript>();
		private readonly List<Type> monoScriptsToSearchTypes = new List<Type>();

		// Path(s) of .cginc, .cg, .hlsl and .glslinc assets in assetsToSearchSet
		private readonly HashSet<string> shaderIncludesToSearchSet = new HashSet<string>();

#if UNITY_2017_3_OR_NEWER
		// Path(s) of the Assembly Definition Files in objectsToSearchSet (Value: files themselves)
		private readonly Dictionary<string, Object> assemblyDefinitionFilesToSearch = new Dictionary<string, Object>( 8 );
#endif

		// An optimization to fetch an animation clip's curve bindings only once
		private readonly Dictionary<AnimationClip, EditorCurveBinding[]> animationClipUniqueBindings = new Dictionary<AnimationClip, EditorCurveBinding[]>( 256 );

		private bool searchPrefabConnections;
		private bool searchMonoBehavioursForScript;
		private bool searchTextureReferences;
#if UNITY_2018_1_OR_NEWER
		private bool searchShaderGraphsForSubGraphs;
#endif

		private bool searchSerializableVariablesOnly;
		private bool prevSearchSerializableVariablesOnly;

		private BindingFlags fieldModifiers, propertyModifiers;
		private BindingFlags prevFieldModifiers, prevPropertyModifiers;

		// Unity's internal function that returns a SerializedProperty's corresponding FieldInfo
		private delegate FieldInfo FieldInfoGetter( SerializedProperty p, out Type t );
#if UNITY_2019_3_OR_NEWER
		private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#else
		private readonly FieldInfoGetter fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#endif

		private readonly Func<Object> lightmapSettingsGetter = (Func<Object>) Delegate.CreateDelegate( typeof( Func<Object> ), typeof( LightmapEditorSettings ).GetMethod( "GetLightmapSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
		private readonly Func<Object> renderSettingsGetter = (Func<Object>) Delegate.CreateDelegate( typeof( Func<Object> ), typeof( RenderSettings ).GetMethod( "GetRenderSettings", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
#if UNITY_2021_2_OR_NEWER
		private readonly Func<Cubemap> defaultReflectionProbeGetter = (Func<Cubemap>) Delegate.CreateDelegate( typeof( Func<Cubemap> ), typeof( RenderSettings ).GetProperty( "defaultReflection", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ).GetGetMethod( true ) );
#endif

#if ASSET_USAGE_ADDRESSABLES
		private readonly Func<SpriteAtlas, Sprite[]> spriteAtlasPackedSpritesGetter = (Func<SpriteAtlas, Sprite[]>) Delegate.CreateDelegate( typeof( Func<SpriteAtlas, Sprite[]> ), typeof( SpriteAtlasExtensions ).GetMethod( "GetPackedSprites", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
		private readonly PropertyInfo assetReferenceSubObjectTypeGetter = typeof( AssetReference ).GetProperty( "SubOjbectType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
#endif

#if ASSET_USAGE_VFX_GRAPH
		private static Type vfxResourceType => typeof( Editor ).Assembly.GetType( "UnityEditor.VFX.VisualEffectResource" ) ?? Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "UnityEditor.VFXModule" ).GetType( "UnityEditor.VFX.VisualEffectResource" );
		private readonly Func<string, object> vfxResourceGetter = (Func<string, object>) Delegate.CreateDelegate( typeof( Func<string, object> ), vfxResourceType.GetMethod( "GetResourceAtPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ) );
		private readonly MethodInfo vfxResourceContentsGetter = vfxResourceType.GetMethod( "GetContents", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
		private readonly MethodInfo vfxSerializableObjectValueGetter = Array.Find( Array.Find( AppDomain.CurrentDomain.GetAssemblies(), ( assembly ) => assembly.GetName().Name == "Unity.VisualEffectGraph.Editor" ).GetType( "UnityEditor.VFX.VFXSerializableObject" ).GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ), ( methodInfo ) => methodInfo.Name == "Get" && !methodInfo.IsGenericMethod );
#endif

		private void InitializeSearchFunctionsData( Parameters searchParameters )
		{
			if( typeToSearchFunction == null )
			{
				typeToSearchFunction = new Dictionary<Type, Func<object, ReferenceNode>>()
				{
					{ typeof( GameObject ), SearchGameObject },
					{ typeof( Material ), SearchMaterial },
					{ typeof( Shader ), SearchShader },
					{ typeof( MonoScript ), SearchMonoScript },
					{ typeof( RuntimeAnimatorController ), SearchAnimatorController },
					{ typeof( AnimatorOverrideController ), SearchAnimatorController },
					{ typeof( AnimatorController ), SearchAnimatorController },
					{ typeof( AnimatorStateMachine ), SearchAnimatorStateMachine },
					{ typeof( AnimatorState ), SearchAnimatorState },
					{ typeof( AnimatorStateTransition ), SearchAnimatorStateTransition },
					{ typeof( BlendTree ), SearchBlendTree },
					{ typeof( AnimationClip ), SearchAnimationClip },
					{ typeof( TerrainData ), SearchTerrainData },
					{ typeof( LightmapSettings ), SearchLightmapSettings },
					{ typeof( RenderSettings ), SearchRenderSettings },
#if UNITY_2017_1_OR_NEWER
					{ typeof( SpriteAtlas ), SearchSpriteAtlas },
#endif
				};
			}

			if( extensionToSearchFunction == null )
			{
				extensionToSearchFunction = new Dictionary<string, Func<object, ReferenceNode>>()
				{
					{ "compute", SearchShaderSecondaryAsset },
					{ "cginc", SearchShaderSecondaryAsset },
					{ "cg", SearchShaderSecondaryAsset },
					{ "glslinc", SearchShaderSecondaryAsset },
					{ "hlsl", SearchShaderSecondaryAsset },
#if UNITY_2017_3_OR_NEWER
					{ "asmdef", SearchAssemblyDefinitionFile },
#endif
#if UNITY_2019_2_OR_NEWER
					{ "asmref", SearchAssemblyDefinitionFile },
#endif
#if UNITY_2018_1_OR_NEWER
					{ "shadergraph", SearchShaderGraph },
					{ "shadersubgraph", SearchShaderGraph },
#endif
#if ASSET_USAGE_VFX_GRAPH
					{ "vfx", SearchVFXGraphAsset },
					{ "vfxoperator", SearchVFXGraphAsset },
					{ "vfxblock", SearchVFXGraphAsset },
#endif
				};
			}

			fieldModifiers = searchParameters.fieldModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
			propertyModifiers = searchParameters.propertyModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly;
			searchSerializableVariablesOnly = !searchParameters.searchNonSerializableVariables;

			if( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly )
				typeToVariables.Clear();

			prevFieldModifiers = fieldModifiers;
			prevPropertyModifiers = propertyModifiers;
			prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly;

			searchPrefabConnections = false;
			searchMonoBehavioursForScript = false;
			searchTextureReferences = false;
#if UNITY_2018_1_OR_NEWER
			searchShaderGraphsForSubGraphs = false;
#endif
#if ASSET_USAGE_VFX_GRAPH
			bool searchVFXGraphs = false;
#endif

			foreach( Object obj in objectsToSearchSet )
			{
				if( obj is Texture || obj is Sprite )
					searchTextureReferences = true;
				else if( obj is MonoScript )
				{
					searchMonoBehavioursForScript = true;

					Type monoScriptType = ( (MonoScript) obj ).GetClass();
					if( monoScriptType != null && !monoScriptType.IsSealed )
					{
						monoScriptsToSearch.Add( (MonoScript) obj );
						monoScriptsToSearchTypes.Add( monoScriptType );
					}
				}
				else if( obj is GameObject )
					searchPrefabConnections = true;
#if UNITY_2017_3_OR_NEWER
				else if( obj is UnityEditorInternal.AssemblyDefinitionAsset )
					assemblyDefinitionFilesToSearch[AssetDatabase.GetAssetPath( obj )] = obj;
#endif
#if ASSET_USAGE_VFX_GRAPH
				else if( !searchVFXGraphs && ( obj is Shader || obj is Mesh || obj.GetType().Name.StartsWithFast( "PointCache" ) || obj.GetType().Name == "ShaderGraphVfxAsset" ) )
					searchVFXGraphs = true;
#endif
			}

			// We need to search for class/interface inheritance references manually because AssetDatabase.GetDependencies doesn't take that into account
			if( monoScriptsToSearch.Count > 0 )
			{
				alwaysSearchedExtensionsSet.Add( "cs" );
				alwaysSearchedExtensionsSet.Add( "dll" );
			}

			foreach( string path in assetsToSearchPathsSet )
			{
				string extension = Utilities.GetFileExtension( path );
				if( extension == "hlsl" || extension == "cginc" || extension == "cg" || extension == "glslinc" )
					shaderIncludesToSearchSet.Add( path );
#if UNITY_2018_1_OR_NEWER
				else if( extension == "shadersubgraph" )
					searchShaderGraphsForSubGraphs = true;
#endif
			}

			// AssetDatabase.GetDependencies doesn't take #include lines in shader source codes into consideration. If we are searching for references
			// of a potential #include target (shaderIncludesToSearchSet), we must search all shader assets and check their #include lines manually
			if( shaderIncludesToSearchSet.Count > 0 )
			{
				alwaysSearchedExtensionsSet.Add( "shader" );
				alwaysSearchedExtensionsSet.Add( "compute" );
				alwaysSearchedExtensionsSet.Add( "cginc" );
				alwaysSearchedExtensionsSet.Add( "cg" );
				alwaysSearchedExtensionsSet.Add( "glslinc" );
				alwaysSearchedExtensionsSet.Add( "hlsl" );
			}

#if UNITY_2017_3_OR_NEWER
			// AssetDatabase.GetDependencies doesn't return references from Assembly Definition Files to their Assembly Definition References,
			// so if we are searching for an Assembly Definition File's usages, we must search all Assembly Definition Files' references manually.
			if( assemblyDefinitionFilesToSearch.Count > 0 )
			{
				alwaysSearchedExtensionsSet.Add( "asmdef" );
#if UNITY_2019_2_OR_NEWER
				alwaysSearchedExtensionsSet.Add( "asmref" );
#endif
			}
#endif

#if UNITY_2018_1_OR_NEWER
			// AssetDatabase.GetDependencies doesn't work with Shader Graph assets. We must search all Shader Graph assets in the following cases:
			// searchTextureReferences: to find Texture references used in various nodes and properties
			// searchShaderGraphsForSubGraphs: to find Shader Sub-graph references in other Shader Graph assets
			// shaderIncludesToSearchSet: to find .cginc, .cg, .glslinc and .hlsl references used in Custom Function nodes
			if( searchTextureReferences || searchShaderGraphsForSubGraphs || shaderIncludesToSearchSet.Count > 0 )
			{
				alwaysSearchedExtensionsSet.Add( "shadergraph" );
				alwaysSearchedExtensionsSet.Add( "shadersubgraph" );
			}
#endif

#if ASSET_USAGE_VFX_GRAPH
			if( searchTextureReferences || searchVFXGraphs )
			{
				alwaysSearchedExtensionsSet.Add( "vfx" );
				alwaysSearchedExtensionsSet.Add( "vfxoperator" );
				alwaysSearchedExtensionsSet.Add( "vfxblock" );
			}
#endif
		}

		private ReferenceNode SearchGameObject( object obj )
		{
			GameObject go = (GameObject) obj;
			ReferenceNode referenceNode = PopReferenceNode( go );

			// Check if this GameObject's prefab is one of the selected assets
			if( searchPrefabConnections )
			{
#if UNITY_2018_3_OR_NEWER
				Object prefab = go;
				while( prefab = PrefabUtility.GetCorrespondingObjectFromSource( prefab ) )
#else
				Object prefab = PrefabUtility.GetPrefabParent( go );
				if( prefab )
#endif
				{
					if( objectsToSearchSet.Contains( prefab ) && assetsToSearchRootPrefabs.ContainsFast( prefab as GameObject ) )
					{
						referenceNode.AddLinkTo( GetReferenceNode( prefab ), "Prefab object" );

						if( searchParameters.searchRefactoring != null )
							searchParameters.searchRefactoring( new PrefabMatch( go, prefab ) );
					}
				}
			}

			// Search through all the components of the object
			Component[] components = go.GetComponents<Component>();
			for( int i = 0; i < components.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( components[i] ), isWeakLink: true );

			return referenceNode;
		}

		private ReferenceNode SearchComponent( object obj )
		{
			Component component = (Component) obj;

			// Ignore Transform component (no object field to search for)
			if( component is Transform )
				return null;

			ReferenceNode referenceNode = PopReferenceNode( component );

			if( searchMonoBehavioursForScript && component is MonoBehaviour )
			{
				// If a searched asset is script, check if this component is an instance of it
				// Although SearchVariablesWithSerializedObject can detect these references with SerializedObject, it isn't possible when reflection is used in Play mode
				MonoScript script = MonoScript.FromMonoBehaviour( (MonoBehaviour) component );
				if( objectsToSearchSet.Contains( script ) )
				{
					referenceNode.AddLinkTo( GetReferenceNode( script ) );

					if( searchParameters.searchRefactoring != null )
						searchParameters.searchRefactoring( new BehaviourUsageMatch( component.gameObject, script, component ) );
				}
			}

			if( component is Animation )
			{
				// Search animation clips for references
				if( searchParameters.searchRefactoring == null )
				{
					foreach( AnimationState anim in (Animation) component )
						referenceNode.AddLinkTo( SearchObject( anim.clip ) );
				}
				else
				{
					AnimationClip[] clips = AnimationUtility.GetAnimationClips( component.gameObject );
					bool modifiedClips = false;
					for( int i = 0; i < clips.Length; i++ )
					{
						referenceNode.AddLinkTo( SearchObject( clips[i] ) );

						if( objectsToSearchSet.Contains( clips[i] ) )
						{
							searchParameters.searchRefactoring( new AnimationSystemMatch( component, clips[i], ( newValue ) =>
							{
								clips[i] = (AnimationClip) newValue;
								modifiedClips = true;
							} ) );
						}
					}

					if( modifiedClips )
						AnimationUtility.SetAnimationClips( (Animation) component, clips );
				}

				// Search the objects that are animated by this Animation component for references
				SearchAnimatedObjects( referenceNode );
			}
			else if( component is Animator )
			{
				// Search animation clips for references (via AnimatorController)
				RuntimeAnimatorController animatorController = ( (Animator) component ).runtimeAnimatorController;
				referenceNode.AddLinkTo( SearchObject( animatorController ) );

				if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( animatorController ) )
					searchParameters.searchRefactoring( new AnimationSystemMatch( component, animatorController, ( newValue ) => ( (Animator) component ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) );

				// Search the objects that are animated by this Animator component for references
				SearchAnimatedObjects( referenceNode );
			}
#if UNITY_2017_2_OR_NEWER
			else if( component is Tilemap )
			{
				// Search the tiles for references
				TileBase[] tiles = new TileBase[( (Tilemap) component ).GetUsedTilesCount()];
				( (Tilemap) component ).GetUsedTilesNonAlloc( tiles );

				if( tiles != null )
				{
					for( int i = 0; i < tiles.Length; i++ )
					{
						referenceNode.AddLinkTo( SearchObject( tiles[i] ), "Tile" );

						if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( tiles[i] ) )
							searchParameters.searchRefactoring( new OtherSearchMatch( component, tiles[i], ( newValue ) => ( (Tilemap) component ).SwapTile( tiles[i], (TileBase) newValue ) ) );
					}
				}
			}
#endif
#if UNITY_2017_1_OR_NEWER
			else if( component is PlayableDirector )
			{
				// Search the PlayableAsset's scene bindings for references
				PlayableAsset playableAsset = ( (PlayableDirector) component ).playableAsset;
				if( playableAsset != null && !playableAsset.Equals( null ) )
				{
					foreach( PlayableBinding binding in playableAsset.outputs )
					{
						Object bindingValue = ( (PlayableDirector) component ).GetGenericBinding( binding.sourceObject );
						referenceNode.AddLinkTo( SearchObject( bindingValue ), "Binding: " + binding.streamName );

						if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( bindingValue ) )
							searchParameters.searchRefactoring( new AnimationSystemMatch( component, bindingValue, ( newValue ) => ( (PlayableDirector) component ).SetGenericBinding( binding.sourceObject, newValue ) ) );
					}
				}
			}
#endif
			else if( component is ParticleSystemRenderer )
			{
				// Search ParticleSystemRenderer's custom meshes for references (at runtime, they can't be searched with reflection, unfortunately)
				if( isInPlayMode && !AssetDatabase.Contains( component ) )
				{
					Mesh[] meshes = new Mesh[( (ParticleSystemRenderer) component ).meshCount];
					int meshCount = ( (ParticleSystemRenderer) component ).GetMeshes( meshes );
					bool modifiedMeshes = false;
					for( int i = 0; i < meshCount; i++ )
					{
						referenceNode.AddLinkTo( SearchObject( meshes[i] ), "Renderer Module: Mesh" );

						if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( meshes[i] ) )
						{
							searchParameters.searchRefactoring( new OtherSearchMatch( component, meshes[i], ( newValue ) =>
							{
								meshes[i] = (Mesh) newValue;
								modifiedMeshes = true;
							} ) );
						}
					}

					if( modifiedMeshes )
						( (ParticleSystemRenderer) component ).SetMeshes( meshes, meshCount );
				}
			}
			else if( component is ParticleSystem )
			{
				// At runtime, some ParticleSystem properties can't be searched with reflection, search them manually here
				if( isInPlayMode && !AssetDatabase.Contains( component ) )
				{
					ParticleSystem particleSystem = (ParticleSystem) component;

					try
					{
						ParticleSystem.CollisionModule collisionModule = particleSystem.collision;
#if UNITY_2020_2_OR_NEWER
						for( int i = 0, j = collisionModule.planeCount; i < j; i++ )
#else
						for( int i = 0, j = collisionModule.maxPlaneCount; i < j; i++ )
#endif
						{
							Transform plane = collisionModule.GetPlane( i );
							referenceNode.AddLinkTo( SearchObject( plane ), "Collision Module: Plane" );

							if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( plane ) )
								searchParameters.searchRefactoring( new OtherSearchMatch( collisionModule, plane, component, ( newValue ) => collisionModule.SetPlane( i, (Transform) newValue ) ) );
						}
					}
					catch { }

					try
					{
						ParticleSystem.TriggerModule triggerModule = particleSystem.trigger;
#if UNITY_2020_2_OR_NEWER
						for( int i = 0, j = triggerModule.colliderCount; i < j; i++ )
#else
						for( int i = 0, j = triggerModule.maxColliderCount; i < j; i++ )
#endif
						{
							Component collider = triggerModule.GetCollider( i );
							referenceNode.AddLinkTo( SearchObject( collider ), "Trigger Module: Collider" );

							if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( collider ) )
								searchParameters.searchRefactoring( new OtherSearchMatch( triggerModule, collider, component, ( newValue ) => triggerModule.SetCollider( i, (Component) newValue ) ) );
						}
					}
					catch { }

#if UNITY_2017_1_OR_NEWER
					try
					{
						ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule = particleSystem.textureSheetAnimation;
						for( int i = 0, j = textureSheetAnimationModule.spriteCount; i < j; i++ )
						{
							Sprite sprite = textureSheetAnimationModule.GetSprite( i );
							referenceNode.AddLinkTo( SearchObject( sprite ), "Texture Sheet Animation Module: Sprite" );

							if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( sprite ) )
								searchParameters.searchRefactoring( new OtherSearchMatch( textureSheetAnimationModule, sprite, component, ( newValue ) => textureSheetAnimationModule.SetSprite( i, (Sprite) newValue ) ) );
						}
					}
					catch { }
#endif

#if UNITY_5_5_OR_NEWER
					try
					{
						ParticleSystem.SubEmittersModule subEmittersModule = particleSystem.subEmitters;
						for( int i = 0, j = subEmittersModule.subEmittersCount; i < j; i++ )
						{
							ParticleSystem subEmitterSystem = subEmittersModule.GetSubEmitterSystem( i );
							referenceNode.AddLinkTo( SearchObject( subEmitterSystem ), "Sub Emitters Module: ParticleSystem" );

							if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( subEmitterSystem ) )
								searchParameters.searchRefactoring( new OtherSearchMatch( subEmittersModule, subEmitterSystem, component, ( newValue ) => subEmittersModule.SetSubEmitterSystem( i, (ParticleSystem) newValue ) ) );
						}
					}
					catch { }
#endif
				}
			}

			SearchVariablesWithSerializedObject( referenceNode );
			return referenceNode;
		}

		private ReferenceNode SearchMaterial( object obj )
		{
			const string TEXTURE_PROPERTY_PREFIX = "m_SavedProperties.m_TexEnvs[";

			Material material = (Material) obj;
			ReferenceNode referenceNode = PopReferenceNode( material );

			// We used to search only the shader and the Texture properties in this function but it has changed for 2 major reasons:
			// 1) Materials can store more than these references now. For example, HDRP materials can have references to other HDRP materials
			// 2) It wasn't possible to search Texture properties that were no longer used by the shader
			// Thus, we are searching every property of the material using SerializedObject
			SearchVariablesWithSerializedObject( referenceNode );

			// Post-process the found results and convert links that start with TEXTURE_PROPERTY_PREFIX to their readable names
			SerializedObject materialSO = null;
			for( int i = referenceNode.NumberOfOutgoingLinks - 1; i >= 0; i-- )
			{
				List<string> linkDescriptions = referenceNode[i].descriptions;
				for( int j = linkDescriptions.Count - 1; j >= 0; j-- )
				{
					int texturePropertyPrefixIndex = linkDescriptions[j].IndexOf( TEXTURE_PROPERTY_PREFIX );
					if( texturePropertyPrefixIndex >= 0 )
					{
						texturePropertyPrefixIndex += TEXTURE_PROPERTY_PREFIX.Length;
						int texturePropertyEndIndex = linkDescriptions[j].IndexOf( ']', texturePropertyPrefixIndex );
						if( texturePropertyEndIndex > texturePropertyPrefixIndex )
						{
							int texturePropertyIndex;
							if( int.TryParse( linkDescriptions[j].Substring( texturePropertyPrefixIndex, texturePropertyEndIndex - texturePropertyPrefixIndex ), out texturePropertyIndex ) )
							{
								if( materialSO == null )
									materialSO = new SerializedObject( material );

								string propertyName = materialSO.FindProperty( "m_SavedProperties.m_TexEnvs.Array.data[" + texturePropertyIndex + "].first" ).stringValue;
								if( material.HasProperty( propertyName ) )
									linkDescriptions[j] = "[Property: " + propertyName + "]";
								else if( searchParameters.searchUnusedMaterialProperties )
								{
									// Move unused references to the end of the list so that used references come first
									linkDescriptions.Add( "[Property (UNUSED): " + propertyName + "]" );
									linkDescriptions.RemoveAt( j );
								}
								else
									linkDescriptions.RemoveAt( j );
							}
						}
					}
				}

				if( linkDescriptions.Count == 0 ) // All shader properties were unused and we weren't searching for unused material properties
					referenceNode.RemoveLink( i );
			}

			// At runtime, Textures assigned to clone materials can't be searched with reflection, search them manually here
			if( searchTextureReferences && isInPlayMode && !AssetDatabase.Contains( material ) )
			{
				Shader shader = material.shader;
				int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader );
				for( int i = 0; i < shaderPropertyCount; i++ )
				{
					if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv )
					{
						string propertyName = ShaderUtil.GetPropertyName( shader, i );
						Texture assignedTexture = material.GetTexture( propertyName );
						if( objectsToSearchSet.Contains( assignedTexture ) )
						{
							referenceNode.AddLinkTo( GetReferenceNode( assignedTexture ), "Shader property: " + propertyName );

							if( searchParameters.searchRefactoring != null )
								searchParameters.searchRefactoring( new OtherSearchMatch( material, assignedTexture, ( newValue ) => material.SetTexture( propertyName, (Texture) newValue ) ) );
						}
					}
				}
			}

			return referenceNode;
		}

		// Searches default Texture values assigned to shader properties, as well as #include references in shader source code
		private ReferenceNode SearchShader( object obj )
		{
			Shader shader = (Shader) obj;
			ReferenceNode referenceNode = PopReferenceNode( shader );

			if( searchTextureReferences )
			{
				ShaderImporter shaderImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( shader ) ) as ShaderImporter;
				if( shaderImporter != null )
				{
					int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader );
					for( int i = 0; i < shaderPropertyCount; i++ )
					{
						if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv )
						{
							string propertyName = ShaderUtil.GetPropertyName( shader, i );
							Texture defaultTexture = shaderImporter.GetDefaultTexture( propertyName );
#if UNITY_2018_1_OR_NEWER
							if( !defaultTexture )
								defaultTexture = shaderImporter.GetNonModifiableTexture( propertyName );
#endif

							if( objectsToSearchSet.Contains( defaultTexture ) )
							{
								referenceNode.AddLinkTo( GetReferenceNode( defaultTexture ), "Default Texture: " + propertyName );

								if( searchParameters.searchRefactoring != null )
									searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( shaderImporter, defaultTexture, propertyName, null ) );
							}
						}
					}
				}
			}

			// Search shader source code for #include references
			if( shaderIncludesToSearchSet.Count > 0 )
				SearchShaderSourceCodeForCGIncludes( referenceNode );

			return referenceNode;
		}

		// Searches .compute, .cginc, .cg, .hlsl and .glslinc assets for #include references
		private ReferenceNode SearchShaderSecondaryAsset( object obj )
		{
			if( shaderIncludesToSearchSet.Count == 0 )
				return null;

			ReferenceNode referenceNode = PopReferenceNode( obj );
			SearchShaderSourceCodeForCGIncludes( referenceNode );
			return referenceNode;
		}

		// Searches class/interface inheritances and default UnityEngine.Object values assigned to script variables
		private ReferenceNode SearchMonoScript( object obj )
		{
			MonoScript script = (MonoScript) obj;
			Type scriptType = script.GetClass();
			if( scriptType == null || ( !scriptType.IsSubclassOf( typeof( MonoBehaviour ) ) && !scriptType.IsSubclassOf( typeof( ScriptableObject ) ) ) )
				return null;

			ReferenceNode referenceNode = PopReferenceNode( script );

			// Check for class/interface inheritance references
			for( int i = monoScriptsToSearch.Count - 1; i >= 0; i-- )
			{
				if( monoScriptsToSearchTypes[i] != scriptType && monoScriptsToSearchTypes[i].IsAssignableFrom( scriptType ) )
					referenceNode.AddLinkTo( GetReferenceNode( monoScriptsToSearch[i] ), monoScriptsToSearchTypes[i].IsInterface ? "Implements interface" : "Extends class" );
			}

			MonoImporter scriptImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( script ) ) as MonoImporter;
			if( scriptImporter != null )
			{
				VariableGetterHolder[] variables = GetFilteredVariablesForType( scriptType );
				for( int i = 0; i < variables.Length; i++ )
				{
					if( variables[i].isSerializable && !variables[i].IsProperty )
					{
						Object defaultValue = scriptImporter.GetDefaultReference( variables[i].Name );
						if( objectsToSearchSet.Contains( defaultValue ) )
						{
							referenceNode.AddLinkTo( GetReferenceNode( defaultValue ), "Default variable value: " + variables[i].Name );

							if( searchParameters.searchRefactoring != null )
								searchParameters.searchRefactoring( new AssetImporterDefaultValueMatch( scriptImporter, defaultValue, variables[i].Name, variables ) );
						}
					}
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorController( object obj )
		{
			RuntimeAnimatorController controller = (RuntimeAnimatorController) obj;
			ReferenceNode referenceNode = PopReferenceNode( controller );

			if( controller is AnimatorController )
			{
				AnimatorControllerLayer[] layers = ( (AnimatorController) controller ).layers;
				for( int i = 0; i < layers.Length; i++ )
				{
					if( objectsToSearchSet.Contains( layers[i].avatarMask ) )
					{
						referenceNode.AddLinkTo( GetReferenceNode( layers[i].avatarMask ), layers[i].name + " Mask" );

						if( searchParameters.searchRefactoring != null )
							searchParameters.searchRefactoring( new AnimationSystemMatch( layers[i], layers[i].avatarMask, controller, ( newValue ) => layers[i].avatarMask = (AvatarMask) newValue ) );
					}

					referenceNode.AddLinkTo( SearchObject( layers[i].stateMachine ) );
				}
			}
			else
			{
				if( controller is AnimatorOverrideController )
				{
					RuntimeAnimatorController parentController = ( (AnimatorOverrideController) controller ).runtimeAnimatorController;
					if( objectsToSearchSet.Contains( parentController ) )
					{
						referenceNode.AddLinkTo( GetReferenceNode( parentController ) );

						if( searchParameters.searchRefactoring != null )
							searchParameters.searchRefactoring( new AnimationSystemMatch( controller, parentController, ( newValue ) => ( (AnimatorOverrideController) controller ).runtimeAnimatorController = (RuntimeAnimatorController) newValue ) );
					}

					if( searchParameters.searchRefactoring != null )
					{
						List<KeyValuePair<AnimationClip, AnimationClip>> overrideClips = new List<KeyValuePair<AnimationClip, AnimationClip>>( ( (AnimatorOverrideController) controller ).overridesCount );
						( (AnimatorOverrideController) controller ).GetOverrides( overrideClips );
						bool modifiedOverrideClips = false;
						for( int i = overrideClips.Count - 1; i >= 0; i-- )
						{
							if( objectsToSearchSet.Contains( overrideClips[i].Value ) )
							{
								searchParameters.searchRefactoring( new AnimationSystemMatch( controller, overrideClips[i].Value, ( newValue ) =>
								{
									overrideClips[i] = new KeyValuePair<AnimationClip, AnimationClip>( overrideClips[i].Key, (AnimationClip) newValue );
									modifiedOverrideClips = true;
								} ) );
							}
						}

						if( modifiedOverrideClips )
							( (AnimatorOverrideController) controller ).ApplyOverrides( overrideClips );
					}
				}

				AnimationClip[] animClips = controller.animationClips;
				for( int i = 0; i < animClips.Length; i++ )
					referenceNode.AddLinkTo( SearchObject( animClips[i] ) );
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorStateMachine( object obj )
		{
			AnimatorStateMachine animatorStateMachine = (AnimatorStateMachine) obj;
			ReferenceNode referenceNode = PopReferenceNode( animatorStateMachine );

			ChildAnimatorStateMachine[] stateMachines = animatorStateMachine.stateMachines;
			for( int i = 0; i < stateMachines.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( stateMachines[i].stateMachine ), "Child State Machine" );

			ChildAnimatorState[] states = animatorStateMachine.states;
			for( int i = 0; i < states.Length; i++ )
				referenceNode.AddLinkTo( SearchObject( states[i].state ) );

			if( searchMonoBehavioursForScript )
			{
				StateMachineBehaviour[] behaviours = animatorStateMachine.behaviours;
				for( int i = 0; i < behaviours.Length; i++ )
				{
					MonoScript script = MonoScript.FromScriptableObject( behaviours[i] );
					if( objectsToSearchSet.Contains( script ) )
					{
						referenceNode.AddLinkTo( GetReferenceNode( script ) );

						if( searchParameters.searchRefactoring != null )
							searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorStateMachine, script, behaviours[i] ) );
					}
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorState( object obj )
		{
			AnimatorState animatorState = (AnimatorState) obj;
			ReferenceNode referenceNode = PopReferenceNode( animatorState );

			referenceNode.AddLinkTo( SearchObject( animatorState.motion ), "Motion" );

			if( searchParameters.searchRefactoring != null && animatorState.motion as AnimationClip && objectsToSearchSet.Contains( animatorState.motion ) )
				searchParameters.searchRefactoring( new AnimationSystemMatch( animatorState, animatorState.motion, ( newValue ) => animatorState.motion = (Motion) newValue ) );

			if( searchMonoBehavioursForScript )
			{
				StateMachineBehaviour[] behaviours = animatorState.behaviours;
				for( int i = 0; i < behaviours.Length; i++ )
				{
					MonoScript script = MonoScript.FromScriptableObject( behaviours[i] );
					if( objectsToSearchSet.Contains( script ) )
					{
						referenceNode.AddLinkTo( GetReferenceNode( script ) );

						if( searchParameters.searchRefactoring != null )
							searchParameters.searchRefactoring( new BehaviourUsageMatch( animatorState, script, behaviours[i] ) );
					}
				}
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimatorStateTransition( object obj )
		{
			// Don't search AnimatorStateTransition objects, it will just return duplicate results of SearchAnimatorStateMachine
			return PopReferenceNode( obj );
		}

		private ReferenceNode SearchBlendTree( object obj )
		{
			BlendTree blendTree = (BlendTree) obj;
			ReferenceNode referenceNode = PopReferenceNode( blendTree );

			ChildMotion[] children = blendTree.children;
			for( int i = 0; i < children.Length; i++ )
			{
				referenceNode.AddLinkTo( SearchObject( children[i].motion ), "Motion" );

				if( searchParameters.searchRefactoring != null && children[i].motion as AnimationClip && objectsToSearchSet.Contains( children[i].motion ) )
					searchParameters.searchRefactoring( new AnimationSystemMatch( blendTree, children[i].motion, ( newValue ) => children[i].motion = (Motion) newValue ) );
			}

			return referenceNode;
		}

		private ReferenceNode SearchAnimationClip( object obj )
		{
			AnimationClip clip = (AnimationClip) obj;
			ReferenceNode referenceNode = PopReferenceNode( clip );

			// Get all curves from animation clip
			EditorCurveBinding[] objectCurves = AnimationUtility.GetObjectReferenceCurveBindings( clip );
			for( int i = 0; i < objectCurves.Length; i++ )
			{
				// Search through all the keyframes in this curve
				ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve( clip, objectCurves[i] );
				bool modifiedKeyframes = false;
				for( int j = 0; j < keyframes.Length; j++ )
				{
					referenceNode.AddLinkTo( SearchObject( keyframes[j].value ), "Keyframe: " + keyframes[j].time );

					if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( keyframes[j].value ) )
					{
						searchParameters.searchRefactoring( new AnimationSystemMatch( clip, keyframes[j].value, ( newValue ) =>
						{
							keyframes[j].value = newValue;
							modifiedKeyframes = true;
						} ) );
					}
				}

				if( modifiedKeyframes )
					AnimationUtility.SetObjectReferenceCurve( clip, objectCurves[i], keyframes );
			}

			// Get all events from animation clip
			AnimationEvent[] events = AnimationUtility.GetAnimationEvents( clip );
			bool modifiedEvents = false;
			for( int i = 0; i < events.Length; i++ )
			{
				referenceNode.AddLinkTo( SearchObject( events[i].objectReferenceParameter ), "AnimationEvent: " + events[i].time );

				if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( events[i].objectReferenceParameter ) )
				{
					searchParameters.searchRefactoring( new AnimationSystemMatch( clip, events[i].objectReferenceParameter, ( newValue ) =>
					{
						events[i].objectReferenceParameter = newValue;
						modifiedEvents = true;
					} ) );
				}
			}

			if( modifiedEvents )
				AnimationUtility.SetAnimationEvents( clip, events );

			return referenceNode;
		}

		// TerrainData's properties like tree/detail/layer definitions aren't exposed to SerializedObject so use reflection instead
		private ReferenceNode SearchTerrainData( object obj )
		{
			ReferenceNode referenceNode = PopReferenceNode( obj );
			SearchVariablesWithReflection( referenceNode );
			return referenceNode;
		}

		private ReferenceNode SearchLightmapSettings( object obj )
		{
			ReferenceNode referenceNode = PopReferenceNode( obj );

			referenceNode.AddLinkTo( SearchObject( LightmapSettings.lightProbes ), "Light Probes" );

			LightmapData[] lightmaps = LightmapSettings.lightmaps;
			if( lightmaps != null )
			{
				for( int i = 0; i < lightmaps.Length; i++ )
					referenceNode.AddLinkTo( SearchObject( lightmaps[i] ), "Lightmap" );
			}

			SearchVariablesWithSerializedObject( referenceNode, true );
			return referenceNode;
		}

		private ReferenceNode SearchRenderSettings( object obj )
		{
			ReferenceNode referenceNode = PopReferenceNode( obj );

#if UNITY_2021_2_OR_NEWER
			referenceNode.AddLinkTo( SearchObject( defaultReflectionProbeGetter() ), "Default Reflection Probe" );
#else
			referenceNode.AddLinkTo( SearchObject( ReflectionProbe.defaultTexture ), "Default Reflection Probe" );
#endif
			SearchVariablesWithSerializedObject( referenceNode, true );
			return referenceNode;
		}

#if UNITY_2017_1_OR_NEWER
		private ReferenceNode SearchSpriteAtlas( object obj )
		{
			SpriteAtlas spriteAtlas = (SpriteAtlas) obj;
			ReferenceNode referenceNode = PopReferenceNode( spriteAtlas );

			SerializedObject spriteAtlasSO = new SerializedObject( spriteAtlas );
			if( spriteAtlas.isVariant )
			{
				SerializedProperty masterAtlasProperty = spriteAtlasSO.FindProperty( "m_MasterAtlas" );
				Object masterAtlas = masterAtlasProperty.objectReferenceValue;
				if( objectsToSearchSet.Contains( masterAtlas ) )
				{
					referenceNode.AddLinkTo( SearchObject( masterAtlas ), "Master Atlas" );

					if( searchParameters.searchRefactoring != null )
						searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, masterAtlas, masterAtlasProperty ) );
				}
			}

			SerializedProperty packables = spriteAtlasSO.FindProperty( "m_EditorData.packables" );
			if( packables != null )
			{
				for( int i = 0, length = packables.arraySize; i < length; i++ )
				{
					SerializedProperty packedSpriteProperty = packables.GetArrayElementAtIndex( i );
					Object packedSprite = packedSpriteProperty.objectReferenceValue;
					SearchSpriteAtlas( referenceNode, packedSprite );

					if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( packedSprite ) )
						searchParameters.searchRefactoring( new SerializedPropertyMatch( spriteAtlas, packedSprite, packedSpriteProperty ) );
				}
			}
#if UNITY_2018_2_OR_NEWER
			else
			{
				Object[] _packables = spriteAtlas.GetPackables();
				if( _packables != null )
				{
					for( int i = 0; i < _packables.Length; i++ )
						SearchSpriteAtlas( referenceNode, _packables[i] );
				}
			}
#endif

			return referenceNode;
		}

		private void SearchSpriteAtlas( ReferenceNode referenceNode, Object packedAsset )
		{
			if( packedAsset == null || packedAsset.Equals( null ) )
				return;

			referenceNode.AddLinkTo( SearchObject( packedAsset ), "Packed Texture" );

			if( packedAsset is Texture )
			{
				// Search the Texture's sprites if the Texture asset isn't included in the "SEARCHED OBJECTS" list (i.e. user has
				// added only a Sprite sub-asset of the Texture to the list, not the Texture asset itself). Otherwise, references to
				// both the Texture and its sprites will be found which can be considered as duplicate references
				if( AssetDatabase.IsMainAsset( packedAsset ) && !assetsToSearchSet.Contains( packedAsset ) )
				{
					Object[] textureSubAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( packedAsset ) );
					for( int i = 0; i < textureSubAssets.Length; i++ )
					{
						if( textureSubAssets[i] is Sprite )
							referenceNode.AddLinkTo( SearchObject( textureSubAssets[i] ), "Packed Texture" );
					}
				}
			}
			else if( packedAsset.IsFolder() )
			{
				// Search all Sprites in the folder
				string[] texturesInFolder = AssetDatabase.FindAssets( "t:Texture2D", new string[] { AssetDatabase.GetAssetPath( packedAsset ) } );
				if( texturesInFolder != null )
				{
					for( int i = 0; i < texturesInFolder.Length; i++ )
					{
						string texturePath = AssetDatabase.GUIDToAssetPath( texturesInFolder[i] );
						TextureImporter textureImporter = AssetImporter.GetAtPath( texturePath ) as TextureImporter;
						if( textureImporter != null && textureImporter.textureType == TextureImporterType.Sprite )
						{
							// Search the Texture and its sprites
							SearchSpriteAtlas( referenceNode, AssetDatabase.LoadMainAssetAtPath( texturePath ) );
						}
					}
				}
			}
		}
#endif

#if UNITY_2017_3_OR_NEWER
		// Find references from an Assembly Definition File to its Assembly Definition References
		private ReferenceNode SearchAssemblyDefinitionFile( object obj )
		{
			if( assemblyDefinitionFilesToSearch.Count == 0 )
				return null;

			AssemblyDefinitionReferences assemblyDefinitionFile = JsonUtility.FromJson<AssemblyDefinitionReferences>( ( (TextAsset) obj ).text );
			ReferenceNode referenceNode = PopReferenceNode( obj );

			if( !string.IsNullOrEmpty( assemblyDefinitionFile.reference ) )
			{
				if( assemblyDefinitionFile.references == null )
					assemblyDefinitionFile.references = new List<string>( 1 ) { assemblyDefinitionFile.reference };
				else
					assemblyDefinitionFile.references.Add( assemblyDefinitionFile.reference );
			}

			if( assemblyDefinitionFile.references != null )
			{
				for( int i = 0; i < assemblyDefinitionFile.references.Count; i++ )
				{
#if UNITY_2019_1_OR_NEWER
					string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyReference( assemblyDefinitionFile.references[i] );
#else
					string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName( assemblyDefinitionFile.references[i] );
#endif
					if( !string.IsNullOrEmpty( assemblyPath ) )
					{
						Object searchedAssemblyDefinitionFile;
						if( assemblyDefinitionFilesToSearch.TryGetValue( assemblyPath, out searchedAssemblyDefinitionFile ) )
							referenceNode.AddLinkTo( GetReferenceNode( searchedAssemblyDefinitionFile ), "Referenced Assembly" );
					}
				}
			}

			return referenceNode;
		}
#endif

#if UNITY_2018_1_OR_NEWER
		// Searches Shader Graph assets for references
		private ReferenceNode SearchShaderGraph( object obj )
		{
			if( !searchTextureReferences && !searchShaderGraphsForSubGraphs && shaderIncludesToSearchSet.Count == 0 )
				return null;

			ReferenceNode referenceNode = PopReferenceNode( obj );

			// Shader Graph assets are JSON files, they must be crawled manually to find references
			string graphJson = File.ReadAllText( AssetDatabase.GetAssetPath( (Object) obj ) );
			if( graphJson.IndexOf( "\"m_ObjectId\"", 0, Mathf.Min( 200, graphJson.Length ) ) >= 0 )
			{
				// New Shader Graph serialization format is used: https://github.com/Unity-Technologies/Graphics/pull/222
				// Iterate over all these occurrences:   "guid\": \"GUID_VALUE\" (\" is used instead of " because it is a nested JSON)
				IterateOverValuesInString( graphJson, new string[] { "\"guid\\\"" }, '"', ( guid ) =>
				{
					if( guid.Length > 1 )
					{
						if( guid[guid.Length - 1] == '\\' )
							guid = guid.Substring( 0, guid.Length - 1 );

						string referencePath = AssetDatabase.GUIDToAssetPath( guid );
						if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) )
						{
							Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath );
							if( objectsToSearchSet.Contains( reference ) )
								referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in graph" );
						}
					}
				} );

				if( shaderIncludesToSearchSet.Count > 0 )
				{
					// Iterate over all these occurrences:   "m_FunctionSource": "GUID_VALUE" (this one is not nested JSON)
					IterateOverValuesInString( graphJson, new string[] { "\"m_FunctionSource\"" }, '"', ( guid ) =>
					{
						string referencePath = AssetDatabase.GUIDToAssetPath( guid );
						if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) )
						{
							Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath );
							if( objectsToSearchSet.Contains( reference ) )
								referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in node: Custom Function" );
						}
					} );
				}
			}
			else
			{
				// Old Shader Graph serialization format is used. Although we could use the same search method as the new serialization format (which
				// is potentially faster), this alternative search method yields more information about references
				ShaderGraphReferences shaderGraph = JsonUtility.FromJson<ShaderGraphReferences>( graphJson );

				if( shaderGraph.m_SerializedProperties != null )
				{
					for( int i = shaderGraph.m_SerializedProperties.Count - 1; i >= 0; i-- )
					{
						string propertyJSON = shaderGraph.m_SerializedProperties[i].JSONnodeData;
						if( string.IsNullOrEmpty( propertyJSON ) )
							continue;

						ShaderGraphReferences.PropertyData propertyData = JsonUtility.FromJson<ShaderGraphReferences.PropertyData>( propertyJSON );
						if( propertyData.m_Value == null )
							continue;

						string texturePath = propertyData.m_Value.GetTexturePath();
						if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) )
							continue;

						Texture texture = AssetDatabase.LoadAssetAtPath<Texture>( texturePath );
						if( objectsToSearchSet.Contains( texture ) )
							referenceNode.AddLinkTo( GetReferenceNode( texture ), "Default Texture: " + propertyData.GetName() );
					}
				}

				if( shaderGraph.m_SerializableNodes != null )
				{
					for( int i = shaderGraph.m_SerializableNodes.Count - 1; i >= 0; i-- )
					{
						string nodeJSON = shaderGraph.m_SerializableNodes[i].JSONnodeData;
						if( string.IsNullOrEmpty( nodeJSON ) )
							continue;

						ShaderGraphReferences.NodeData nodeData = JsonUtility.FromJson<ShaderGraphReferences.NodeData>( nodeJSON );
						if( !string.IsNullOrEmpty( nodeData.m_FunctionSource ) )
						{
							string customFunctionPath = AssetDatabase.GUIDToAssetPath( nodeData.m_FunctionSource );
							if( !string.IsNullOrEmpty( customFunctionPath ) && assetsToSearchPathsSet.Contains( customFunctionPath ) )
							{
								Object customFunction = AssetDatabase.LoadMainAssetAtPath( customFunctionPath );
								if( objectsToSearchSet.Contains( customFunction ) )
									referenceNode.AddLinkTo( GetReferenceNode( customFunction ), "Used in node: " + nodeData.m_Name );
							}
						}

						if( searchShaderGraphsForSubGraphs )
						{
							string subGraphPath = nodeData.GetSubGraphPath();
							if( !string.IsNullOrEmpty( subGraphPath ) && assetsToSearchPathsSet.Contains( subGraphPath ) )
							{
								Object subGraph = AssetDatabase.LoadMainAssetAtPath( subGraphPath );
								if( objectsToSearchSet.Contains( subGraph ) )
									referenceNode.AddLinkTo( GetReferenceNode( subGraph ), "Used as Sub-graph" );
							}
						}

						if( nodeData.m_SerializableSlots == null )
							continue;

						for( int j = nodeData.m_SerializableSlots.Count - 1; j >= 0; j-- )
						{
							string nodeSlotJSON = nodeData.m_SerializableSlots[j].JSONnodeData;
							if( string.IsNullOrEmpty( nodeSlotJSON ) )
								continue;

							string texturePath = JsonUtility.FromJson<ShaderGraphReferences.NodeSlotData>( nodeSlotJSON ).GetTexturePath();
							if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) )
								continue;

							Texture texture = AssetDatabase.LoadAssetAtPath<Texture>( texturePath );
							if( objectsToSearchSet.Contains( texture ) )
								referenceNode.AddLinkTo( GetReferenceNode( texture ), "Used in node: " + nodeData.m_Name );
						}
					}
				}
			}

			return referenceNode;
		}
#endif

#if ASSET_USAGE_VFX_GRAPH
		private ReferenceNode SearchVFXGraphAsset( object obj )
		{
			ReferenceNode referenceNode = PopReferenceNode( obj );

			object vfxResource = vfxResourceGetter( AssetDatabase.GetAssetPath( (Object) obj ) );
			foreach( Object vfxResourceContent in (Object[]) vfxResourceContentsGetter.Invoke( vfxResource, null ) )
				referenceNode.AddLinkTo( SearchObject( vfxResourceContent ) );

			return referenceNode;
		}
#endif

		// Find references from an Animation/Animator component to the objects that it animates
		private void SearchAnimatedObjects( ReferenceNode referenceNode )
		{
			GameObject root = ( (Component) referenceNode.nodeObject ).gameObject;
			AnimationClip[] clips = AnimationUtility.GetAnimationClips( root );
			for( int i = 0; i < clips.Length; i++ )
			{
				AnimationClip clip = clips[i];
				if( !clip )
					continue;

				bool isClipUnique = true;
				for( int j = i - 1; j >= 0; j-- )
				{
					if( clips[j] == clip )
					{
						isClipUnique = false;
						break;
					}
				}

				if( !isClipUnique )
					continue;

				EditorCurveBinding[] uniqueBindings;
				if( !animationClipUniqueBindings.TryGetValue( clip, out uniqueBindings ) )
				{
					// Calculate all the "unique" paths that the animation clip's curves have
					// Both float curves (GetCurveBindings) and object reference curves (GetObjectReferenceCurveBindings) are checked
					List<EditorCurveBinding> _uniqueBindings = new List<EditorCurveBinding>( 2 );
					EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings( clip );
					for( int j = 0; j < bindings.Length; j++ )
					{
						string bindingPath = bindings[j].path;
						if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object
							continue;

						bool isBindingUnique = true;
						for( int k = _uniqueBindings.Count - 1; k >= 0; k-- )
						{
							if( bindingPath == _uniqueBindings[k].path )
							{
								isBindingUnique = false;
								break;
							}
						}

						if( isBindingUnique )
							_uniqueBindings.Add( bindings[j] );
					}

					bindings = AnimationUtility.GetObjectReferenceCurveBindings( clip );
					for( int j = 0; j < bindings.Length; j++ )
					{
						string bindingPath = bindings[j].path;
						if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object
							continue;

						bool isBindingUnique = true;
						for( int k = _uniqueBindings.Count - 1; k >= 0; k-- )
						{
							if( bindingPath == _uniqueBindings[k].path )
							{
								isBindingUnique = false;
								break;
							}
						}

						if( isBindingUnique )
							_uniqueBindings.Add( bindings[j] );
					}

					uniqueBindings = _uniqueBindings.ToArray();
					animationClipUniqueBindings[clip] = uniqueBindings;
				}

				string clipName = clip.name;
				for( int j = 0; j < uniqueBindings.Length; j++ )
					referenceNode.AddLinkTo( SearchObject( AnimationUtility.GetAnimatedObject( root, uniqueBindings[j] ) ), "Animated via clip: " + clipName );
			}
		}

		// Search #include references in shader source code
		private void SearchShaderSourceCodeForCGIncludes( ReferenceNode referenceNode )
		{
			string shaderPath = AssetDatabase.GetAssetPath( (Object) referenceNode.nodeObject );

			// Iterate over all these occurrences:    #include "INCLUDE_REFERENCE"   or   #include_with_pragmas "INCLUDE_REFERENCE"
			IterateOverValuesInString( File.ReadAllText( shaderPath ), new string[] { "#include ", "#include_with_pragmas " }, '"', ( include ) =>
			{
				bool isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include );
				if( !isIncludePotentialReference )
				{
					// Get absolute path of the #include
					include = Path.GetFullPath( Path.Combine( Path.GetDirectoryName( shaderPath ), include ) );

					int trimStartLength = Directory.GetCurrentDirectory().Length + 1; // Convert absolute path to a Project-relative path
					if( include.Length > trimStartLength )
					{
						include = include.Substring( trimStartLength ).Replace( '\\', '/' );
						isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include );
					}
				}

				if( isIncludePotentialReference )
				{
					Object cgShader = AssetDatabase.LoadMainAssetAtPath( include );
					if( objectsToSearchSet.Contains( cgShader ) )
						referenceNode.AddLinkTo( GetReferenceNode( cgShader ), "Used with #include" );
				}
			} );
		}

		// Search through variables of an object with SerializedObject
		private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode, bool forceUseSerializedObject = false )
		{
			Object unityObject = (Object) referenceNode.nodeObject;
			if( !isInPlayMode || unityObject.IsAsset() || forceUseSerializedObject )
			{
#if ASSET_USAGE_ADDRESSABLES
				// See: https://github.com/yasirkula/UnityAssetUsageDetector/issues/29
				if( searchParameters.addressablesSupport && unityObject.name == "Deprecated EditorExtensionImpl" )
					return;
#endif

				SerializedObject so = new SerializedObject( unityObject );
				SerializedProperty iterator = so.GetIterator();
				SerializedProperty iteratorVisible = so.GetIterator();
				if( iterator.Next( true ) )
				{
					bool iteratingVisible = iteratorVisible.NextVisible( true );
#if UNITY_2018_3_OR_NEWER
					bool searchPrefabOverridesOnly = searchParameters.hideReduntantPrefabVariantLinks && unityObject.IsAsset() && PrefabUtility.GetCorrespondingObjectFromSource( unityObject ) != null;
#endif
					bool enterChildren;
					do
					{
						// Iterate over NextVisible properties AND the properties that have corresponding FieldInfos (internal Unity
						// properties don't have FieldInfos so we are skipping them, which is good because search results found in
						// those properties aren't interesting and mostly confusing)
						bool shouldMoveVisibleIterator = iteratingVisible && SerializedProperty.EqualContents( iterator, iteratorVisible );
						bool isVisible = shouldMoveVisibleIterator || iterator.type == "Array";
						if( !isVisible )
						{
							Type propFieldType;
							isVisible = fieldInfoGetter( iterator, out propFieldType ) != null;
						}

						if( !isVisible )
							enterChildren = false;
#if UNITY_2018_3_OR_NEWER
						else if( searchPrefabOverridesOnly && !iterator.prefabOverride )
							enterChildren = false;
#endif
						else
						{
							Object propertyValue;
							ReferenceNode searchResult;
							switch( iterator.propertyType )
							{
								case SerializedPropertyType.ObjectReference:
									propertyValue = iterator.objectReferenceValue;
									searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
									enterChildren = false;
									break;
								case SerializedPropertyType.ExposedReference:
									propertyValue = iterator.exposedReferenceValue;
									searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
									enterChildren = false;
									break;
#if UNITY_2019_3_OR_NEWER
								case SerializedPropertyType.ManagedReference:
									object managedReferenceValue = GetRawSerializedPropertyValue( iterator );
									propertyValue = managedReferenceValue as Object;
									searchResult = SearchObject( PreferablyGameObject( managedReferenceValue ) );
									enterChildren = false;
									break;
#endif
								case SerializedPropertyType.Generic:
#if ASSET_USAGE_ADDRESSABLES
									if( searchParameters.addressablesSupport && iterator.type.StartsWithFast( "AssetReference" ) && GetRawSerializedPropertyValue( iterator ) is AssetReference assetReference )
									{
										propertyValue = GetAddressablesAssetReferenceValue( assetReference );
										searchResult = SearchObject( PreferablyGameObject( propertyValue ) );
										enterChildren = false;
									}
									else
#endif
#if ASSET_USAGE_VFX_GRAPH
									if( vfxSerializableObjectValueGetter != null && iterator.type == "VFXSerializableObject" && GetRawSerializedPropertyValue( iterator ) is object vfxSerializableObject )
									{
										object vfxSerializableObjectValue = vfxSerializableObjectValueGetter.Invoke( vfxSerializableObject, null );
										propertyValue = vfxSerializableObjectValue as Object;
										searchResult = SearchObject( PreferablyGameObject( vfxSerializableObjectValue ) );
										enterChildren = false;
									}
									else
#endif
									{
										propertyValue = null;
										searchResult = null;
										enterChildren = true;
									}

									break;
								default:
									propertyValue = null;
									searchResult = null;
									enterChildren = false;
									break;
							}

							if( searchResult != null && searchResult != referenceNode )
							{
								string propertyPath = iterator.propertyPath;

								// m_RD.texture is a redundant reference that shows up when searching sprites
								if( !propertyPath.EndsWithFast( "m_RD.texture" ) )
								{
									referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath.Replace( ".Array.data[", "[" ) ); // "arrayVariable.Array.data[0]" becomes "arrayVariable[0]"

									if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( propertyValue ) )
										searchParameters.searchRefactoring( new SerializedPropertyMatch( unityObject, propertyValue, iterator ) );
								}
							}
						}

						if( shouldMoveVisibleIterator )
							iteratingVisible = iteratorVisible.NextVisible( enterChildren );
					} while( iterator.Next( enterChildren ) );

					return;
				}
			}

			// Use reflection algorithm as fallback
			SearchVariablesWithReflection( referenceNode );
		}

		// Search through variables of an object with reflection
		private void SearchVariablesWithReflection( ReferenceNode referenceNode )
		{
			// Get filtered variables for this object
			VariableGetterHolder[] variables = GetFilteredVariablesForType( referenceNode.nodeObject.GetType() );
			for( int i = 0; i < variables.Length; i++ )
			{
				// When possible, don't search non-serializable variables
				if( searchSerializableVariablesOnly && !variables[i].isSerializable )
					continue;

				try
				{
					object variableValue = variables[i].Get( referenceNode.nodeObject );
					if( variableValue == null || variableValue.Equals( null ) )
						continue;

					// Values stored inside ICollection objects are searched using IEnumerable,
					// no need to have duplicate search entries
					if( !( variableValue is ICollection ) )
					{
#if ASSET_USAGE_ADDRESSABLES
						if( searchParameters.addressablesSupport && variableValue is AssetReference )
						{
							variableValue = GetAddressablesAssetReferenceValue( (AssetReference) variableValue );
							if( variableValue == null || variableValue.Equals( null ) )
								continue;
						}
#endif

						ReferenceNode searchResult = SearchObject( PreferablyGameObject( variableValue ) );
						if( searchResult != null && searchResult != referenceNode )
						{
							referenceNode.AddLinkTo( searchResult, ( variables[i].IsProperty ? "Property: " : "Variable: " ) + variables[i].Name );

							if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( variableValue as Object ) )
								searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, (Object) variableValue, variables[i].variable ) );
						}
					}

					if( variableValue is IEnumerable && !( variableValue is Transform ) )
					{
						// If the field is IEnumerable (possibly an array or collection), search through members of it
						// Note that Transform IEnumerable (children of the transform) is not iterated
						int index = 0;
						List<Object> foundReferences = null;
						foreach( object element in (IEnumerable) variableValue )
						{
							ReferenceNode searchResult = SearchObject( PreferablyGameObject( element ) );
							if( searchResult != null && searchResult != referenceNode )
							{
								referenceNode.AddLinkTo( searchResult, string.Concat( variables[i].IsProperty ? "Property: " : "Variable: ", variables[i].Name, "[", index + "]" ) );

								if( searchParameters.searchRefactoring != null && objectsToSearchSet.Contains( element as Object ) )
								{
									if( foundReferences == null )
										foundReferences = new List<Object>( 2 ) { (Object) element };
									else if( !foundReferences.Contains( (Object) element ) )
										foundReferences.Add( (Object) element );
								}
							}

							index++;
						}

						if( foundReferences != null )
						{
							for( int j = foundReferences.Count - 1; j >= 0; j-- )
								searchParameters.searchRefactoring( new ReflectionMatch( referenceNode.nodeObject, foundReferences[j], variableValue ) );
						}
					}
				}
				catch( UnassignedReferenceException ) { }
				catch( MissingReferenceException ) { }
				catch( MissingComponentException ) { }
				catch( NotImplementedException ) { }
				catch( Exception e )
				{
					// Unknown exceptions usually occur when variableValue is an IEnumerable and its enumerator throws an unhandled exception in MoveNext or Current
					StringBuilder sb = Utilities.stringBuilder;
					sb.Length = 0;
					sb.EnsureCapacity( callStack.Count * 50 + 1000 );

					sb.Append( "Skipped searching " ).Append( referenceNode.nodeObject.GetType().FullName ).Append( "." ).Append( variables[i].Name ).AppendLine( " because it threw exception:" ).Append( e ).AppendLine();

					Object latestUnityObjectInCallStack = AppendCallStackToStringBuilder( sb );
					Debug.LogWarning( sb.ToString(), latestUnityObjectInCallStack );
				}
			}
		}

		// Get filtered variables for a type
		private VariableGetterHolder[] GetFilteredVariablesForType( Type type )
		{
			VariableGetterHolder[] result;
			if( typeToVariables.TryGetValue( type, out result ) )
				return result;

			// This is the first time this type of object is seen, filter and cache its variables
			// Variable filtering process:
			// 1- skip Obsolete variables
			// 2- skip primitive types, enums and strings
			// 3- skip common Unity types that can't hold any references (e.g. Vector3, Rect, Color, Quaternion)
			// 
			// P.S. IsIgnoredUnityType() extension function handles steps 2) and 3)

			validVariables.Clear();

			// Filter the fields
			if( fieldModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) )
			{
				Type currType = type;
				while( currType != typeof( object ) )
				{
					FieldInfo[] fields = currType.GetFields( fieldModifiers );
					for( int i = 0; i < fields.Length; i++ )
					{
						FieldInfo field = fields[i];

						// Skip obsolete fields
						if( Attribute.IsDefined( field, typeof( ObsoleteAttribute ) ) )
							continue;

						// Skip primitive types
						if( field.FieldType.IsIgnoredUnityType() )
							continue;

#if UNITY_2021_2_OR_NEWER
						// "ref struct"s can't be accessed via reflection
						if( field.FieldType.IsByRefLike )
							continue;
#endif

						// Additional filtering for fields:
						// 1- Ignore "m_RectTransform", "m_CanvasRenderer" and "m_Canvas" fields of Graphic components
						string fieldName = field.Name;
						if( typeof( Graphic ).IsAssignableFrom( currType ) &&
							( fieldName == "m_RectTransform" || fieldName == "m_CanvasRenderer" || fieldName == "m_Canvas" ) )
							continue;

						VariableGetVal getter = field.CreateGetter( type );
						if( getter != null )
							validVariables.Add( new VariableGetterHolder( field, getter, searchSerializableVariablesOnly ? field.IsSerializable() : true ) );
					}

					currType = currType.BaseType;
				}
			}

			if( propertyModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) )
			{
				Type currType = type;
				while( currType != typeof( object ) )
				{
					PropertyInfo[] properties = currType.GetProperties( propertyModifiers );
					for( int i = 0; i < properties.Length; i++ )
					{
						PropertyInfo property = properties[i];

						// Skip obsolete properties
						if( Attribute.IsDefined( property, typeof( ObsoleteAttribute ) ) )
							continue;

						// Skip primitive types
						if( property.PropertyType.IsIgnoredUnityType() )
							continue;

#if UNITY_2021_2_OR_NEWER
						// "ref struct"s can't be accessed via reflection
						if( property.PropertyType.IsByRefLike )
							continue;
#endif

						// Skip properties without a getter function
						MethodInfo propertyGetter = property.GetGetMethod( true );
						if( propertyGetter == null )
							continue;

						// Skip indexer properties
						if( property.GetIndexParameters().Length > 0 )
							continue;

						// No need to check properties with 'override' keyword
						if( propertyGetter.GetBaseDefinition().DeclaringType != propertyGetter.DeclaringType )
							continue;

						string propertyName = property.Name;

						// Ignore "gameObject", "transform", "rectTransform" and "attachedRigidbody" properties of components to get more useful results
						if( typeof( Component ).IsAssignableFrom( currType ) && ( propertyName == "gameObject" ||
							propertyName == "transform" || propertyName == "attachedRigidbody" || propertyName == "rectTransform" ) )
							continue;
						// Ignore "canvasRenderer" and "canvas" properties of Graphic components to get more useful results
						else if( typeof( Graphic ).IsAssignableFrom( currType ) &&
							( propertyName == "canvasRenderer" || propertyName == "canvas" ) )
							continue;
						// Prevent accessing properties of Unity that instantiate an existing resource (causing memory leak)
						else if( typeof( MeshFilter ).IsAssignableFrom( currType ) && propertyName == "mesh" )
							continue;
						// Same as above
						else if( ( propertyName == "material" || propertyName == "materials" ) &&
							( typeof( Renderer ).IsAssignableFrom( currType ) || typeof( Collider ).IsAssignableFrom( currType ) ||
#if !UNITY_2019_3_OR_NEWER
#pragma warning disable 0618
							typeof( GUIText ).IsAssignableFrom( currType ) ||
#pragma warning restore 0618
#endif
							typeof( Collider2D ).IsAssignableFrom( currType ) ) )
							continue;
						// Ignore certain Material properties that are already searched via SearchMaterial function (also, if a material doesn't have a _Color or _BaseColor
						// property and its "color" property is called, it logs an error to the console, so this rule helps avoid that scenario, as well)
						else if( ( propertyName == "color" || propertyName == "mainTexture" ) && typeof( Material ).IsAssignableFrom( currType ) )
							continue;
						// Ignore "parameters" property of Animator since it doesn't contain any useful data and logs a warning to the console when Animator is inactive
						else if( typeof( Animator ).IsAssignableFrom( currType ) && propertyName == "parameters" )
							continue;
						// Ignore "spriteAnimator" property of TMP_Text component because this property adds a TMP_SpriteAnimator component to the object if it doesn't exist
						else if( propertyName == "spriteAnimator" && currType.Name == "TMP_Text" )
							continue;
						// Ignore "meshFilter" property of TextMeshPro and TMP_SubMesh components because this property adds a MeshFilter component to the object if it doesn't exist
						else if( propertyName == "meshFilter" && ( currType.Name == "TextMeshPro" || currType.Name == "TMP_SubMesh" ) )
							continue;
						// Ignore "users" property of TerrainData because it returns the Terrains in the scene that use that TerrainData. This causes issues with callStack because TerrainData
						// is already in callStack when Terrains are searched via "users" property of it and hence, Terrain->TerrainData references for that TerrainData can't be found in scenes
						// (this is how callStack works, it prevents searching an object if it's already in callStack to avoid infinite recursion)
						else if( propertyName == "users" && typeof( TerrainData ).IsAssignableFrom( currType ) )
							continue;
						else
						{
							VariableGetVal getter = property.CreateGetter();
							if( getter != null )
								validVariables.Add( new VariableGetterHolder( property, getter, searchSerializableVariablesOnly ? property.IsSerializable() : true ) );
						}
					}

					currType = currType.BaseType;
				}
			}

			result = validVariables.ToArray();

			// Cache the filtered fields
			typeToVariables.Add( type, result );

			return result;
		}

		// Credit: http://answers.unity.com/answers/425602/view.html
		// Returns the raw System.Object value of a SerializedProperty
		private object GetRawSerializedPropertyValue( SerializedProperty property )
		{
			object result = property.serializedObject.targetObject;
			string[] path = property.propertyPath.Replace( ".Array.data[", "[" ).Split( '.' );
			for( int i = 0; i < path.Length; i++ )
			{
				string pathElement = path[i];

				int arrayStartIndex = pathElement.IndexOf( '[' );
				if( arrayStartIndex < 0 )
					result = GetFieldValue( result, pathElement );
				else
				{
					string variableName = pathElement.Substring( 0, arrayStartIndex );

					int arrayEndIndex = pathElement.IndexOf( ']', arrayStartIndex + 1 );
					int arrayElementIndex = int.Parse( pathElement.Substring( arrayStartIndex + 1, arrayEndIndex - arrayStartIndex - 1 ) );
					result = GetFieldValue( result, variableName, arrayElementIndex );
				}
			}

			return result;
		}

		// Credit: http://answers.unity.com/answers/425602/view.html
		private object GetFieldValue( object source, string fieldName )
		{
			if( source == null )
				return null;

			FieldInfo fieldInfo = null;
			Type type = source.GetType();
			while( fieldInfo == null && type != typeof( object ) )
			{
				fieldInfo = type.GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly );
				type = type.BaseType;
			}

			if( fieldInfo != null )
				return fieldInfo.GetValue( source );

			PropertyInfo propertyInfo = null;
			type = source.GetType();
			while( propertyInfo == null && type != typeof( object ) )
			{
				propertyInfo = type.GetProperty( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase );
				type = type.BaseType;
			}

			if( propertyInfo != null )
				return propertyInfo.GetValue( source, null );

			if( fieldName.Length > 2 && fieldName.StartsWith( "m_", StringComparison.OrdinalIgnoreCase ) )
				return GetFieldValue( source, fieldName.Substring( 2 ) );

			return null;
		}

		// Credit: http://answers.unity.com/answers/425602/view.html
		private object GetFieldValue( object source, string fieldName, int arrayIndex )
		{
			IEnumerable enumerable = GetFieldValue( source, fieldName ) as IEnumerable;
			if( enumerable == null )
				return null;

			if( enumerable is IList )
				return ( (IList) enumerable )[arrayIndex];

			IEnumerator enumerator = enumerable.GetEnumerator();
			for( int i = 0; i <= arrayIndex; i++ )
				enumerator.MoveNext();

			return enumerator.Current;
		}

#if ASSET_USAGE_ADDRESSABLES
		private Object GetAddressablesAssetReferenceValue( AssetReference assetReference )
		{
			Object result = assetReference.editorAsset;
			if( !result )
				return null;

			string subObjectName = assetReference.SubObjectName;
			if( !string.IsNullOrEmpty( subObjectName ) )
			{
				if( result is SpriteAtlas )
				{
					Sprite[] packedSprites = spriteAtlasPackedSpritesGetter( (SpriteAtlas) result );
					if( packedSprites != null )
					{
						for( int i = 0; i < packedSprites.Length; i++ )
						{
							if( packedSprites[i] && packedSprites[i].name == subObjectName )
								return packedSprites[i];
						}
					}
				}
				else
				{
					Type subObjectType = (Type) assetReferenceSubObjectTypeGetter.GetValue( assetReference, null ) ?? typeof( Object );
					Object[] subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath( AssetDatabase.GetAssetPath( result ) );
					for( int k = 0; k < subAssets.Length; k++ )
					{
						if( subAssets[k] && subAssets[k].name == subObjectName && subObjectType.IsAssignableFrom( subAssets[k].GetType() ) )
							return subAssets[k];
					}
				}
			}

			return result;
		}
#endif

		// Iterates over all occurrences of specific key-value pairs in string
		// Example1: #include "VALUE"  valuePrefix=#include, valueWrapperChar="
		// Example2: "guid": "VALUE"  valuePrefix="guid", valueWrapperChar="
		private void IterateOverValuesInString( string str, string[] valuePrefixes, char valueWrapperChar, Action<string> valueAction )
		{
			for( int i = 0; i < valuePrefixes.Length; i++ )
			{
				string valuePrefix = valuePrefixes[i];
				int valueStartIndex, valueEndIndex = 0;
				while( true )
				{
					valueStartIndex = str.IndexOf( valuePrefix, valueEndIndex );
					if( valueStartIndex < 0 )
						break;

					valueStartIndex = str.IndexOf( valueWrapperChar, valueStartIndex + valuePrefix.Length );
					if( valueStartIndex < 0 )
						break;

					valueStartIndex++;
					valueEndIndex = str.IndexOf( valueWrapperChar, valueStartIndex );
					if( valueEndIndex < 0 )
						break;

					if( valueEndIndex > valueStartIndex )
						valueAction( str.Substring( valueStartIndex, valueEndIndex - valueStartIndex ) );
				}
			}
		}

		// If obj is Component, switches to its GameObject
		private object PreferablyGameObject( object obj )
		{
			Component component = obj as Component;
			return ( component != null && !component.Equals( null ) ) ? component.gameObject : obj;
		}
	}
}