#if UNITY_EDITOR
namespace NiAkaDev.EzMirror.Editor
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using UdonSharpEditor;
    using UnityEditor;
    using UnityEngine;
    using UnityEngine.UI;
    using VRC.Dynamics;
    using VRC.SDK3.Components;
    using VRC.SDK3.Dynamics.Contact.Components;
    using VRC.SDKBase;
    using VRC.Udon;

    public partial class EzMirrorEditor : UnityEditor.Editor
    {
        private void DrawOneClickSetup()
        {
            if (targets.Length != 1)
                return;

            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                EditorGUILayout.LabelField(L("OneClickTitle", "まず押す"), SectionHeaderStyle);
                EditorGUILayout.HelpBox(
                    L(
                        "OneClickDescription",
                        "子オブジェクトの Mirror / Trigger / Toggle を拾い、Off固定プロファイルとLQ背景クリア球、PC / Quest既定をまとめて整えます。"
                    ),
                    MessageType.None
                );

                if (GUILayout.Button(L("AutoSetupButton", "基本セットアップを自動実行")))
                {
                    ApplyToTargets(ApplyBeginnerSetup, "Setup EzMirror");
                    GUIUtility.ExitGUI();
                }
            }
        }

        private static void ApplyBeginnerSetup(EzMirror mirror)
        {
            EnsureBeginnerHierarchy(mirror);
            AutoWireReferences(mirror);
            RepairToggleVisualReference(mirror);
            ApplyDefaultInputSettings(mirror);
            EnsureInputControls(mirror);
            AutoWireReferences(mirror);
            RepairToggleVisualReference(mirror);
            EnsureProfilesIncludeOff(mirror);
            EnsureLqBackgroundSphere(mirror);
            ApplyDefaultObjectLayers(mirror);
            ApplyDefaultProfileSettings(mirror);
            AssignDefaultProfileAssets(mirror);
            EnsureProfileArraySizes(mirror);
            RemoveLegacyProfileIconObjects(mirror);
            ApplyDefaultMirrorReflectionSettings(mirror);
            EnsureGeneratedProfileMaterials(mirror, false);
            if (
                TryGetCommonFadeRanges(
                    mirror.profileMaterials,
                    out float distanceRange,
                    out float horizontalRange
                )
            )
            {
                ApplyTriggerAlignedFadeToMaterials(mirror, distanceRange, horizontalRange, true);
            }
            WireToggleEvent(mirror);
            EnsureEditorPreviewMaterial(mirror, true);
        }

        private static void RemoveLegacyProfileIconObjects(EzMirror mirror)
        {
            if (mirror == null)
                return;

            Image iconImage = EnsureSingleIconImage(mirror);
            if (iconImage == null)
                return;

            EnsureProfileIconSprites(mirror);
            EnsureProfileIconColors(mirror);
            int iconCount = Mathf.Max(4, GetEditorProfileCount(mirror));
            if (!HasUsableProfileIconSprites(mirror, iconCount))
                return;

            Transform toggleTransform = mirror.uiToggle == null
                ? mirror.transform.Find(SwitchUiObjectName + "/UI_Canvas/ToggleUI")
                : mirror.uiToggle.transform;
            if (toggleTransform == null)
                return;

            if (PreserveUnmigratedLegacyIconSprites(mirror, toggleTransform))
            {
                mirror.profileIconObjects = FindLegacyProfileIconObjects(mirror, iconCount);
                EditorUtility.SetDirty(mirror);
                PrefabUtility.RecordPrefabInstancePropertyModifications(mirror);
                return;
            }

            if (!HasUsableProfileIconSprites(mirror, iconCount))
                return;

            bool removed = false;
            removed |= DestroyLegacyIconChild(toggleTransform, "Icon", iconImage);
            for (int i = 0; i < LegacyProfileIconNames.Length; i++)
                removed |= DestroyLegacyIconChild(
                    toggleTransform,
                    LegacyProfileIconNames[i],
                    iconImage
                );
            if (!removed)
                return;

            mirror.profileIconObjects = FindLegacyProfileIconObjects(mirror, iconCount);
            EditorUtility.SetDirty(mirror);
            PrefabUtility.RecordPrefabInstancePropertyModifications(mirror);
        }

        private static bool PreserveUnmigratedLegacyIconSprites(
            EzMirror mirror,
            Transform toggleTransform
        )
        {
            bool preserved = false;
            for (int i = 0; i < LegacyProfileIconNames.Length; i++)
                preserved |= PreserveLegacyIconSprite(
                    mirror,
                    toggleTransform,
                    LegacyProfileIconNames[i],
                    i
                );
            return preserved;
        }

        private static bool PreserveLegacyIconSprite(
            EzMirror mirror,
            Transform toggleTransform,
            string legacyName,
            int profileIndex
        )
        {
            Transform legacy = FindLegacyIconChild(toggleTransform, legacyName);
            if (legacy == null)
                return false;

            Image legacyImage = legacy.GetComponent<Image>();
            if (legacyImage == null)
                legacyImage = legacy.GetComponentInChildren<Image>(true);
            if (legacyImage == null || legacyImage.sprite == null)
                return false;

            Sprite currentIcon = GetArrayValue<Sprite>(
                mirror == null ? null : mirror.profileIconSprites,
                profileIndex,
                null
            );
            if (currentIcon == legacyImage.sprite)
                return false;

            Sprite defaultIcon = EzMirrorAssetResolver.LoadDefaultIconSprite(profileIndex);
            if (defaultIcon == legacyImage.sprite)
                return false;

            if (mirror == null || mirror.profileIconSprites == null)
                return false;
            if (profileIndex < 0 || profileIndex >= mirror.profileIconSprites.Length)
                return false;

            mirror.profileIconSprites[profileIndex] = legacyImage.sprite;
            return true;
        }

        private static bool HasUsableProfileIconSprites(EzMirror mirror, int count)
        {
            if (mirror == null || mirror.profileIconSprites == null)
                return false;

            int safeCount = Mathf.Max(1, count);
            if (mirror.profileIconSprites.Length < safeCount)
                return false;

            for (int i = 0; i < safeCount; i++)
            {
                if (mirror.profileIconSprites[i] == null)
                    return false;
            }
            return true;
        }

        private static Transform FindLegacyIconChild(Transform toggleTransform, string childName)
        {
            if (toggleTransform == null)
                return null;

            Transform directChild = toggleTransform.Find(childName);
            if (directChild != null)
                return directChild;

            Transform legacyContainer = toggleTransform.Find("Icon");
            return legacyContainer == null ? null : legacyContainer.Find(childName);
        }

        private static bool DestroyLegacyIconChild(
            Transform toggleTransform,
            string childName,
            Image protectedImage
        )
        {
            Transform legacyChild = toggleTransform.Find(childName);
            if (legacyChild == null || protectedImage == null)
                return false;
            if (legacyChild.gameObject == protectedImage.gameObject)
                return false;

            Undo.DestroyObjectImmediate(legacyChild.gameObject);
            return true;
        }

        private static void EnsureBeginnerHierarchy(EzMirror mirror)
        {
            if (mirror == null)
                return;

            GameObject templateRoot = EzMirrorAssetResolver.LoadDefaultPrefab();
            if (templateRoot == null || templateRoot == mirror.gameObject)
                return;

            for (int i = 0; i < BeginnerTemplatePaths.Length; i++)
                EnsureTemplateObject(mirror.transform, templateRoot.transform, BeginnerTemplatePaths[i]);
        }

        private static Transform EnsureTemplateObject(
            Transform targetRoot,
            Transform templateRoot,
            string relativePath
        )
        {
            if (targetRoot == null || templateRoot == null || string.IsNullOrEmpty(relativePath))
                return null;

            Transform existing = targetRoot.Find(relativePath);
            if (existing != null)
            {
                EnsureTemplateComponents(existing, templateRoot.Find(relativePath));
                return existing;
            }

            Transform template = templateRoot.Find(relativePath);
            if (template == null)
                return null;

            string parentPath = GetParentPath(relativePath);
            Transform targetParent = string.IsNullOrEmpty(parentPath)
                ? targetRoot
                : EnsureTemplateObject(targetRoot, templateRoot, parentPath);
            if (targetParent == null)
                return null;

            existing = targetRoot.Find(relativePath);
            if (existing != null)
                return existing;

            GameObject clone = Instantiate(template.gameObject);
            clone.name = template.name;
            Undo.RegisterCreatedObjectUndo(clone, "Create EzMirror Object");
            clone.transform.SetParent(targetParent, false);
            CopyLocalTransform(template, clone.transform);
            clone.SetActive(template.gameObject.activeSelf);
            EditorUtility.SetDirty(clone);
            return clone.transform;
        }

        private static void EnsureTemplateComponents(Transform target, Transform template)
        {
            if (target == null || template == null)
                return;

            Component[] templateComponents = template.GetComponents<Component>();
            for (int i = 0; i < templateComponents.Length; i++)
            {
                Component templateComponent = templateComponents[i];
                if (templateComponent == null
                    || templateComponent is Transform
                    || templateComponent is UdonBehaviour
                    || templateComponent is UdonSharp.UdonSharpBehaviour)
                {
                    continue;
                }

                Type componentType = templateComponent.GetType();
                if (target.GetComponent(componentType) != null)
                    continue;

                UnityEditorInternal.ComponentUtility.CopyComponent(templateComponent);
                UnityEditorInternal.ComponentUtility.PasteComponentAsNew(target.gameObject);
                EditorUtility.SetDirty(target.gameObject);
            }
        }

        private static string GetParentPath(string relativePath)
        {
            int slashIndex = relativePath.LastIndexOf('/');
            return slashIndex < 0 ? string.Empty : relativePath.Substring(0, slashIndex);
        }

        private static void CopyLocalTransform(Transform source, Transform destination)
        {
            if (source == null || destination == null)
                return;

            destination.localPosition = source.localPosition;
            destination.localRotation = source.localRotation;
            destination.localScale = source.localScale;
        }

        private static void ApplyDefaultInputSettings(EzMirror mirror)
        {
            EzMirror defaultMirror = GetDefaultPrefabMirror();
            if (
                (!mirror.enableUguiInput && !mirror.enableInteractInput && !mirror.enableContactInput)
                || (mirror.enableUguiInput && !mirror.enableInteractInput && !mirror.enableContactInput)
            )
            {
                if (defaultMirror == null)
                {
                    mirror.enableUguiInput = true;
                }
                else
                {
                    mirror.enableUguiInput = defaultMirror.enableUguiInput;
                    mirror.enableInteractInput = defaultMirror.enableInteractInput;
                    mirror.enableContactInput = defaultMirror.enableContactInput;
                }
            }

            if (mirror.contactCooldownSeconds <= 0f)
                mirror.contactCooldownSeconds =
                    defaultMirror != null && defaultMirror.contactCooldownSeconds > 0f
                        ? defaultMirror.contactCooldownSeconds
                        : 0.5f;
            if (mirror.hapticDuration <= 0f)
                mirror.hapticDuration =
                    defaultMirror != null && defaultMirror.hapticDuration > 0f
                        ? defaultMirror.hapticDuration
                        : 0.08f;
            if (mirror.hapticAmplitude <= 0f)
                mirror.hapticAmplitude =
                    defaultMirror != null && defaultMirror.hapticAmplitude > 0f
                        ? defaultMirror.hapticAmplitude
                        : 0.35f;
            if (mirror.hapticFrequency <= 0f)
                mirror.hapticFrequency =
                    defaultMirror != null && defaultMirror.hapticFrequency > 0f
                        ? defaultMirror.hapticFrequency
                        : 0.5f;
            mirror.enableHapticFeedback = true;
        }

        private static EzMirror GetDefaultPrefabMirror()
        {
            GameObject templateRoot = EzMirrorAssetResolver.LoadDefaultPrefab();
            return templateRoot == null ? null : templateRoot.GetComponent<EzMirror>();
        }

        private static Transform FindDefaultPrefabTemplate(string relativePath)
        {
            GameObject templateRoot = EzMirrorAssetResolver.LoadDefaultPrefab();
            if (templateRoot == null)
                return null;
            if (string.IsNullOrEmpty(relativePath))
                return templateRoot.transform;

            return templateRoot.transform.Find(relativePath);
        }

        private static void ApplyDefaultObjectLayers(EzMirror mirror)
        {
            if (mirror == null)
                return;

            SetLayerIfExists(mirror.gameObject, DefaultLayerName);
            if (mirror.sfxAudioSource != null)
                SetLayerIfExists(mirror.sfxAudioSource.gameObject, DefaultLayerName);
            if (mirror.triggerCollider != null)
                SetLayerIfExists(mirror.triggerCollider.gameObject, WalkthroughLayerName);
            if (mirror.uiToggle != null)
            {
                Canvas canvas = mirror.uiToggle.GetComponentInParent<Canvas>(true);
                if (canvas != null)
                    SetLayerRecursivelyIfExists(canvas.gameObject, WalkthroughLayerName);
            }
            if (mirror.profileMirror != null)
                SetLayerIfExists(mirror.profileMirror.gameObject, WaterLayerName);

            GameObject lqBackground = GetChildObject(mirror.transform, LqBackgroundObjectName);
            if (lqBackground != null)
                SetLayerIfExists(lqBackground, MirrorReflectionLayerName);

            Transform switchUi = mirror.transform.Find(SwitchUiObjectName);
            if (switchUi != null)
            {
                Transform interact = switchUi.Find(InteractInputObjectName);
                Transform contact = switchUi.Find(ContactInputObjectName);
                if (interact != null)
                    SetLayerIfExists(interact.gameObject, InteractiveLayerName);
                if (contact != null)
                    SetLayerIfExists(contact.gameObject, InteractiveLayerName);
                Transform switchModel = switchUi.Find(SwitchModelObjectName);
                if (switchModel != null)
                    SetLayerRecursivelyIfExists(switchModel.gameObject, DefaultLayerName);
            }
        }

        private static void AutoWireReferences(EzMirror mirror)
        {
            VRCMirrorReflection foundMirror = mirror.GetComponentInChildren<VRCMirrorReflection>(true);
            if (foundMirror != null)
            {
                mirror.profileMirror = foundMirror;
                mirror.profileRenderer = foundMirror.GetComponent<Renderer>();
            }

            mirror.uiToggle = mirror.GetComponentInChildren<Toggle>(true);
            mirror.interactInputProxy = mirror.GetComponentInChildren<EzMirrorInteractInputProxy>(true);
            mirror.inputProxy = mirror.GetComponentInChildren<EzMirrorInputProxy>(true);
            if (mirror.interactInputProxy != null)
                mirror.inputHitboxCollider = mirror.interactInputProxy.GetComponent<BoxCollider>();
            if (mirror.inputProxy != null)
                mirror.contactReceiver = mirror.inputProxy.GetComponent<VRCContactReceiver>();
            if (mirror.contactReceiver == null)
                mirror.contactReceiver = mirror.GetComponentInChildren<VRCContactReceiver>(true);

            Transform trigger = FindDeep(mirror.transform, "Trigger");
            BoxCollider triggerCollider = trigger == null ? null : trigger.GetComponent<BoxCollider>();
            mirror.triggerCollider =
                triggerCollider == null ? FindDisplayTriggerCollider(mirror) : triggerCollider;
            mirror.iconImage = FindSingleIconImage(mirror);
            mirror.sfxAudioSource = mirror.GetComponentInChildren<AudioSource>(true);
            mirror.switchAnimator = FindSwitchAnimator(mirror);
            mirror.profileIconObjects = FindLegacyProfileIconObjects(
                mirror,
                Mathf.Max(4, GetEditorProfileCount(mirror))
            );
        }

        private static Animator FindSwitchAnimator(EzMirror mirror)
        {
            if (mirror == null)
                return null;
            if (mirror.switchAnimator != null && mirror.switchAnimator.transform.IsChildOf(mirror.transform))
                return mirror.switchAnimator;

            Transform switchModel = mirror.transform.Find(SwitchUiObjectName + "/" + SwitchModelObjectName);
            if (switchModel != null)
            {
                Animator animator = switchModel.GetComponent<Animator>();
                if (animator != null)
                    return animator;
            }

            return null;
        }

        private static Image FindSingleIconImage(EzMirror mirror)
        {
            if (mirror == null)
                return null;
            if (mirror.iconImage != null && mirror.iconImage.transform.IsChildOf(mirror.transform))
                return mirror.iconImage;

            if (mirror.uiToggle != null)
            {
                Image toggleImage = mirror.uiToggle.GetComponent<Image>();
                if (toggleImage != null)
                    return toggleImage;
            }

            Transform toggleTransform = mirror.transform.Find(SwitchUiObjectName + "/UI_Canvas/ToggleUI");
            if (toggleTransform != null)
            {
                Image toggleImage = toggleTransform.GetComponent<Image>();
                if (toggleImage != null)
                    return toggleImage;
            }

            return FindComponentDeep<Image>(mirror.transform, "Icon");
        }

        private static Image EnsureSingleIconImage(EzMirror mirror)
        {
            if (mirror == null || mirror.uiToggle == null)
                return FindSingleIconImage(mirror);

            Image iconImage = mirror.uiToggle.GetComponent<Image>();
            if (iconImage != null)
                return iconImage;

            iconImage = Undo.AddComponent<Image>(mirror.uiToggle.gameObject);
            iconImage.sprite = EzMirrorAssetResolver.LoadDefaultIconSprite(OffProfileIndex);
            iconImage.color = Color.white;
            iconImage.preserveAspect = true;
            iconImage.raycastTarget = true;
            EditorUtility.SetDirty(iconImage);
            PrefabUtility.RecordPrefabInstancePropertyModifications(iconImage);
            return iconImage;
        }

        private static GameObject[] FindLegacyProfileIconObjects(EzMirror mirror, int count)
        {
            GameObject[] icons = new GameObject[Mathf.Max(1, count)];
            if (mirror == null)
                return icons;

            for (int i = 0; i < LegacyProfileIconNames.Length && i < icons.Length; i++)
                icons[i] = GetChildObject(mirror.transform, LegacyProfileIconNames[i]);
            return icons;
        }

        private static BoxCollider FindDisplayTriggerCollider(EzMirror mirror)
        {
            if (mirror == null)
                return null;

            BoxCollider[] colliders = mirror.GetComponentsInChildren<BoxCollider>(true);
            for (int i = 0; i < colliders.Length; i++)
            {
                if (
                    IsSelectableDisplayTriggerCollider(mirror, colliders[i], true)
                    && colliders[i].gameObject.name == "Trigger"
                )
                    return colliders[i];
            }

            for (int i = 0; i < colliders.Length; i++)
            {
                if (IsSelectableDisplayTriggerCollider(mirror, colliders[i], true))
                    return colliders[i];
            }

            for (int i = 0; i < colliders.Length; i++)
            {
                if (IsSelectableDisplayTriggerCollider(mirror, colliders[i], false))
                    return colliders[i];
            }

            return null;
        }

        private static bool IsSelectableDisplayTriggerCollider(
            EzMirror mirror,
            BoxCollider collider,
            bool requireTrigger
        )
        {
            if (collider == null)
                return false;
            if (collider == mirror.inputHitboxCollider)
                return false;
            if (
                mirror.interactInputProxy != null
                && collider.gameObject == mirror.interactInputProxy.gameObject
            )
                return false;
            if (mirror.inputProxy != null && collider.gameObject == mirror.inputProxy.gameObject)
                return false;
            if (
                mirror.contactReceiver != null
                && collider.gameObject == mirror.contactReceiver.gameObject
            )
                return false;
            if (collider.GetComponent<Canvas>() != null)
                return false;
            if (requireTrigger && !collider.isTrigger)
                return false;
            return true;
        }

        private static void RepairToggleVisualReference(EzMirror mirror)
        {
            if (mirror == null || mirror.uiToggle == null)
                return;

            Image iconImage = EnsureSingleIconImage(mirror);
            if (iconImage == null)
                return;

            if (mirror.iconImage != iconImage)
            {
                Undo.RecordObject(mirror, "Repair EzMirror Icon Image");
                mirror.iconImage = iconImage;
                EditorUtility.SetDirty(mirror);
                PrefabUtility.RecordPrefabInstancePropertyModifications(mirror);
            }

            Undo.RecordObject(mirror.uiToggle, "Repair EzMirror Toggle Visual");
            mirror.uiToggle.targetGraphic = iconImage;
            mirror.uiToggle.graphic = null;
            EditorUtility.SetDirty(mirror.uiToggle);
            PrefabUtility.RecordPrefabInstancePropertyModifications(mirror.uiToggle);
            Undo.RecordObject(iconImage, "Repair EzMirror Icon Image");
            iconImage.preserveAspect = true;
            EditorUtility.SetDirty(iconImage);
            PrefabUtility.RecordPrefabInstancePropertyModifications(iconImage);
        }

        private static void EnsureProfilesIncludeOff(EzMirror mirror)
        {
            if (mirror.profileMaterials == null || mirror.profileMaterials.Length == 0)
            {
                mirror.profileMaterials = new Material[4];
                return;
            }

            if (mirror.profileMaterials[0] != null)
            {
                mirror.profileMaterials = InsertDefaultAtStart(mirror.profileMaterials);
                mirror.profileBaseMaterials = InsertDefaultAtStart(mirror.profileBaseMaterials);
                InsertOffProfileAssetReference(mirror);
                mirror.profileNames = InsertDefaultAtStart(mirror.profileNames);
                mirror.profileDisablePixelLights = InsertDefaultAtStart(mirror.profileDisablePixelLights);
                mirror.profileTurnOffMirrorOcclusion = InsertDefaultAtStart(mirror.profileTurnOffMirrorOcclusion);
                mirror.profileReflectLayers = InsertDefaultAtStart(mirror.profileReflectLayers);
                mirror.profileCameraClearFlags = InsertDefaultAtStart(mirror.profileCameraClearFlags);
                mirror.profileCustomSkyboxes = InsertDefaultAtStart(mirror.profileCustomSkyboxes);
                mirror.profileCustomClearColors = InsertDefaultAtStart(mirror.profileCustomClearColors);
                mirror.profileHideBackground = InsertDefaultAtStart(mirror.profileHideBackground);
                mirror.profileTransparency = InsertDefaultAtStart(mirror.profileTransparency);
                mirror.profilePcOnly = InsertDefaultAtStart(mirror.profilePcOnly);
                mirror.profileIconSprites = InsertDefaultAtStart(mirror.profileIconSprites);
                mirror.profileIconColors = InsertDefaultAtStart(mirror.profileIconColors);
            }

            if (mirror.profileMaterials.Length < 4)
                mirror.profileMaterials = ResizeCopy(mirror.profileMaterials, 4);
            if (mirror.profileBaseMaterials == null || mirror.profileBaseMaterials.Length < mirror.profileMaterials.Length)
                mirror.profileBaseMaterials = ResizeCopy(mirror.profileBaseMaterials, mirror.profileMaterials.Length);
            EnsureProfileAssetReferencesSize(mirror, mirror.profileMaterials.Length);
        }

        private static void EnsureLqBackgroundSphere(EzMirror mirror)
        {
            GameObject sphere = GetChildObject(mirror.transform, LqBackgroundObjectName);
            bool created = sphere == null;
            if (sphere == null)
            {
                sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                Undo.RegisterCreatedObjectUndo(sphere, "Create LQ Background");
                sphere.name = LqBackgroundObjectName;
                sphere.transform.SetParent(mirror.transform, false);
                Collider collider = sphere.GetComponent<Collider>();
                if (collider != null)
                    Undo.DestroyObjectImmediate(collider);
            }

            if (created)
            {
                sphere.transform.localPosition = mirror.profileMirror == null
                    ? Vector3.zero
                    : mirror.profileMirror.transform.localPosition;
                sphere.transform.localRotation = Quaternion.identity;
                sphere.transform.localScale = new Vector3(24f, 24f, 24f);
            }
            int mirrorReflectionLayer = LayerMask.NameToLayer(MirrorReflectionLayerName);
            if (mirrorReflectionLayer >= 0)
                sphere.layer = mirrorReflectionLayer;
            sphere.SetActive(false);

            Renderer renderer = sphere.GetComponent<Renderer>();
            if (renderer != null)
            {
                renderer.sharedMaterial = EzMirrorAssetResolver.LoadTransBgMaterial();
                renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
                renderer.receiveShadows = false;
                renderer.allowOcclusionWhenDynamic = false;
            }

            UpsertProfileActiveObject(mirror, sphere, LqProfileIndex);
            RemoveProfileInactiveObjectReferences(mirror, sphere);
        }

        private static void UpsertProfileActiveObject(
            EzMirror mirror,
            GameObject target,
            int profileIndex
        )
        {
            if (mirror == null || target == null)
                return;

            List<GameObject> objects = new List<GameObject>();
            List<int> modes = new List<int>();
            bool targetAdded = false;
            GameObject[] oldObjects = mirror.profileActiveObjects;
            int[] oldModes = mirror.profileActiveObjectModes;
            int oldCount = oldObjects == null ? 0 : oldObjects.Length;
            for (int i = 0; i < oldCount; i++)
            {
                GameObject oldObject = oldObjects[i];
                int oldMode = oldModes != null && i < oldModes.Length ? oldModes[i] : 0;
                if (oldObject == target)
                {
                    if (targetAdded)
                        continue;

                    oldMode = profileIndex;
                    targetAdded = true;
                }

                objects.Add(oldObject);
                modes.Add(oldMode);
            }

            if (!targetAdded)
            {
                objects.Add(target);
                modes.Add(profileIndex);
            }

            mirror.profileActiveObjects = objects.ToArray();
            mirror.profileActiveObjectModes = modes.ToArray();
        }

        private static void RemoveProfileInactiveObjectReferences(
            EzMirror mirror,
            GameObject target
        )
        {
            if (mirror == null || target == null || mirror.profileInactiveObjects == null)
                return;

            int keepCount = 0;
            for (int i = 0; i < mirror.profileInactiveObjects.Length; i++)
            {
                if (mirror.profileInactiveObjects[i] != target)
                    keepCount++;
            }
            if (keepCount == mirror.profileInactiveObjects.Length)
                return;

            GameObject[] objects = new GameObject[keepCount];
            int[] modes = new int[keepCount];
            int writeIndex = 0;
            for (int i = 0; i < mirror.profileInactiveObjects.Length; i++)
            {
                if (mirror.profileInactiveObjects[i] == target)
                    continue;

                objects[writeIndex] = mirror.profileInactiveObjects[i];
                modes[writeIndex] =
                    mirror.profileInactiveObjectModes != null
                    && i < mirror.profileInactiveObjectModes.Length
                        ? mirror.profileInactiveObjectModes[i]
                        : 0;
                writeIndex++;
            }

            mirror.profileInactiveObjects = objects;
            mirror.profileInactiveObjectModes = modes;
        }

        private static void ApplyDefaultProfileSettings(EzMirror mirror)
        {
            int profileCount = Mathf.Max(4, mirror.profileMaterials == null ? 0 : mirror.profileMaterials.Length);
            mirror.profileMaterials = ResizeCopy(mirror.profileMaterials, profileCount);
            mirror.profileMaterials[0] = null;
            mirror.profileBaseMaterials = ResizeCopy(mirror.profileBaseMaterials, profileCount);
            mirror.profileBaseMaterials[0] = null;
            for (int i = 1; i < profileCount; i++)
            {
                if (mirror.profileBaseMaterials[i] == null)
                    mirror.profileBaseMaterials[i] = GetDefaultBaseMaterialForProfile(i);
            }
            EnsureProfileAssetReferencesSize(mirror, profileCount);

            mirror.profileNames = ResizeCopy(mirror.profileNames, profileCount);
            EnsureDefaultProfileNames(mirror.profileNames, 0);

            mirror.profileIsDefault = new bool[profileCount];
            mirror.profileIsDefault[0] = true;

            mirror.profileMirrorEnabled = new bool[profileCount];
            for (int i = 1; i < profileCount; i++)
                mirror.profileMirrorEnabled[i] = true;

            mirror.profilePcOnly = new bool[profileCount];
            if (profileCount > 1)
                mirror.profilePcOnly[1] = true;
            if (profileCount > 2)
                mirror.profilePcOnly[2] = true;

            mirror.profileHideBackground = new bool[profileCount];
            if (profileCount > 3)
                mirror.profileHideBackground[3] = true;

            mirror.profileTransparency = new float[profileCount];
            for (int i = 0; i < profileCount; i++)
                mirror.profileTransparency[i] = GetDefaultProfileTransparency(i);

            mirror.profileIconSprites = ResizeCopy(mirror.profileIconSprites, profileCount);
            mirror.profileIconColors = ResizeCopy(mirror.profileIconColors, profileCount);
            mirror.profileIconObjects = ResizeCopy(mirror.profileIconObjects, profileCount);
            EnsureProfileIconSprites(mirror);
            EnsureProfileIconColors(mirror);
        }

        private static void ApplyDefaultMirrorReflectionSettings(EzMirror mirror)
        {
            int profileCount = mirror.profileMaterials == null ? 0 : mirror.profileMaterials.Length;
            for (int i = 0; i < profileCount; i++)
            {
                mirror.profileDisablePixelLights[i] = i != OffProfileIndex;
                mirror.profileTurnOffMirrorOcclusion[i] = i != OffProfileIndex;
                mirror.profileReflectLayers[i] = GetDefaultProfileReflectLayers(i);
                mirror.profileCameraClearFlags[i] = (MirrorClearFlags)(i == LqProfileIndex ? 2 : 0);
                mirror.profileCustomSkyboxes[i] = null;
                mirror.profileCustomClearColors[i] = i == LqProfileIndex
                    ? new Color(0f, 0f, 0f, 0f)
                    : new Color(0f, 0f, 0f, 1f);
            }
        }

        private static LayerMask GetDefaultProfileReflectLayers(int profileIndex)
        {
            LayerMask layerMask = default;
            layerMask.value = profileIndex == OffProfileIndex
                ? 0
                : profileIndex == LqProfileIndex
                    ? BuildLayerMask(PlayerLayerName, MirrorReflectionLayerName, HardwareObjectsLayerName)
                    : BuildLayerMask(
                        DefaultLayerName,
                        PlayerLayerName,
                        EnvironmentLayerName,
                        PickupLayerName,
                        PickupNoEnvironmentLayerName,
                        MirrorReflectionLayerName,
                        HardwareObjectsLayerName
                    );
            return layerMask;
        }

        private static int BuildLayerMask(params string[] layerNames)
        {
            int mask = 0;
            for (int i = 0; i < layerNames.Length; i++)
            {
                int layer = LayerMask.NameToLayer(layerNames[i]);
                if (layer >= 0)
                    mask |= 1 << layer;
            }
            return mask;
        }

        private static void SetLayerIfExists(GameObject targetObject, string layerName)
        {
            if (targetObject == null)
                return;

            int layer = LayerMask.NameToLayer(layerName);
            if (layer < 0)
                return;

            targetObject.layer = layer;
            EditorUtility.SetDirty(targetObject);
            PrefabUtility.RecordPrefabInstancePropertyModifications(targetObject);
        }

        private static void SetLayerRecursivelyIfExists(GameObject rootObject, string layerName)
        {
            if (rootObject == null)
                return;

            int layer = LayerMask.NameToLayer(layerName);
            if (layer < 0)
                return;

            Transform[] transforms = rootObject.GetComponentsInChildren<Transform>(true);
            for (int i = 0; i < transforms.Length; i++)
            {
                GameObject targetObject = transforms[i].gameObject;
                targetObject.layer = layer;
                EditorUtility.SetDirty(targetObject);
                PrefabUtility.RecordPrefabInstancePropertyModifications(targetObject);
            }
        }

        private static void EnsureProfileArraySizes(EzMirror mirror)
        {
            int count = Mathf.Max(1, GetEditorProfileCount(mirror));
            mirror.profileMaterials = ResizeCopy(mirror.profileMaterials, count);
            mirror.profileBaseMaterials = ResizeCopy(mirror.profileBaseMaterials, count);
            EnsureProfileAssetReferencesSize(mirror, count);
            int oldNameCount = mirror.profileNames == null ? 0 : mirror.profileNames.Length;
            mirror.profileNames = ResizeCopy(mirror.profileNames, count);
            mirror.profileIsDefault = ResizeCopy(mirror.profileIsDefault, count);
            mirror.profileMirrorEnabled = ResizeCopy(mirror.profileMirrorEnabled, count);
            mirror.profileDisablePixelLights = ResizeCopy(mirror.profileDisablePixelLights, count);
            mirror.profileTurnOffMirrorOcclusion = ResizeCopy(mirror.profileTurnOffMirrorOcclusion, count);
            mirror.profileReflectLayers = ResizeCopy(mirror.profileReflectLayers, count);
            mirror.profileCameraClearFlags = ResizeCopy(mirror.profileCameraClearFlags, count);
            mirror.profileCustomSkyboxes = ResizeCopy(mirror.profileCustomSkyboxes, count);
            mirror.profileCustomClearColors = ResizeCopy(mirror.profileCustomClearColors, count);
            mirror.profileHideBackground = ResizeCopy(mirror.profileHideBackground, count);
            int oldTransparencyCount = mirror.profileTransparency == null
                ? 0
                : mirror.profileTransparency.Length;
            mirror.profileTransparency = ResizeCopy(mirror.profileTransparency, count);
            mirror.profilePcOnly = ResizeCopy(mirror.profilePcOnly, count);
            mirror.profileIconSprites = ResizeCopy(mirror.profileIconSprites, count);
            mirror.profileIconColors = ResizeCopy(mirror.profileIconColors, count);
            mirror.profileIconObjects = ResizeCopy(mirror.profileIconObjects, count);
            mirror.profileMaterials[0] = null;
            mirror.profileBaseMaterials[0] = null;
            EnsureDefaultProfileNames(mirror.profileNames, oldNameCount);
            for (int i = 1; i < count; i++)
            {
                if (mirror.profileBaseMaterials[i] == null)
                    mirror.profileBaseMaterials[i] = GetDefaultBaseMaterialForProfile(i);
            }
            for (int i = oldTransparencyCount; i < count; i++)
                mirror.profileTransparency[i] = GetDefaultProfileTransparency(i);
            mirror.profileMirrorEnabled[0] = false;
            mirror.profileTransparency[0] = 0f;
            mirror.profilePcOnly[0] = false;
            EnsureProfileIconSprites(mirror);
            EnsureProfileIconColors(mirror);
        }

        private static void EnsureDefaultProfileNames(string[] names, int startIndex)
        {
            if (names == null)
                return;

            int safeStartIndex = Mathf.Max(0, startIndex);
            for (int i = safeStartIndex; i < names.Length; i++)
            {
                if (string.IsNullOrEmpty(names[i]))
                    names[i] = GetFallbackProfileName(i);
            }
        }

        private static T[] ResizeCopy<T>(T[] values, int size)
        {
            T[] resized = new T[Mathf.Max(0, size)];
            if (values != null)
                Array.Copy(values, resized, Mathf.Min(values.Length, resized.Length));
            return resized;
        }

        private static T[] InsertDefaultAtStart<T>(T[] values)
        {
            if (values == null)
                return null;
            T[] shifted = new T[values.Length + 1];
            Array.Copy(values, 0, shifted, 1, values.Length);
            return shifted;
        }

        private static void WireToggleEvent(EzMirror mirror)
        {
            if (mirror.uiToggle == null)
                return;

            UdonBehaviour backing = UdonSharpEditorUtility.GetBackingUdonBehaviour(mirror);
            if (backing == null)
                return;

            Toggle toggle = mirror.uiToggle;
            Undo.RecordObject(toggle, "Wire EzMirror Toggle");
            SerializedObject serializedToggle = new SerializedObject(toggle);
            SerializedProperty calls = serializedToggle.FindProperty("onValueChanged.m_PersistentCalls.m_Calls");
            if (calls == null)
                return;

            int ezMirrorCallIndex = -1;
            for (int i = 0; i < calls.arraySize; i++)
            {
                SerializedProperty existingCall = calls.GetArrayElementAtIndex(i);
                if (!IsEzMirrorToggleCall(existingCall))
                    continue;

                if (ezMirrorCallIndex < 0)
                {
                    ezMirrorCallIndex = i;
                    continue;
                }

                calls.DeleteArrayElementAtIndex(i);
                i--;
            }

            if (ezMirrorCallIndex < 0)
            {
                ezMirrorCallIndex = calls.arraySize;
                calls.arraySize++;
            }

            SerializedProperty call = calls.GetArrayElementAtIndex(ezMirrorCallIndex);
            ConfigureEzMirrorToggleCall(call, backing);
            serializedToggle.ApplyModifiedProperties();
            EditorUtility.SetDirty(toggle);
            PrefabUtility.RecordPrefabInstancePropertyModifications(toggle);
        }

        private static bool IsEzMirrorToggleCall(SerializedProperty call)
        {
            if (call == null)
                return false;

            SerializedProperty methodName = call.FindPropertyRelative("m_MethodName");
            SerializedProperty stringArgument =
                call.FindPropertyRelative("m_Arguments.m_StringArgument");

            return methodName != null
                && stringArgument != null
                && methodName.stringValue == "SendCustomEvent"
                && stringArgument.stringValue == ToggleEventName;
        }

        private static void ConfigureEzMirrorToggleCall(
            SerializedProperty call,
            UdonBehaviour backing
        )
        {
            call.FindPropertyRelative("m_Target").objectReferenceValue = backing;
            call.FindPropertyRelative("m_TargetAssemblyTypeName").stringValue = "VRC.Udon.UdonBehaviour, VRC.Udon";
            call.FindPropertyRelative("m_MethodName").stringValue = "SendCustomEvent";
            call.FindPropertyRelative("m_Mode").enumValueIndex = 5;
            call.FindPropertyRelative("m_Arguments.m_StringArgument").stringValue = ToggleEventName;
            call.FindPropertyRelative("m_CallState").enumValueIndex = 2;
        }

        private static GameObject GetChildObject(Transform root, string childName)
        {
            Transform found = FindDeep(root, childName);
            return found == null ? null : found.gameObject;
        }

        private static T FindComponentDeep<T>(Transform root, string childName) where T : Component
        {
            Transform found = FindDeep(root, childName);
            return found == null ? null : found.GetComponent<T>();
        }

        private static Transform FindDeep(Transform root, string childName)
        {
            if (root.name == childName)
                return root;
            for (int i = 0; i < root.childCount; i++)
            {
                Transform found = FindDeep(root.GetChild(i), childName);
                if (found != null)
                    return found;
            }
            return null;
        }
    }
}
#endif
