using System;
using System.Collections.Generic;
using JetBrains.Annotations;
#if TextMeshPro
using TMPro;
#endif
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using UnityEngine.UI;

namespace VRC.SDKBase.Validation
{
    public static class WorldValidation
    {
        private static readonly Lazy<string> _debugCategoryName = new Lazy<string>(InitializeLogging);
        private static string DebugCategoryName => _debugCategoryName.Value;

        private static string InitializeLogging()
        {
            const string categoryName = "WorldValidation";
            VRC.Core.Logger.DescribeCategory(categoryName, VRC.Core.Logger.Color.red);
            VRC.Core.Logger.EnableCategory(categoryName);
            return categoryName;
        }

        static string[] ComponentTypeWhiteList = null;

        public enum WhiteListConfiguration
        {
            None,
            VRCSDK2,
            VRCSDK3,
            Unchanged
        }

        static WhiteListConfiguration ComponentTypeWhiteListConfiguration = WhiteListConfiguration.None;

        static readonly string[] ComponentTypeWhiteListCommon = new string[]
        {
            #if UNITY_STANDALONE
            "UnityEngine.Rendering.PostProcessing.PostProcessDebug",
            "UnityEngine.Rendering.PostProcessing.PostProcessLayer",
            "UnityEngine.Rendering.PostProcessing.PostProcessVolume",
            #endif
            "VRC.Core.PipelineManager",
            "UiInputField",
            "VRCProjectSettings",
            "DynamicBone",
            "DynamicBoneCollider",
            "TMPro.TMP_Dropdown",
            "TMPro.TMP_InputField",
            "TMPro.TMP_ScrollbarEventHandler",
            "TMPro.TMP_SelectionCaret",
            "TMPro.TMP_SpriteAnimator",
            "TMPro.TMP_SubMesh",
            "TMPro.TMP_SubMeshUI",
            "TMPro.TMP_Text",
            "TMPro.TextMeshPro",
            "TMPro.TextMeshProUGUI",
            "TMPro.TextContainer",
            "TMPro.TMP_Dropdown+DropdownItem",
            "UnityEngine.EventSystems.EventSystem",
            "UnityEngine.EventSystems.EventTrigger",
            "UnityEngine.EventSystems.UIBehaviour",
            "UnityEngine.EventSystems.BaseInput",
            "UnityEngine.EventSystems.BaseInputModule",
            "UnityEngine.EventSystems.PointerInputModule",
            "UnityEngine.EventSystems.StandaloneInputModule",
            "UnityEngine.EventSystems.TouchInputModule",
            "UnityEngine.EventSystems.BaseRaycaster",
            "UnityEngine.EventSystems.PhysicsRaycaster",
            "UnityEngine.UI.Button",
            "UnityEngine.UI.Dropdown",
            "UnityEngine.UI.Dropdown+DropdownItem",
            "UnityEngine.UI.Graphic",
            "UnityEngine.UI.GraphicRaycaster",
            "UnityEngine.UI.Image",
            "UnityEngine.UI.InputField",
            "UnityEngine.UI.Mask",
            "UnityEngine.UI.MaskableGraphic",
            "UnityEngine.UI.RawImage",
            "UnityEngine.UI.RectMask2D",
            "UnityEngine.UI.Scrollbar",
            "UnityEngine.UI.ScrollRect",
            "UnityEngine.UI.Selectable",
            "UnityEngine.UI.Slider",
            "UnityEngine.UI.Text",
            "UnityEngine.UI.Toggle",
            "UnityEngine.UI.ToggleGroup",
            "UnityEngine.UI.AspectRatioFitter",
            "UnityEngine.UI.CanvasScaler",
            "UnityEngine.UI.ContentSizeFitter",
            "UnityEngine.UI.GridLayoutGroup",
            "UnityEngine.UI.HorizontalLayoutGroup",
            "UnityEngine.UI.HorizontalOrVerticalLayoutGroup",
            "UnityEngine.UI.LayoutElement",
            "UnityEngine.UI.LayoutGroup",
            "UnityEngine.UI.VerticalLayoutGroup",
            "UnityEngine.UI.BaseMeshEffect",
            "UnityEngine.UI.Outline",
            "UnityEngine.UI.PositionAsUV1",
            "UnityEngine.UI.Shadow",
            "OVRLipSync",
            "OVRLipSyncContext",
            "OVRLipSyncContextBase",
            "OVRLipSyncContextCanned",
            "OVRLipSyncContextMorphTarget",
            "OVRLipSyncContextTextureFlip",
            "ONSPReflectionZone",
            "OculusSpatializerUnity",
            "ONSPAmbisonicsNative",
            "ONSPAudioSource",
            "RootMotion.FinalIK.BipedIK",
            "RootMotion.FinalIK.FingerRig",
            "RootMotion.FinalIK.Grounder",
            "RootMotion.FinalIK.GrounderBipedIK",
            "RootMotion.FinalIK.GrounderFBBIK",
            "RootMotion.FinalIK.GrounderIK",
            "RootMotion.FinalIK.GrounderQuadruped",
            "RootMotion.FinalIK.GrounderVRIK",
            "RootMotion.FinalIK.AimIK",
            "RootMotion.FinalIK.CCDIK",
            "RootMotion.FinalIK.FABRIK",
            "RootMotion.FinalIK.FABRIKRoot",
            "RootMotion.FinalIK.FullBodyBipedIK",
            "RootMotion.FinalIK.IK",
            "RootMotion.FinalIK.IKExecutionOrder",
            "RootMotion.FinalIK.LegIK",
            "RootMotion.FinalIK.LimbIK",
            "RootMotion.FinalIK.LookAtIK",
            "RootMotion.FinalIK.TrigonometricIK",
            "RootMotion.FinalIK.VRIK",
            "RootMotion.FinalIK.FBBIKArmBending",
            "RootMotion.FinalIK.FBBIKHeadEffector",
            "RootMotion.FinalIK.TwistRelaxer",
            "RootMotion.FinalIK.InteractionObject",
            "RootMotion.FinalIK.InteractionSystem",
            "RootMotion.FinalIK.InteractionTarget",
            "RootMotion.FinalIK.InteractionTrigger",
            "RootMotion.FinalIK.GenericPoser",
            "RootMotion.FinalIK.HandPoser",
            "RootMotion.FinalIK.Poser",
            "RootMotion.FinalIK.RagdollUtility",
            "RootMotion.FinalIK.RotationLimit",
            "RootMotion.FinalIK.RotationLimitAngle",
            "RootMotion.FinalIK.RotationLimitHinge",
            "RootMotion.FinalIK.RotationLimitPolygonal",
            "RootMotion.FinalIK.RotationLimitSpline",
            "RootMotion.FinalIK.AimPoser",
            "RootMotion.FinalIK.Amplifier",
            "RootMotion.FinalIK.BodyTilt",
            "RootMotion.FinalIK.HitReaction",
            "RootMotion.FinalIK.HitReactionVRIK",
            "RootMotion.FinalIK.Inertia",
            "RootMotion.FinalIK.OffsetModifier",
            "RootMotion.FinalIK.OffsetModifierVRIK",
            "RootMotion.FinalIK.OffsetPose",
            "RootMotion.FinalIK.Recoil",
            "RootMotion.FinalIK.ShoulderRotator",
            "RootMotion.Dynamics.AnimationBlocker",
            "RootMotion.Dynamics.BehaviourBase",
            "RootMotion.Dynamics.BehaviourFall",
            "RootMotion.Dynamics.BehaviourPuppet",
            "RootMotion.Dynamics.JointBreakBroadcaster",
            "RootMotion.Dynamics.MuscleCollisionBroadcaster",
            "RootMotion.Dynamics.PressureSensor",
            "RootMotion.Dynamics.Prop",
            "RootMotion.Dynamics.PropRoot",
            "RootMotion.Dynamics.PuppetMaster",
            "RootMotion.Dynamics.PuppetMasterSettings",
            // TODO: remove these if they are only needed in editor
            "RootMotion.Dynamics.BipedRagdollCreator",
            "RootMotion.Dynamics.RagdollCreator",
            "RootMotion.Dynamics.RagdollEditor",
            //
            "RootMotion.SolverManager",
            "RootMotion.TriggerEventBroadcaster",
            "UnityEngine.WindZone",
            "UnityEngine.Tilemaps.Tilemap",
            "UnityEngine.Tilemaps.TilemapRenderer",
            "UnityEngine.Terrain",
            "UnityEngine.Tree",
            "UnityEngine.SpriteMask",
            "UnityEngine.Grid",
            "UnityEngine.GridLayout",
            "UnityEngine.AudioSource",
            "UnityEngine.AudioReverbZone",
            "UnityEngine.AudioLowPassFilter",
            "UnityEngine.AudioHighPassFilter",
            "UnityEngine.AudioDistortionFilter",
            "UnityEngine.AudioEchoFilter",
            "UnityEngine.AudioChorusFilter",
            "UnityEngine.AudioReverbFilter",
            "UnityEngine.AudioListener", // will be force-disabled on load, but can't be removed since it's a dependency for some of the above
            "UnityEngine.Playables.PlayableDirector",
            "UnityEngine.TerrainCollider",
            "UnityEngine.Canvas",
            "UnityEngine.CanvasGroup",
            "UnityEngine.CanvasRenderer",
            "UnityEngine.TextMesh",
            "UnityEngine.Animator",
            "UnityEngine.AI.NavMeshAgent",
            "UnityEngine.AI.NavMeshObstacle",
            "UnityEngine.AI.OffMeshLink",
            "UnityEngine.Cloth",
            "UnityEngine.WheelCollider",
            "UnityEngine.Rigidbody",
            "UnityEngine.Joint",
            "UnityEngine.HingeJoint",
            "UnityEngine.SpringJoint",
            "UnityEngine.FixedJoint",
            "UnityEngine.CharacterJoint",
            "UnityEngine.ConfigurableJoint",
            "UnityEngine.ConstantForce",
            "UnityEngine.Collider",
            "UnityEngine.BoxCollider",
            "UnityEngine.SphereCollider",
            "UnityEngine.MeshCollider",
            "UnityEngine.CapsuleCollider",
            "UnityEngine.CharacterController",
            "UnityEngine.ParticleSystem",
            "UnityEngine.ParticleSystemRenderer",
            "UnityEngine.BillboardRenderer",
            "UnityEngine.Camera",
            "UnityEngine.FlareLayer",
            "UnityEngine.SkinnedMeshRenderer",
            "UnityEngine.Renderer",
            "UnityEngine.TrailRenderer",
            "UnityEngine.LineRenderer",
            "UnityEngine.GUIElement",
            "UnityEngine.GUILayer",
            "UnityEngine.Light",
            "UnityEngine.LightProbeGroup",
            "UnityEngine.LightProbeProxyVolume",
            "UnityEngine.LODGroup",
            "UnityEngine.ReflectionProbe",
            "UnityEngine.SpriteRenderer",
            "UnityEngine.Transform",
            "UnityEngine.RectTransform",
            "UnityEngine.Rendering.SortingGroup",
            "UnityEngine.Projector",
            "UnityEngine.OcclusionPortal",
            "UnityEngine.OcclusionArea",
            "UnityEngine.LensFlare",
            "UnityEngine.Skybox",
            "UnityEngine.MeshFilter",
            "UnityEngine.Halo",
            "UnityEngine.MeshRenderer",
            "UnityEngine.Collider2D",
            "UnityEngine.Rigidbody2D",
            "UnityEngine.CompositeCollider2D",
            "UnityEngine.ConstantForce2D",
            "UnityEngine.AreaEffector2D",
            "UnityEngine.CapsuleCollider2D",
            "UnityEngine.DistanceJoint2D",
            "UnityEngine.EdgeCollider2D",
            "UnityEngine.Effector2D",
            "UnityEngine.BoxCollider2D",
            "UnityEngine.CircleCollider2D",
            "UnityEngine.FixedJoint2D",
            "UnityEngine.HingeJoint2D",
            "UnityEngine.FrictionJoint2D",
            "UnityEngine.PlatformEffector2D",
            "UnityEngine.PointEffector2D",
            "UnityEngine.PolygonCollider2D",
            "UnityEngine.SliderJoint2D",
            "UnityEngine.SurfaceEffector2D",
            "UnityEngine.RelativeJoint2D",
            "UnityEngine.TargetJoint2D",
            "UnityEngine.WheelJoint2D",
            "UnityEngine.Joint2D",
            "UnityEngine.ParticleSystemForceField"
        };

        static readonly string[] ComponentTypeWhiteListSdk2 = new string[]
        {
            #if UNITY_STANDALONE
            "VRCSDK2.VRC_CustomRendererBehaviour",
            "VRCSDK2.VRC_MidiNoteIn",
            "VRCSDK2.scripts.Scenes.VRC_Panorama",
            "VRCSDK2.VRC_Water",
            "UnityStandardAssets.Water.WaterBasic",
            "UnityStandardAssets.Water.Displace",
            "UnityStandardAssets.Water.GerstnerDisplace",
            "UnityStandardAssets.Water.PlanarReflection",
            "UnityStandardAssets.Water.SpecularLighting",
            "UnityStandardAssets.Water.Water",
            "UnityStandardAssets.Water.WaterBase",
            "UnityStandardAssets.Water.WaterTile",
            #endif
            "VRCSDK2.VRCTriggerRelay",
            "VRCSDK2.VRC_AudioBank",
            "VRCSDK2.VRC_DataStorage",
            "VRCSDK2.VRC_EventHandler",
            "VRCSDK2.VRC_IKFollower",
            "VRCSDK2.VRC_Label",
            "VRCSDK2.VRC_KeyEvents",
            "VRCSDK2.VRC_PhysicsRoot",
            "VRCSDK2.VRC_CombatSystem",
            "VRCSDK2.VRC_DestructibleStandard",
            "VRC_VisualDamage",
            "VRCSDK2.VRC_OscButtonIn",
            "VRCSDK2.VRC_GunStats",
            "VRCSDK2.VRC_JukeBox",
            "VRCSDK2.VRC_AddDamage",
            "VRCSDK2.VRC_AddHealth",
            "VRCSDK2.VRC_AvatarCalibrator",
            "VRCSDK2.VRC_AvatarPedestal",
            "VRCSDK2.VRC_NPCSpawn",
            "VRCSDK2.VRC_ObjectSpawn",
            "VRCSDK2.VRC_ObjectSync",
            "VRCSDK2.VRC_Pickup",
            "VRCSDK2.VRC_PortalMarker",
            "VRCSDK2.VRC_SlideShow",
            "VRCSDK2.VRC_SpatialAudioSource",
            "VRCSDK2.VRC_StationInput",
            "VRCSDK2.VRC_SyncAnimation",
            "VRCSDK2.VRC_SyncVideoPlayer",
            "VRCSDK2.VRC_SyncVideoStream",
            "VRCSDK2.VRC_VideoScreen",
            "VRCSDK2.VRC_VideoSpeaker",
            "VRCSDK2.VRC_PlayerAudioOverride",
            "VRCSDK2.VRC_MirrorReflection",
            "VRCSDK2.VRC_PlayerMods",
            "VRCSDK2.VRC_SceneDescriptor",
            "VRCSDK2.VRC_SceneResetPosition",
            "VRCSDK2.VRC_SceneSmoothShift",
            "VRCSDK2.VRC_SpecialLayer",
            "VRCSDK2.VRC_Station",
            "VRCSDK2.VRC_StereoObject",
            "VRCSDK2.VRC_TimedEvents",
            "VRCSDK2.VRC_Trigger",
            "VRCSDK2.VRC_TriggerColliderEventTrigger",
            "VRCSDK2.VRC_UseEvents",
            "VRCSDK2.VRC_UiShape",
            "UnityEngine.Animation",
            #if !UNITY_2019_4_OR_NEWER
            "UnityEngine.GUIText",
            "UnityEngine.GUITexture",
            #endif
            "UnityEngine.Video.VideoPlayer",
            "PhysSound.PhysSoundBase",
            "PhysSound.PhysSoundObject",
            "PhysSound.PhysSoundTempAudio",
            "PhysSound.PhysSoundTempAudioPool",
            "PhysSound.PhysSoundTerrain",
            "RealisticEyeMovements.EyeAndHeadAnimator",
            "RealisticEyeMovements.LookTargetController",
            "UnityStandardAssets.Cameras.AbstractTargetFollower",
            "UnityStandardAssets.Cameras.AutoCam",
            "UnityStandardAssets.Cameras.FreeLookCam",
            "UnityStandardAssets.Cameras.HandHeldCam",
            "UnityStandardAssets.Cameras.LookatTarget",
            "UnityStandardAssets.Cameras.PivotBasedCameraRig",
            "UnityStandardAssets.Cameras.ProtectCameraFromWallClip",
            "UnityStandardAssets.Cameras.TargetFieldOfView",
            "UnityStandardAssets.Characters.FirstPerson.FirstPersonController",
            "UnityStandardAssets.Characters.FirstPerson.HeadBob",
            "UnityStandardAssets.Characters.FirstPerson.RigidbodyFirstPersonController",
            "UnityStandardAssets.Vehicles.Ball.Ball",
            "UnityStandardAssets.Vehicles.Ball.BallUserControl",
            "UnityStandardAssets.Characters.ThirdPerson.AICharacterControl",
            "UnityStandardAssets.Characters.ThirdPerson.ThirdPersonCharacter",
            "UnityStandardAssets.Characters.ThirdPerson.ThirdPersonUserControl",
            "UnityStandardAssets.CrossPlatformInput.AxisTouchButton",
            "UnityStandardAssets.CrossPlatformInput.ButtonHandler",
            "UnityStandardAssets.CrossPlatformInput.InputAxisScrollbar",
            "UnityStandardAssets.CrossPlatformInput.Joystick",
            "UnityStandardAssets.CrossPlatformInput.MobileControlRig",
            "UnityStandardAssets.CrossPlatformInput.TiltInput",
            "UnityStandardAssets.CrossPlatformInput.TouchPad",
            "UnityStandardAssets.Effects.AfterburnerPhysicsForce",
            "UnityStandardAssets.Effects.ExplosionFireAndDebris",
            "UnityStandardAssets.Effects.ExplosionPhysicsForce",
            "UnityStandardAssets.Effects.Explosive",
            "UnityStandardAssets.Effects.ExtinguishableParticleSystem",
            "UnityStandardAssets.Effects.FireLight",
            "UnityStandardAssets.Effects.Hose",
            "UnityStandardAssets.Effects.ParticleSystemMultiplier",
            "UnityStandardAssets.Effects.SmokeParticles",
            "UnityStandardAssets.Effects.WaterHoseParticles",
            "UnityStandardAssets.Utility.ActivateTrigger",
            "UnityStandardAssets.Utility.AutoMoveAndRotate",
            "UnityStandardAssets.Utility.DragRigidbody",
            "UnityStandardAssets.Utility.DynamicShadowSettings",
            "UnityStandardAssets.Utility.FollowTarget",
            "UnityStandardAssets.Utility.FPSCounter",
            "UnityStandardAssets.Utility.ObjectResetter",
            "UnityStandardAssets.Utility.ParticleSystemDestroyer",
            #if !UNITY_2019_4_OR_NEWER
            "UnityStandardAssets.Utility.SimpleActivatorMenu",
            #endif
            "UnityStandardAssets.Utility.SimpleMouseRotator",
            "UnityStandardAssets.Utility.SmoothFollow",
            "UnityStandardAssets.Utility.TimedObjectActivator",
            "UnityStandardAssets.Utility.TimedObjectDestructor",
            "UnityStandardAssets.Utility.WaypointCircuit",
            "UnityStandardAssets.Utility.WaypointProgressTracker",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAiControl",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneAudio",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneController",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneControlSurfaceAnimator",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplanePropellerAnimator",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl2Axis",
            "UnityStandardAssets.Vehicles.Aeroplane.AeroplaneUserControl4Axis",
            "UnityStandardAssets.Vehicles.Aeroplane.JetParticleEffect",
            "UnityStandardAssets.Vehicles.Aeroplane.LandingGear",
            "UnityStandardAssets.Vehicles.Car.BrakeLight",
            "UnityStandardAssets.Vehicles.Car.CarAIControl",
            "UnityStandardAssets.Vehicles.Car.CarAudio",
            "UnityStandardAssets.Vehicles.Car.CarController",
            "UnityStandardAssets.Vehicles.Car.CarSelfRighting",
            "UnityStandardAssets.Vehicles.Car.CarUserControl",
            "UnityStandardAssets.Vehicles.Car.Mudguard",
            "UnityStandardAssets.Vehicles.Car.SkidTrail",
            "UnityStandardAssets.Vehicles.Car.Suspension",
            "UnityStandardAssets.Vehicles.Car.WheelEffects",
            "RenderHeads.Media.AVProVideo.ApplyToMaterial",
            "RenderHeads.Media.AVProVideo.ApplyToMesh",
            "RenderHeads.Media.AVProVideo.AudioOutput",
            "RenderHeads.Media.AVProVideo.DisplayIMGUI",
            "RenderHeads.Media.AVProVideo.DisplayUGUI",
            "RenderHeads.Media.AVProVideo.MediaPlayer",
            "RenderHeads.Media.AVProVideo.SubtitlesUGUI",
            "AlphaButtonClickMask",
            "EventSystemChecker"
        };

        static readonly string[] ComponentTypeWhiteListSdk3 = new string[]
        {
            "VRC.SDK3.VRCDestructibleStandard",
            "VRC.SDK3.Components.VRCVisualDamage",
            "VRC.SDK3.Components.VRCAvatarPedestal",
            "VRC.SDK3.Components.VRCPickup",
            "VRC.SDK3.Components.VRCPortalMarker",
            "VRC.SDK3.Components.VRCSpatialAudioSource",
            "VRC.SDK3.Components.VRCMirrorReflection",
            "VRC.SDK3.Components.VRCSceneDescriptor",
            "VRC.SDK3.Components.VRCStation",
            "VRC.SDK3.Components.VRCUiShape",
            "VRC.SDK3.Components.VRCObjectSync",
            "VRC.SDK3.Components.VRCObjectPool",
            "VRC.SDK3.Components.VRCInputFieldKeyboardOverride",
#if VRC_ENABLE_PLAYER_PERSISTENCE
            "VRC.SDK3.Components.VRCPlayerObject",
#endif
#if VRC_ENABLE_PLAYER_PERSISTENCE || VRC_ENABLE_INSTANCE_PERSISTENCE
            "VRC.SDK3.Components.VRCEnablePersistence",
#endif
            "VRC.SDK3.Video.Components.VRCUnityVideoPlayer",
            "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoPlayer",
            "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoScreen",
            "VRC.SDK3.Video.Components.AVPro.VRCAVProVideoSpeaker",
            "VRC.SDK3.Midi.VRCMidiListener",
            "VRC.SDK3.Midi.VRCMidiPlayer",
            "VRC.SDK3.Components.VRCCameraDollyAnimation",
            "VRC.SDK3.Components.VRCCameraDollyPath",
            "VRC.SDK3.Components.VRCCameraDollyPathPoint",
            "VRC.Udon.UdonBehaviour",
            "VRC.Udon.AbstractUdonBehaviourEventProxy",
            "UnityEngine.Animations.AimConstraint",
            "UnityEngine.Animations.LookAtConstraint",
            "UnityEngine.Animations.ParentConstraint",
            "UnityEngine.Animations.PositionConstraint",
            "UnityEngine.Animations.RotationConstraint",
            "UnityEngine.Animations.ScaleConstraint",
            "UnityEngine.ParticleSystemForceField",
            "Unity.AI.Navigation.NavMeshSurface",
            "Unity.AI.Navigation.NavMeshLink",
            "Unity.AI.Navigation.NavMeshModifier",
            "Unity.AI.Navigation.NavMeshModifierVolume",
            "Cinemachine.Cinemachine3rdPersonAim",
            "Cinemachine.CinemachineBlendListCamera",
            "Cinemachine.CinemachineBrain",
            "Cinemachine.CinemachineCameraOffset",
            "Cinemachine.CinemachineClearShot",
            "Cinemachine.CinemachineCollider",
            "Cinemachine.CinemachineConfiner",
            "Cinemachine.CinemachineDollyCart",
            "Cinemachine.CinemachineExternalCamera",
            "Cinemachine.CinemachineFollowZoom",
            "Cinemachine.CinemachineFreeLook",
            "Cinemachine.CinemachineMixingCamera",
            "Cinemachine.CinemachinePath",
            "Cinemachine.CinemachinePipeline",
            "Cinemachine.CinemachinePixelPerfect",
            "Cinemachine.CinemachineRecomposer",
            "Cinemachine.CinemachineSmoothPath",
            "Cinemachine.CinemachineStateDrivenCamera",
            "Cinemachine.CinemachineStoryboard",
            "Cinemachine.CinemachineTargetGroup",
            "Cinemachine.CinemachineVirtualCamera",
            "Cinemachine.Cinemachine3rdPersonFollow",
            "Cinemachine.CinemachineBasicMultiChannelPerlin",
            "Cinemachine.CinemachineComposer",
            "Cinemachine.CinemachineFramingTransposer",
            "Cinemachine.CinemachineGroupComposer",
            "Cinemachine.CinemachineHardLockToTarget",
            "Cinemachine.CinemachineHardLookAt",
            "Cinemachine.CinemachineOrbitalTransposer",
            "Cinemachine.CinemachinePOV",
            "Cinemachine.CinemachineSameAsFollowTarget",
            "Cinemachine.CinemachineTrackedDolly",
            "Cinemachine.CinemachineTransposer",
            "Cinemachine.CinemachineCore",
        };

        public static readonly string[] ShaderWhiteList = new string[]
        {
            "VRChat/Mobile/Standard Lite",
            "VRChat/Mobile/Diffuse",
            "VRChat/Mobile/Bumped Diffuse",
            "VRChat/Mobile/Bumped Mapped Specular",
            "VRChat/Mobile/Toon Lit",
            "VRChat/Mobile/MatCap Lit",
            "VRChat/Mobile/Lightmapped",
            "VRChat/Mobile/Skybox",
            "VRChat/Mobile/Particles/Additive",
            "VRChat/Mobile/Particles/Multiply",
            "VRChat/Mobile/World/Supersampled UI",
            "FX/MirrorReflection",
            "UI/Default",
        };

        private static readonly HashSet<int> scannedObjects = new HashSet<int>();

        private static void ConfigureWhiteList(WhiteListConfiguration config)
        {
            if(ComponentTypeWhiteListConfiguration == config ||
               config == WhiteListConfiguration.Unchanged)
            {
                return;
            }

            List<string> concatenation = new List<string>();
            concatenation.AddRange(ComponentTypeWhiteListCommon);

            switch(config)
            {
                case WhiteListConfiguration.VRCSDK2:
                    concatenation.AddRange(ComponentTypeWhiteListSdk2);
                    break;
                case WhiteListConfiguration.VRCSDK3:
                    concatenation.AddRange(ComponentTypeWhiteListSdk3);
                    break;
            }

            ComponentTypeWhiteListConfiguration = config;
            ComponentTypeWhiteList = concatenation.ToArray();
        }

        [PublicAPI]
        public static void RemoveIllegalComponents(List<GameObject> targets, WhiteListConfiguration config, bool retry = true, HashSet<Type> tagWhitelistedTypes = null)
        {
            ConfigureWhiteList(config);

            HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes($"world{config}", ComponentTypeWhiteList);

            // combine whitelist types from world tags with cached whitelist
            tagWhitelistedTypes?.UnionWith(whitelist);

            foreach(GameObject target in targets)
            {
#if VRC_CLIENT
                ValidationUtils.RemoveIllegalComponents(target, tagWhitelistedTypes ?? whitelist, retry, true);
#else
                ValidationUtils.RemoveIllegalComponents(target, tagWhitelistedTypes ?? whitelist, retry, true, excludeEditorOnly:true, allowRemovingAssets:false);
#endif

                SecurityScan(target);
                AddScanned(target);

                // Must be called after AddScanned to avoid infinite recursion.
                ScanDropdownTemplates(target, whitelist, config == WhiteListConfiguration.VRCSDK3);
            }
        }

        private static void AddScanned(GameObject obj)
        {
            if(obj == null)
                return;

            if(!scannedObjects.Contains(obj.GetInstanceID()))
                scannedObjects.Add(obj.GetInstanceID());

            for(int idx = 0; idx < obj.transform.childCount; ++idx)
                AddScanned(obj.transform.GetChild(idx)?.gameObject);
        }

        private static bool WasScanned(GameObject obj)
        {
            return scannedObjects.Contains(obj.GetInstanceID());
        }

        [PublicAPI]
        public static void ScanGameObject(GameObject target, WhiteListConfiguration config)
        {
            if(WasScanned(target))
            {
                return;
            }

            ConfigureWhiteList(config);
            HashSet<Type> whitelist = ValidationUtils.WhitelistedTypes("world" + config, ComponentTypeWhiteList);
            ScanGameObject(target, whitelist, config == WhiteListConfiguration.VRCSDK3);
        }

        private static void ScanGameObject(GameObject target, HashSet<Type> whitelist, bool isSDK3)
        {
            if(WasScanned(target))
            {
                return;
            }

#if VRC_CLIENT
            ValidationUtils.RemoveIllegalComponents(target, whitelist);
#else
            ValidationUtils.RemoveIllegalComponents(target, whitelist, excludeEditorOnly:true, allowRemovingAssets:false);
#endif

            SecurityScan(target);
#if VRC_CLIENT && UDON
            if (isSDK3)
            {
                Core.UnityEventFilter.FilterEvents(target);
            }
#endif
            AddScanned(target);

            // Must be called after AddScanned to avoid infinite recursion.
            ScanDropdownTemplates(target, whitelist, isSDK3);
        }

        [PublicAPI]
        public static void ClearScannedGameObjectCache()
        {
            scannedObjects.Clear();
        }

        [PublicAPI]
        public static IEnumerable<Shader> FindIllegalShaders(GameObject target)
        {
            return ValidationUtils.FindIllegalShaders(target, ShaderWhiteList);
        }

        private static void SecurityScan(GameObject target)
        {
            PlayableDirector[] playableDirectors = target.GetComponentsInChildren<PlayableDirector>(true);
            foreach(PlayableDirector playableDirector in playableDirectors)
            {
                StripPlayableDirectorWithPrefabs(playableDirector);
            }

            UnityEngine.Video.VideoPlayer[] videoPlayers = target.GetComponentsInChildren<UnityEngine.Video.VideoPlayer>(true);
            foreach (UnityEngine.Video.VideoPlayer videoPlayer in videoPlayers)
            {
                AddAudioSourceToVideoPlayer(videoPlayer);
            }
        }

        private static void ScanDropdownTemplates(GameObject target, HashSet<Type> whitelist, bool isSDK3)
        {
            Dropdown[] dropdowns = target.GetComponentsInChildren<Dropdown>(true);
            foreach(Dropdown dropdown in dropdowns)
            {
                if(dropdown == null)
                {
                    continue;
                }

                RectTransform dropdownTemplate = dropdown.template;
                if(dropdownTemplate == null)
                {
                    continue;
                }

                ScanGameObject(dropdownTemplate.transform.root.gameObject, whitelist, isSDK3);
            }

            #if TextMeshPro
            TMP_Dropdown[] tmpDropdowns = target.GetComponentsInChildren<TMP_Dropdown>(true);
            foreach(TMP_Dropdown textMeshProDropdown in tmpDropdowns)
            {
                if(textMeshProDropdown == null)
                {
                    continue;
                }

                RectTransform dropdownTemplate = textMeshProDropdown.template;
                if(dropdownTemplate == null)
                {
                    continue;
                }

                ScanGameObject(dropdownTemplate.transform.root.gameObject, whitelist, isSDK3);
            }
            #endif
        }

        private static void StripPlayableDirectorWithPrefabs(PlayableDirector playableDirector)
        {
            if(!(playableDirector.playableAsset is UnityEngine.Timeline.TimelineAsset timelineAsset))
                return;

            IEnumerable<TrackAsset> tracks = timelineAsset.GetOutputTracks();
            foreach(TrackAsset track in tracks)
            {
                if(!(track is ControlTrack))
                    continue;

                IEnumerable<TimelineClip> clips = track.GetClips();
                foreach(TimelineClip clip in clips)
                {
                    if(clip.asset is ControlPlayableAsset controlPlayableAsset && controlPlayableAsset.prefabGameObject != null)
                    {
                        UnityEngine.Object.Destroy(playableDirector);
                        VRC.Core.Logger.LogWarning("PlayableDirector containing prefab removed", DebugCategoryName, playableDirector.gameObject);
                    }
                }
            }
        }

        private static void AddAudioSourceToVideoPlayer(UnityEngine.Video.VideoPlayer videoPlayer)
        {
            // VideoPlayer objects with output mode set to "Direct" bypass client volume controls.

            if (videoPlayer.audioOutputMode == UnityEngine.Video.VideoAudioOutputMode.Direct)
            {
                //if playback is happening (or will) you have to Stop() before you attach an AudioSource.
                bool play_state = (videoPlayer.isPlaying || videoPlayer.playOnAwake);
                if (play_state)
                {
                    videoPlayer.Stop();
                }

                AudioSource vp_src = videoPlayer.gameObject.AddComponent<AudioSource>();
                videoPlayer.audioOutputMode = UnityEngine.Video.VideoAudioOutputMode.AudioSource;
                for (ushort i = 0; i < videoPlayer.audioTrackCount; i++)
                {
                    videoPlayer.SetTargetAudioSource(i,vp_src);
                }

                if (play_state)
                {
                    videoPlayer.Play();
                }
                VRC.Core.Logger.LogWarning("VideoPlayer using DIRECT audio output fixed.");
            }
        }
    }
}
