#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 DrawProfileCards()
        {
            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                EditorGUILayout.HelpBox(
                    L("ProfileListHelp", "プロファイル 0 は Off 固定です。プロファイル 1 から順に切り替わります。通常は名前・表示・透明度・アイコンだけ確認すれば使えます。"),
                    MessageType.None
                );
                EnsureSerializedProfileArraySizes();
                EnsureProfileObjectArraySizes();
                EnsureProfileFoldoutSize(profileMaterials.arraySize);

                for (int i = 0; i < profileMaterials.arraySize; i++)
                {
                    if (DrawProfileCard(i))
                        return;
                }

                using (new EditorGUILayout.HorizontalScope())
                {
                    if (GUILayout.Button(L("AddProfileButton", "プロファイルを追加")))
                    {
                        AddProfile();
                        return;
                    }
                }
            }
        }

        private bool DrawProfileCard(int index)
        {
            if (index == 0)
                return DrawOffProfileCard();

            return DrawRegularProfileCard(index);
        }

        private void DrawDefaultProfileAssetControls()
        {
            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                EditorGUILayout.LabelField(L("ProfileAssetHeader", "プロファイルアセット"), EditorStyles.boldLabel);
                EditorGUILayout.HelpBox(
                    L("ProfileAssetHelp", "Sceneに依存しないプロファイル設定だけをアセット化できます。アイコン画像もプロファイルアセット側で管理し、EzMirror の表示用 Image へ自動反映します。"),
                    MessageType.Info
                );

                if (GUILayout.Button(L("GenerateDefaultProfileAssetsButton", "デフォルトのプロファイルアセットを生成")))
                {
                    ApplyToTargets(RegenerateAndAssignDefaultProfileAssets, "EzMirror プロファイルアセットを初期設定");
                    GUIUtility.ExitGUI();
                }
            }
        }

        private void DrawProfileAssetForCard(int index)
        {
            if (targets.Length != 1)
            {
                EditorGUILayout.HelpBox(
                    L("ProfileAssetSingleTargetOnly", "プロファイルアセットの割り当てと作成は、1つの EzMirror を選択している時だけ使えます。"),
                    MessageType.None
                );
                return;
            }

            EzMirror mirror = target as EzMirror;
            EzMirrorProfileAsset selectedAsset = GetProfileAssetReference(mirror, index);

            EditorGUI.BeginChangeCheck();
            selectedAsset = (EzMirrorProfileAsset)EditorGUILayout.ObjectField(
                LC("ProfileAssetHeader", "プロファイルアセット"),
                selectedAsset,
                typeof(EzMirrorProfileAsset),
                false
            );
            if (EditorGUI.EndChangeCheck())
            {
                SetProfileAssetReference(mirror, index, selectedAsset);
            }
            EditorGUILayout.HelpBox(
                L("ProfileAssetScopeHelp", "プロファイルアセットには、プロファイル名・画質・透明度・PC専用・基盤Material・アイコン画像などのScene非依存設定だけを保存します。ON/OFFオブジェクトなどのScene参照は保存しません。"),
                MessageType.None
            );
            EditorGUILayout.HelpBox(
                L("ProfileAssetApplyExplicitHelp", "アセットを割り当てただけでは現在の設定を上書きしません。内容を取り込みたい時だけ「アセットの設定を反映」を押してください。"),
                MessageType.Info
            );

            using (new EditorGUILayout.HorizontalScope())
            {
                using (new EditorGUI.DisabledScope(selectedAsset == null))
                {
                    if (GUILayout.Button(L("ApplyProfileAssetButton", "アセットの設定を反映")))
                    {
                        ApplyProfileAssetFromInspector(
                            index,
                            selectedAsset,
                            false
                        );
                        GUIUtility.ExitGUI();
                    }
                }

                if (GUILayout.Button(L("CreateProfileAssetButton", "このプロファイルからアセットを作成")))
                {
                    CreateProfileAssetFromInspector(index);
                    GUIUtility.ExitGUI();
                }
            }
        }

        private bool DrawOffProfileCard()
        {
            EnsureOffProfileDefaults();
            SerializedProperty materialProperty = profileMaterials.GetArrayElementAtIndex(0);
            materialProperty.objectReferenceValue = null;

            Color oldBackgroundColor = GUI.backgroundColor;
            GUI.backgroundColor = new Color(0.78f, 0.86f, 1f, 1f);
            using (new EditorGUILayout.VerticalScope(ProfileCardStyle))
            {
                GUI.backgroundColor = oldBackgroundColor;
                DrawProfileCardHeader(
                    0,
                    GetProfileTitle(0, null),
                    L("CannotDeleteButton", "削除不可"),
                    true,
                    out bool isExpanded
                );
                if (isExpanded)
                    DrawOffProfileCardBody();
            }
            GUI.backgroundColor = oldBackgroundColor;

            return false;
        }

        private void DrawOffProfileCardBody()
        {
            EditorGUILayout.HelpBox(
                L("OffProfileHelp", "Offプロファイルはミラーを表示しない固定状態です。"),
                MessageType.Info
            );
            EditorGUILayout.HelpBox(
                L("OffProfileDetailHelp", "Offプロファイルでは Material / 画質設定 / PC専用設定は表示しません。プロファイルアセットも通常プロファイル側で管理します。"),
                MessageType.None
            );

            DrawProfileNameField(0);
            DrawDefaultProfileControl(0);

            DrawProfileGroupHeader(L("GroupOffVisual", "設定のまとまり: Off時の見た目"));
            DrawProfileIconForCard(0);

            DrawProfileGroupHeader(L("GroupOffObjects", "設定のまとまり: Off時だけ切り替えるオブジェクト"));
            DrawProfileObjectsForCard(0, true);
            DrawProfileObjectsForCard(0, false);
            DrawProfileObjectConflictWarnings(0);
        }

        private bool DrawRegularProfileCard(int index)
        {
            Material material = profileMaterials.GetArrayElementAtIndex(index).objectReferenceValue as Material;

            Color oldBackgroundColor = GUI.backgroundColor;
            GUI.backgroundColor = new Color(0.92f, 0.95f, 1f, 1f);
            using (new EditorGUILayout.VerticalScope(ProfileCardStyle))
            {
                GUI.backgroundColor = oldBackgroundColor;
                if (
                    DrawProfileCardHeader(
                        index,
                        GetProfileTitle(index, material),
                        L("DeleteButton", "削除"),
                        false,
                        out bool isExpanded
                    )
                )
                {
                    RemoveProfile(index);
                    return true;
                }

                if (isExpanded)
                    DrawRegularProfileCardBody(index, material);
            }
            GUI.backgroundColor = oldBackgroundColor;

            return false;
        }

        private void DrawRegularProfileCardBody(int index, Material material)
        {
            DrawProfileNameField(index);
            DrawDefaultProfileControl(index);

            DrawProfileGroupHeader(L("GroupDisplay", "設定のまとまり: 表示"));
            EditorGUILayout.PropertyField(
                profileMirrorEnabled.GetArrayElementAtIndex(index),
                LC("MirrorEnabledLabel", "ミラーを表示する")
            );
            EditorGUILayout.Slider(
                profileTransparency.GetArrayElementAtIndex(index),
                0f,
                1f,
                LC("TransparencyLabel", "透明度")
            );
            EditorGUILayout.PropertyField(
                profileHideBackground.GetArrayElementAtIndex(index),
                LC("HideBackgroundLabel", "背景を透明化")
            );
            EditorGUILayout.PropertyField(
                profilePcOnly.GetArrayElementAtIndex(index),
                LC("PcOnlyLabel", "PC専用")
            );

            DrawProfileSafetyHints(index);

            DrawProfileGroupHeader(L("GroupIcon", "設定のまとまり: UIアイコン"));
            DrawProfileIconForCard(index);
            DrawProfileGroupHeader(L("GroupObjects", "設定のまとまり: プロファイル専用オブジェクト"));
            DrawProfileObjectsForCard(index, true);
            DrawProfileObjectsForCard(index, false);
            DrawProfileObjectConflictWarnings(index);

            if (DrawAdvancedFoldout(
                ProfileAdvancedPrefsPrefix + index,
                L("ProfileAdvancedFoldout", "プロファイルの詳細設定")
            ))
            {
                DrawProfileGroupHeader(L("GroupProfileAsset", "設定のまとまり: プロファイルアセット（共有設定）"));
                DrawProfileAssetForCard(index);
                DrawProfileFadeTargetSummary(material);
                DrawProfileGroupHeader(L("GroupMirrorQuality", "設定のまとまり: ミラー画質"));
                DrawProfileMirrorReflectionSettings(index);
            }
        }

        private bool DrawProfileCardHeader(
            int index,
            string title,
            string buttonLabel,
            bool isLocked,
            out bool isExpanded
        )
        {
            EnsureProfileFoldoutSize(profileMaterials.arraySize);

            Rect rowRect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight);
            CalculateProfileCardHeaderRects(
                rowRect,
                isLocked,
                out Rect foldoutRect,
                out Rect lockedLabelRect,
                out Rect buttonRect
            );

            int oldIndentLevel = EditorGUI.indentLevel;
            try
            {
                EditorGUI.indentLevel = 0;
                profileFoldouts[index] = EditorGUI.Foldout(foldoutRect, profileFoldouts[index], title, true);
            }
            finally
            {
                EditorGUI.indentLevel = oldIndentLevel;
            }
            isExpanded = profileFoldouts[index];

            if (isLocked)
                GUI.Label(lockedLabelRect, L("LockedLabel", "固定"), EditorStyles.miniLabel);

            using (new EditorGUI.DisabledScope(isLocked))
            {
                return GUI.Button(buttonRect, buttonLabel);
            }
        }

        internal static void CalculateProfileCardHeaderRects(
            Rect rowRect,
            bool isLocked,
            out Rect foldoutRect,
            out Rect lockedLabelRect,
            out Rect buttonRect
        )
        {
            Rect contentRect = new Rect(
                rowRect.x + ProfileHeaderHorizontalPadding,
                rowRect.y,
                Mathf.Max(0f, rowRect.width - ProfileHeaderHorizontalPadding * 2f),
                rowRect.height
            );

            float buttonWidth = Mathf.Min(ProfileHeaderButtonWidth, contentRect.width);
            buttonRect = new Rect(
                contentRect.xMax - buttonWidth,
                contentRect.y,
                buttonWidth,
                contentRect.height
            );

            float foldoutRight = buttonRect.x - ProfileHeaderGap;
            lockedLabelRect = new Rect(foldoutRight, contentRect.y, 0f, contentRect.height);
            if (isLocked)
            {
                float lockedLabelWidth = Mathf.Min(
                    ProfileHeaderLockedLabelWidth,
                    Mathf.Max(0f, foldoutRight - contentRect.x)
                );
                lockedLabelRect = new Rect(
                    foldoutRight - lockedLabelWidth,
                    contentRect.y,
                    lockedLabelWidth,
                    contentRect.height
                );
                foldoutRight = lockedLabelRect.x - ProfileHeaderGap;
            }

            foldoutRect = new Rect(
                contentRect.x,
                contentRect.y,
                Mathf.Max(0f, foldoutRight - contentRect.x),
                contentRect.height
            );
        }

        private void DrawProfileNameField(int index)
        {
            EditorGUILayout.PropertyField(
                profileNames.GetArrayElementAtIndex(index),
                LC("ProfileNameLabel", "プロファイル名")
            );
            string displayName = GetProfileDisplayName(index);
            if (string.IsNullOrEmpty(profileNames.GetArrayElementAtIndex(index).stringValue))
            {
                EditorGUILayout.HelpBox(
                    LF("ProfileNameFallbackHelp", "空欄のため表示名は「{0}」として扱います。", displayName),
                    MessageType.None
                );
            }
        }

        private static void DrawProfileGroupHeader(string label)
        {
            DrawInspectorSectionGap(ProfileGroupHeaderTopSpacing);
            Rect rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight + 2f);
            Rect lineRect = new Rect(rect.x, rect.y + rect.height - 2f, rect.width, 2f);
            EditorGUI.DrawRect(
                lineRect,
                new Color(NiAkaPink.r, NiAkaPink.g, NiAkaPink.b, 0.24f)
            );
            EditorGUI.LabelField(rect, label, EditorStyles.miniBoldLabel);
            DrawInspectorSectionGap(ProfileGroupHeaderBottomSpacing);
        }

        private void DrawDefaultProfileControl(int index)
        {
            bool isDefault = profileIsDefault.GetArrayElementAtIndex(index).boolValue;
            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.LabelField(
                    L("DefaultProfileLabel", "初期状態 / Reset先"),
                    isDefault ? L("DefaultProfileCurrent", "このプロファイルです") : L("DefaultProfileOther", "別のプロファイルです")
                );
                using (new EditorGUI.DisabledScope(isDefault))
                {
                    if (GUILayout.Button(L("SetDefaultProfileButton", "このプロファイルを初期状態にする"), GUILayout.Width(190)))
                    {
                        SetDefaultProfile(index);
                    }
                }
            }
        }

        private void DrawProfileSafetyHints(int index)
        {
            if (
                index > 0
                && IsDefaultProfileIndex(index)
                && index < profilePcOnly.arraySize
                && profilePcOnly.GetArrayElementAtIndex(index).boolValue
            )
            {
                EditorGUILayout.HelpBox(
                    L("DefaultPcOnlyWarning", "このプロファイルは初期状態ですがPC専用です。Quest / Android / iOS ではプロファイル 0（Off）へ戻ります。"),
                    MessageType.Warning
                );
            }

            if (index < profileHideBackground.arraySize && profileHideBackground.GetArrayElementAtIndex(index).boolValue)
            {
                EditorGUILayout.HelpBox(
                    L("HideBackgroundHelp", "背景を透明化は、プロファイル適用時にMaterialの Hide Background をONにします。"),
                    MessageType.None
                );
            }
        }

        private static string GetFallbackProfileName(int index)
        {
            switch (index)
            {
                case 0:
                    return "Off";
                case 1:
                    return L("FallbackHqMirror", "HQミラー");
                case 2:
                    return L("FallbackTransparentMirror", "半透明ミラー");
                case 3:
                    return L("FallbackLqMirror", "LQミラー");
                default:
                    return LF("ProfileTitlePrefix", "プロファイル {0}", index);
            }
        }

        private static void DrawProfileFadeTargetSummary(Material material)
        {
            if (material == null)
            {
                EditorGUILayout.HelpBox(
                    L("MaterialMissingHelp", "生成Materialは通常設定では表示しません。必要なら「Materialを自動生成/更新」を押してください。"),
                    MessageType.Warning
                );
                return;
            }

            if (!HasAnyFadeProperty(material))
            {
                EditorGUILayout.HelpBox(
                    L("NoFadePropertyHelp", "この Material には距離フェード用Propertyが見つかりません。フェード距離設定とGizmoの対象外です。"),
                    MessageType.Info
                );
                return;
            }

            EditorGUILayout.HelpBox(
                L("GeneratedMaterialHiddenHelp", "生成Materialは通常設定では表示しません。フェード距離は Step 4 の「フェード距離設定」でまとめて調整します。"),
                MessageType.Info
            );
        }

        private void DrawProfileMirrorReflectionSettings(int index)
        {
            EditorGUILayout.LabelField(L("MirrorQualityHeader", "ミラー画質設定"), EditorStyles.boldLabel);
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(
                profileDisablePixelLights.GetArrayElementAtIndex(index),
                LC("DisablePixelLightsLabel", "Pixel Lightsを無効化")
            );
            EditorGUILayout.PropertyField(
                profileTurnOffMirrorOcclusion.GetArrayElementAtIndex(index),
                LC("TurnOffMirrorOcclusionLabel", "ミラー遮蔽を無効化")
            );
            EditorGUILayout.PropertyField(
                profileReflectLayers.GetArrayElementAtIndex(index),
                LC("ReflectLayersLabel", "映すレイヤー")
            );
            EditorGUILayout.PropertyField(
                profileCameraClearFlags.GetArrayElementAtIndex(index),
                LC("CameraClearFlagsLabel", "背景の消し方")
            );
            EditorGUILayout.PropertyField(
                profileCustomSkyboxes.GetArrayElementAtIndex(index),
                LC("CustomSkyboxLabel", "専用Skybox")
            );
            EditorGUILayout.PropertyField(
                profileCustomClearColors.GetArrayElementAtIndex(index),
                LC("ClearColorLabel", "背景色")
            );
            EditorGUI.indentLevel--;
        }

        private void DrawProfileIconForCard(int index)
        {
            if (
                profileIconSprites == null
                || profileIconColors == null
                || index < 0
                || index >= profileIconSprites.arraySize
                || index >= profileIconColors.arraySize
            )
                return;

            EditorGUILayout.LabelField(L("ProfileIconHeader", "プロファイルアイコン"), EditorStyles.boldLabel);
            EditorGUI.indentLevel++;
            SerializedProperty iconSprite = profileIconSprites.GetArrayElementAtIndex(index);
            SerializedProperty iconColor = profileIconColors.GetArrayElementAtIndex(index);
            DrawProfileIconCard(index, iconSprite, iconColor);
            DrawLegacyProfileIconControls(index, iconSprite, iconColor);
            EditorGUI.indentLevel--;
        }

        private void DrawLegacyProfileIconControls(
            int index,
            SerializedProperty iconSprite,
            SerializedProperty iconColor
        )
        {
            if (profileIconObjects == null || index >= profileIconObjects.arraySize)
                return;

            SerializedProperty legacyObject = profileIconObjects.GetArrayElementAtIndex(index);
            if (legacyObject.objectReferenceValue == null)
                return;

            legacyProfileIconFoldout = EditorGUILayout.Foldout(
                legacyProfileIconFoldout,
                L("LegacyIconFoldout", "旧アイコン互換設定"),
                true
            );
            if (!legacyProfileIconFoldout)
                return;

            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                EditorGUILayout.HelpBox(
                    L(
                        "LegacyIconHelp",
                        "古い複数オブジェクト式のアイコン参照です。必要な場合だけ残し、通常は単一Image方式へ移行してください。"
                    ),
                    MessageType.Info
                );
                EditorGUILayout.PropertyField(
                    legacyObject,
                    LC("IconObjectLabel", "旧アイコン表示オブジェクト（互換）")
                );

                using (new EditorGUILayout.HorizontalScope())
                {
                    if (GUILayout.Button(L("LegacyIconMigrateButton", "このアイコンへ移行")))
                        MigrateLegacyProfileIcon(index, legacyObject, iconSprite, iconColor);
                    if (GUILayout.Button(L("LegacyIconClearButton", "旧参照だけ外す")))
                        legacyObject.objectReferenceValue = null;
                }
            }
        }

        private static void MigrateLegacyProfileIcon(
            int index,
            SerializedProperty legacyObject,
            SerializedProperty iconSprite,
            SerializedProperty iconColor
        )
        {
            GameObject legacyGameObject = legacyObject.objectReferenceValue as GameObject;
            Image legacyImage = legacyGameObject == null
                ? null
                : legacyGameObject.GetComponent<Image>();
            if (legacyImage == null && legacyGameObject != null)
                legacyImage = legacyGameObject.GetComponentInChildren<Image>(true);

            if (legacyImage != null)
            {
                iconSprite.objectReferenceValue = legacyImage.sprite;
                iconColor.colorValue = legacyImage.color;
            }
            else
            {
                iconSprite.objectReferenceValue = EzMirrorAssetResolver.LoadDefaultIconSprite(index);
            }
            legacyObject.objectReferenceValue = null;
        }

        private void DrawProfileIconCard(
            int index,
            SerializedProperty iconSprite,
            SerializedProperty iconColor
        )
        {
            EzMirror mirror = target as EzMirror;
            EzMirrorProfileAsset profileAsset = targets.Length == 1
                ? GetProfileAssetReference(mirror, index)
                : null;
            bool disablesAssetIconEditing = targets.Length > 1 && index > OffProfileIndex;
            Sprite defaultSprite = GetDefaultProfileIconSprite(index);
            Sprite sceneSprite = iconSprite.objectReferenceValue as Sprite;
            Sprite assetSprite = profileAsset == null ? null : profileAsset.iconSprite;
            bool hasLocalOverride =
                profileAsset != null
                && assetSprite == null
                && sceneSprite != null
                && sceneSprite != defaultSprite;
            Sprite explicitSprite =
                assetSprite ?? (profileAsset == null || hasLocalOverride ? sceneSprite : null);
            bool isMixed = iconSprite.hasMultipleDifferentValues;
            bool isColorMixed = iconColor.hasMultipleDifferentValues;
            Sprite previewSprite = isMixed ? null : explicitSprite ?? defaultSprite;
            Color previewColor = ResolveProfileIconColor(
                mirror,
                index,
                profileAsset,
                iconColor.colorValue
            );
            bool isUsingDefault =
                !isMixed && previewSprite != null && previewSprite == defaultSprite;

            float lineHeight = EditorGUIUtility.singleLineHeight;
            float verticalGap = EditorGUIUtility.standardVerticalSpacing;
            float cardHeight = Mathf.Max(
                IconPreviewSize + IconPreviewPadding * 2f,
                lineHeight * 5f + verticalGap * 4f + IconPreviewPadding * 2f
            );
            Rect rowRect = EditorGUILayout.GetControlRect(false, cardHeight);
            Rect cardRect = EditorGUI.IndentedRect(rowRect);
            if (Event.current.type == EventType.Repaint)
                EditorStyles.helpBox.Draw(cardRect, false, false, false, false);

            Rect iconRect = new Rect(
                cardRect.x + IconPreviewPadding,
                cardRect.y + IconPreviewPadding,
                IconPreviewSize,
                IconPreviewSize
            );
            DrawEditableIconThumbnail(
                iconRect,
                index,
                iconSprite,
                profileAsset,
                previewSprite,
                previewColor,
                isMixed,
                isColorMixed,
                isUsingDefault,
                disablesAssetIconEditing
            );

            float rightX = iconRect.xMax + IconPreviewTextGap;
            Rect rightRect = new Rect(
                rightX,
                cardRect.y + IconPreviewPadding,
                Mathf.Max(0f, cardRect.xMax - rightX - IconPreviewPadding),
                cardRect.height - IconPreviewPadding * 2f
            );
            DrawIconCardDetails(
                rightRect,
                index,
                profileAsset,
                iconSprite,
                iconColor,
                previewSprite,
                previewColor,
                explicitSprite,
                isMixed,
                isColorMixed,
                isUsingDefault,
                hasLocalOverride,
                disablesAssetIconEditing
            );
        }

        private void DrawEditableIconThumbnail(
            Rect rect,
            int index,
            SerializedProperty iconSprite,
            EzMirrorProfileAsset profileAsset,
            Sprite previewSprite,
            Color previewColor,
            bool isMixed,
            bool isColorMixed,
            bool isUsingDefault,
            bool isEditingDisabled
        )
        {
            bool oldMixedValue = EditorGUI.showMixedValue;
            EditorGUI.showMixedValue = isMixed;
            EditorGUI.BeginChangeCheck();
            Sprite selectedSprite;
            using (new EditorGUI.DisabledScope(isEditingDisabled))
            {
                selectedSprite = (Sprite)EditorGUI.ObjectField(
                    rect,
                    previewSprite,
                    typeof(Sprite),
                    false
                );
            }
            EditorGUI.showMixedValue = oldMixedValue;
            bool changed = EditorGUI.EndChangeCheck();
            if (!isEditingDisabled && changed)
                SetProfileIconFromInspector(index, iconSprite, profileAsset, selectedSprite);

            DrawIconThumbnailSurface(rect, previewSprite, previewColor, isMixed, isColorMixed, isUsingDefault);
        }

        private void DrawIconThumbnailSurface(
            Rect rect,
            Sprite previewSprite,
            Color previewColor,
            bool isMixed,
            bool isColorMixed,
            bool isUsingDefault
        )
        {
            if (Event.current.type != EventType.Repaint)
                return;

            Color backgroundColor = EditorGUIUtility.isProSkin
                ? new Color(0.12f, 0.12f, 0.14f)
                : new Color(0.98f, 0.94f, 0.97f);
            EditorGUI.DrawRect(rect, backgroundColor);
            GUI.Box(rect, GUIContent.none, EditorStyles.helpBox);

            if (isMixed || isColorMixed)
            {
                EditorGUI.LabelField(rect, "—", IconPreviewPlaceholderStyle);
                return;
            }

            if (previewSprite == null)
            {
                EditorGUI.LabelField(
                    rect,
                    L("IconSelectPlaceholder", "Select"),
                    IconPreviewPlaceholderStyle
                );
                return;
            }

            Rect previewRect = new Rect(
                rect.x + 5f,
                rect.y + 5f,
                rect.width - 10f,
                rect.height - 10f
            );
            DrawSpritePreview(previewRect, previewSprite, previewColor);

            if (isUsingDefault)
            {
                Rect badgeRect = new Rect(rect.x + 4f, rect.yMax - 18f, rect.width - 8f, 15f);
                GUI.Label(badgeRect, L("IconDefaultBadge", "標準"), IconPreviewBadgeStyle);
            }

            DrawRectOutline(rect, IconPreviewBorderColor);
        }

        internal static void DrawSpritePreview(Rect rect, Sprite sprite, Color tintColor)
        {
            Texture2D texture = sprite.texture;
            Rect spriteRect = sprite.rect;
            if (texture == null || spriteRect.width <= 0f || spriteRect.height <= 0f)
                return;

            Rect drawRect = FitRectToAspect(rect, spriteRect.width / spriteRect.height);
            Rect textureCoords = new Rect(
                spriteRect.x / texture.width,
                spriteRect.y / texture.height,
                spriteRect.width / texture.width,
                spriteRect.height / texture.height
            );
            Color oldColor = GUI.color;
            GUI.color = tintColor;
            GUI.DrawTextureWithTexCoords(drawRect, texture, textureCoords, true);
            GUI.color = oldColor;
        }

        private static Rect FitRectToAspect(Rect rect, float aspect)
        {
            float width = rect.width;
            float height = width / aspect;
            if (height > rect.height)
            {
                height = rect.height;
                width = height * aspect;
            }

            return new Rect(
                rect.x + (rect.width - width) * 0.5f,
                rect.y + (rect.height - height) * 0.5f,
                width,
                height
            );
        }

        private static void DrawRectOutline(Rect rect, Color color)
        {
            EditorGUI.DrawRect(new Rect(rect.x, rect.y, rect.width, 1f), color);
            EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - 1f, rect.width, 1f), color);
            EditorGUI.DrawRect(new Rect(rect.x, rect.y, 1f, rect.height), color);
            EditorGUI.DrawRect(new Rect(rect.xMax - 1f, rect.y, 1f, rect.height), color);
        }

        private void DrawIconCardDetails(
            Rect rect,
            int index,
            EzMirrorProfileAsset profileAsset,
            SerializedProperty iconSprite,
            SerializedProperty iconColor,
            Sprite previewSprite,
            Color previewColor,
            Sprite explicitSprite,
            bool isMixed,
            bool isColorMixed,
            bool isUsingDefault,
            bool hasLocalOverride,
            bool isEditingDisabled
        )
        {
            float lineHeight = EditorGUIUtility.singleLineHeight;
            float verticalGap = EditorGUIUtility.standardVerticalSpacing;
            Rect titleRect = new Rect(rect.x, rect.y, rect.width, lineHeight);
            Rect buttonsRect = new Rect(
                rect.x,
                rect.yMax - lineHeight,
                rect.width,
                lineHeight
            );
            Rect colorRect = new Rect(
                rect.x,
                buttonsRect.y - verticalGap - lineHeight,
                rect.width,
                lineHeight
            );
            Rect sourceRect = new Rect(
                rect.x,
                titleRect.yMax + verticalGap,
                rect.width,
                Mathf.Max(lineHeight, colorRect.y - titleRect.yMax - verticalGap * 2f)
            );

            GUI.Label(titleRect, GetIconCardTitle(index), EditorStyles.miniBoldLabel);
            GUI.Label(
                sourceRect,
                GetIconCardDescription(
                    index,
                    profileAsset,
                    isMixed,
                    isUsingDefault,
                    hasLocalOverride,
                    isEditingDisabled,
                    previewSprite
                ),
                EditorStyles.wordWrappedMiniLabel
            );

            bool oldMixedValue = EditorGUI.showMixedValue;
            EditorGUI.showMixedValue = isColorMixed;
            EditorGUI.BeginChangeCheck();
            Color selectedColor;
            using (new EditorGUI.DisabledScope(isEditingDisabled))
            {
                selectedColor = EditorGUI.ColorField(
                    colorRect,
                    LC("IconColorLabel", "アイコン色"),
                    previewColor,
                    true,
                    true,
                    true
                );
            }
            EditorGUI.showMixedValue = oldMixedValue;
            if (!isEditingDisabled && EditorGUI.EndChangeCheck())
                SetProfileIconColorFromInspector(index, iconColor, profileAsset, selectedColor);

            using (new EditorGUI.DisabledScope(isEditingDisabled || isMixed || explicitSprite == null))
            {
                Rect resetRect = new Rect(
                    buttonsRect.x,
                    buttonsRect.y,
                    Mathf.Min(IconPreviewButtonWidth, buttonsRect.width),
                    buttonsRect.height
                );
                if (GUI.Button(resetRect, L("IconResetButton", "標準に戻す"), EditorStyles.miniButton))
                    SetProfileIconFromInspector(index, iconSprite, profileAsset, null);
            }

            if (profileAsset == null)
                return;

            Rect assetRect = new Rect(
                buttonsRect.x + IconPreviewButtonWidth + 6f,
                buttonsRect.y,
                Mathf.Min(
                    IconPreviewButtonWidth,
                    Mathf.Max(0f, buttonsRect.width - IconPreviewButtonWidth - 6f)
                ),
                buttonsRect.height
            );
            if (
                assetRect.width > 30f
                && GUI.Button(
                    assetRect,
                    L("IconSelectAssetButton", "アセット選択"),
                    EditorStyles.miniButton
                )
            )
            {
                SelectAndPing(profileAsset);
            }
        }

        private void SetProfileIconFromInspector(
            int index,
            SerializedProperty iconSprite,
            EzMirrorProfileAsset profileAsset,
            Sprite selectedSprite
        )
        {
            if (targets.Length > 1 && index > OffProfileIndex)
                return;

            bool editsProfileAsset =
                profileAsset != null && index > OffProfileIndex && targets.Length == 1;
            if (editsProfileAsset)
            {
                Undo.RecordObject(profileAsset, "Change EzMirror プロファイルアイコン");
                profileAsset.iconSprite = selectedSprite;
                EditorUtility.SetDirty(profileAsset);
            }

            iconSprite.objectReferenceValue = selectedSprite ?? GetDefaultProfileIconSprite(index);
            Repaint();
        }

        private void SetProfileIconColorFromInspector(
            int index,
            SerializedProperty iconColor,
            EzMirrorProfileAsset profileAsset,
            Color selectedColor
        )
        {
            if (targets.Length > 1 && index > OffProfileIndex)
                return;

            bool editsProfileAsset =
                profileAsset != null && index > OffProfileIndex && targets.Length == 1;
            if (editsProfileAsset)
            {
                Undo.RecordObject(profileAsset, "Change EzMirror プロファイルアイコン色");
                profileAsset.iconColor = selectedColor;
                EditorUtility.SetDirty(profileAsset);
            }

            iconColor.colorValue = selectedColor;
            Repaint();
        }

        private static Sprite GetDefaultProfileIconSprite(int index)
        {
            return EzMirrorAssetResolver.LoadDefaultIconSprite(index)
                ?? EzMirrorAssetResolver.LoadDefaultIconSprite(int.MaxValue);
        }

        internal static Color GetDefaultProfileIconColorForTest(int profileIndex)
        {
            return GetDefaultProfileIconColor(profileIndex);
        }

        private static Color GetDefaultProfileIconColor(int profileIndex)
        {
            switch (profileIndex)
            {
                case 0:
                    return new Color(0.72f, 0.74f, 0.8f, 1f);
                case 1:
                    return new Color(0.38f, 0.78f, 1f, 1f);
                case 2:
                    return new Color(0.74f, 0.55f, 1f, 1f);
                case 3:
                    return new Color(0.45f, 0.88f, 0.62f, 1f);
                default:
                    return new Color(1f, 0.82f, 0.35f, 1f);
            }
        }

        private static Color ResolveProfileIconColor(
            EzMirror mirror,
            int profileIndex,
            EzMirrorProfileAsset profileAsset,
            Color serializedFallback
        )
        {
            if (profileAsset != null && profileIndex > OffProfileIndex)
                return EnsureVisibleIconColor(profileAsset.iconColor, profileIndex);

            Color existingColor = GetArrayValue(
                mirror == null ? null : mirror.profileIconColors,
                profileIndex,
                serializedFallback
            );
            return EnsureVisibleIconColor(existingColor, profileIndex);
        }

        private static Color EnsureVisibleIconColor(Color color, int profileIndex)
        {
            return color.a > 0f ? color : GetDefaultProfileIconColor(profileIndex);
        }

        private void DrawProfileObjectsForCard(int index, bool isActiveList)
        {
            SerializedProperty objects = isActiveList ? profileActiveObjects : profileInactiveObjects;
            SerializedProperty modes = isActiveList ? profileActiveObjectModes : profileInactiveObjectModes;
            string label = isActiveList
                ? L("ProfileActiveObjectsLabel", "このプロファイルでONにするオブジェクト（このプロファイル中だけ表示）")
                : L("ProfileInactiveObjectsLabel", "このプロファイルでOFFにするオブジェクト（このプロファイル中だけ非表示）");
            string emptyMessage = isActiveList
                ? L("ProfileActiveObjectsEmpty", "このプロファイルだけでONにする追加オブジェクトは未設定です。")
                : L("ProfileInactiveObjectsEmpty", "このプロファイルだけでOFFにする追加オブジェクトは未設定です。");
            string addButton = isActiveList
                ? L("AddProfileActiveObjectButton", "このプロファイルでONにするオブジェクトを追加")
                : L("AddProfileInactiveObjectButton", "このプロファイルでOFFにするオブジェクトを追加");

            EditorGUILayout.LabelField(label, EditorStyles.boldLabel);

            bool hasObject = false;
            for (int i = 0; i < objects.arraySize; i++)
            {
                if (i >= modes.arraySize)
                    continue;
                if (modes.GetArrayElementAtIndex(i).intValue != index)
                    continue;

                hasObject = true;
                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUILayout.PropertyField(objects.GetArrayElementAtIndex(i), GUIContent.none);
                    if (GUILayout.Button(L("RemoveProfileObjectButton", "リストから外す"), GUILayout.Width(88)))
                    {
                        DeleteProfileObjectAt(i, isActiveList);
                        return;
                    }
                }
            }

            if (!hasObject)
                EditorGUILayout.HelpBox(emptyMessage, MessageType.None);

            if (GUILayout.Button(addButton))
            {
                AddProfileObject(index, isActiveList);
                return;
            }
        }

        private void DrawProfileObjectConflictWarnings(int index)
        {
            for (int i = 0; i < profileActiveObjects.arraySize; i++)
            {
                if (i >= profileActiveObjectModes.arraySize)
                    continue;
                if (profileActiveObjectModes.GetArrayElementAtIndex(i).intValue != index)
                    continue;

                UnityEngine.Object targetObject = profileActiveObjects.GetArrayElementAtIndex(i).objectReferenceValue;
                if (targetObject == null)
                    continue;
                if (!ContainsProfileObject(profileInactiveObjects, profileInactiveObjectModes, index, targetObject))
                    continue;

                EditorGUILayout.HelpBox(
                    L("ProfileObjectConflictError", "同じGameObjectが、このプロファイルのON対象とOFF対象の両方に入っています。どちらか片方にしてください。"),
                    MessageType.Error
                );
                return;
            }
        }

        private static bool ContainsProfileObject(
            SerializedProperty objects,
            SerializedProperty modes,
            int profileIndex,
            UnityEngine.Object targetObject
        )
        {
            for (int i = 0; i < objects.arraySize; i++)
            {
                if (i >= modes.arraySize)
                    continue;
                if (modes.GetArrayElementAtIndex(i).intValue != profileIndex)
                    continue;
                if (objects.GetArrayElementAtIndex(i).objectReferenceValue == targetObject)
                    return true;
            }

            return false;
        }

        private void AddProfile()
        {
            int index = profileMaterials.arraySize;
            SetProfileArraySizes(index + 1);
            EnsureProfileFoldoutSize(index + 1);
            profileMaterials.GetArrayElementAtIndex(index).objectReferenceValue = null;
            profileBaseMaterials.GetArrayElementAtIndex(index).objectReferenceValue =
                GetDefaultBaseMaterialForProfile(index);
            if (targets.Length == 1)
                EnsureProfileAssetReferencesSize(target as EzMirror, index + 1);
            profileNames.GetArrayElementAtIndex(index).stringValue = GetFallbackProfileName(index);
            profileMirrorEnabled.GetArrayElementAtIndex(index).boolValue = true;
            profileIsDefault.GetArrayElementAtIndex(index).boolValue = false;
            profileDisablePixelLights.GetArrayElementAtIndex(index).boolValue =
                GetDefaultProfileDisablePixelLights(index);
            profileTurnOffMirrorOcclusion.GetArrayElementAtIndex(index).boolValue =
                GetDefaultProfileTurnOffMirrorOcclusion(index);
            profileReflectLayers.GetArrayElementAtIndex(index).intValue =
                GetDefaultProfileReflectLayers(index).value;
            profileCameraClearFlags.GetArrayElementAtIndex(index).intValue =
                (int)GetDefaultProfileCameraClearFlags(index);
            profileCustomSkyboxes.GetArrayElementAtIndex(index).objectReferenceValue = null;
            profileCustomClearColors.GetArrayElementAtIndex(index).colorValue =
                GetDefaultProfileClearColor(index);
            profileHideBackground.GetArrayElementAtIndex(index).boolValue =
                GetDefaultProfileHideBackground(index);
            profileTransparency.GetArrayElementAtIndex(index).floatValue =
                GetDefaultProfileTransparency(index);
            profilePcOnly.GetArrayElementAtIndex(index).boolValue = false;
            profileIconSprites.GetArrayElementAtIndex(index).objectReferenceValue =
                GetDefaultProfileIconSprite(index);
            profileIconColors.GetArrayElementAtIndex(index).colorValue =
                GetDefaultProfileIconColor(index);
            profileIconObjects.GetArrayElementAtIndex(index).objectReferenceValue = null;
            EnsureOffProfileDefaults();
            EnsureSingleDefaultProfile();
        }

        private void RemoveProfile(int index)
        {
            if (index <= 0)
                return;

            EnsureSerializedProfileArraySizes();

            DeleteArrayElement(profileMaterials, index);
            DeleteArrayElement(profileBaseMaterials, index);
            if (targets.Length == 1)
                DeleteProfileAssetReferenceAt(target as EzMirror, index);
            DeleteArrayElement(profileNames, index);
            DeleteArrayElement(profileIsDefault, index);
            DeleteArrayElement(profileMirrorEnabled, index);
            DeleteArrayElement(profileDisablePixelLights, index);
            DeleteArrayElement(profileTurnOffMirrorOcclusion, index);
            DeleteArrayElement(profileReflectLayers, index);
            DeleteArrayElement(profileCameraClearFlags, index);
            DeleteArrayElement(profileCustomSkyboxes, index);
            DeleteArrayElement(profileCustomClearColors, index);
            DeleteArrayElement(profileHideBackground, index);
            DeleteArrayElement(profileTransparency, index);
            DeleteArrayElement(profilePcOnly, index);
            DeleteArrayElement(profileIconSprites, index);
            DeleteArrayElement(profileIconColors, index);
            DeleteArrayElement(profileIconObjects, index);
            RemoveProfileObjectsForRemovedProfile(index, true);
            RemoveProfileObjectsForRemovedProfile(index, false);
            EnsureProfileFoldoutSize(profileMaterials.arraySize);
            EnsureOffProfileDefaults();
            EnsureSingleDefaultProfile();
        }

        private void EnsureSerializedProfileArraySizes()
        {
            InsertOffSlotForLegacySerializedProfiles();
            int size = GetSerializedProfileArrayMaxSize();
            int oldBaseMaterialSize = profileBaseMaterials.arraySize;
            int oldNameSize = profileNames.arraySize;
            int oldDefaultSize = profileIsDefault.arraySize;
            int oldMirrorEnabledSize = profileMirrorEnabled.arraySize;
            int oldDisablePixelLightsSize = profileDisablePixelLights.arraySize;
            int oldTurnOffMirrorOcclusionSize = profileTurnOffMirrorOcclusion.arraySize;
            int oldReflectLayersSize = profileReflectLayers.arraySize;
            int oldCameraClearFlagsSize = profileCameraClearFlags.arraySize;
            int oldCustomSkyboxSize = profileCustomSkyboxes.arraySize;
            int oldCustomClearColorSize = profileCustomClearColors.arraySize;
            int oldHideBackgroundSize = profileHideBackground.arraySize;
            int oldTransparencySize = profileTransparency.arraySize;
            int oldPcOnlySize = profilePcOnly.arraySize;
            int oldIconSpriteSize = profileIconSprites.arraySize;
            int oldIconColorSize = profileIconColors.arraySize;
            int oldIconObjectSize = profileIconObjects.arraySize;

            SetProfileArraySizes(size);

            for (int i = oldBaseMaterialSize; i < profileBaseMaterials.arraySize; i++)
                profileBaseMaterials.GetArrayElementAtIndex(i).objectReferenceValue =
                    i == 0 ? null : GetDefaultBaseMaterialForProfile(i);
            FillDefaultSerializedProfileNames(oldNameSize);
            if (oldDefaultSize == 0 && profileIsDefault.arraySize > 0)
                profileIsDefault.GetArrayElementAtIndex(0).boolValue = true;
            for (int i = oldMirrorEnabledSize; i < profileMirrorEnabled.arraySize; i++)
                profileMirrorEnabled.GetArrayElementAtIndex(i).boolValue = i > 0;
            for (int i = oldDisablePixelLightsSize; i < profileDisablePixelLights.arraySize; i++)
                profileDisablePixelLights.GetArrayElementAtIndex(i).boolValue =
                    GetDefaultProfileDisablePixelLights(i);
            for (int i = oldTurnOffMirrorOcclusionSize; i < profileTurnOffMirrorOcclusion.arraySize; i++)
                profileTurnOffMirrorOcclusion.GetArrayElementAtIndex(i).boolValue =
                    GetDefaultProfileTurnOffMirrorOcclusion(i);
            for (int i = oldReflectLayersSize; i < profileReflectLayers.arraySize; i++)
                profileReflectLayers.GetArrayElementAtIndex(i).intValue =
                    GetDefaultProfileReflectLayers(i).value;
            for (int i = oldCameraClearFlagsSize; i < profileCameraClearFlags.arraySize; i++)
                profileCameraClearFlags.GetArrayElementAtIndex(i).intValue =
                    (int)GetDefaultProfileCameraClearFlags(i);
            for (int i = oldCustomSkyboxSize; i < profileCustomSkyboxes.arraySize; i++)
                profileCustomSkyboxes.GetArrayElementAtIndex(i).objectReferenceValue = null;
            for (int i = oldCustomClearColorSize; i < profileCustomClearColors.arraySize; i++)
                profileCustomClearColors.GetArrayElementAtIndex(i).colorValue =
                    GetDefaultProfileClearColor(i);
            for (int i = oldHideBackgroundSize; i < profileHideBackground.arraySize; i++)
                profileHideBackground.GetArrayElementAtIndex(i).boolValue =
                    GetDefaultProfileHideBackground(i);
            for (int i = oldTransparencySize; i < profileTransparency.arraySize; i++)
                profileTransparency.GetArrayElementAtIndex(i).floatValue =
                    GetDefaultProfileTransparency(i);
            for (int i = oldPcOnlySize; i < profilePcOnly.arraySize; i++)
                profilePcOnly.GetArrayElementAtIndex(i).boolValue = false;
            for (int i = oldIconSpriteSize; i < profileIconSprites.arraySize; i++)
                profileIconSprites.GetArrayElementAtIndex(i).objectReferenceValue =
                    GetDefaultProfileIconSprite(i);
            for (int i = oldIconColorSize; i < profileIconColors.arraySize; i++)
                profileIconColors.GetArrayElementAtIndex(i).colorValue =
                    GetDefaultProfileIconColor(i);
            for (int i = oldIconObjectSize; i < profileIconObjects.arraySize; i++)
                profileIconObjects.GetArrayElementAtIndex(i).objectReferenceValue = null;

            EnsureOffProfileDefaults();
            EnsureSingleDefaultProfile();
            EnsureProfileFoldoutSize(profileMaterials.arraySize);
        }

        private static bool GetDefaultProfileDisablePixelLights(int profileIndex)
        {
            return profileIndex > OffProfileIndex;
        }

        private static bool GetDefaultProfileTurnOffMirrorOcclusion(int profileIndex)
        {
            return profileIndex > OffProfileIndex;
        }

        private static MirrorClearFlags GetDefaultProfileCameraClearFlags(int profileIndex)
        {
            return (MirrorClearFlags)(profileIndex == LqProfileIndex ? 2 : 0);
        }

        private static Color GetDefaultProfileClearColor(int profileIndex)
        {
            return profileIndex == LqProfileIndex
                ? new Color(0f, 0f, 0f, 0f)
                : new Color(0f, 0f, 0f, 1f);
        }

        private static bool GetDefaultProfileHideBackground(int profileIndex)
        {
            return profileIndex == LqProfileIndex;
        }

        private void FillDefaultSerializedProfileNames(int oldSize)
        {
            int startIndex = oldSize <= 0 ? 0 : oldSize;
            for (int i = startIndex; i < profileNames.arraySize; i++)
                profileNames.GetArrayElementAtIndex(i).stringValue = GetFallbackProfileName(i);
        }

        private void EnsureProfileFoldoutSize(int size)
        {
            int safeSize = Mathf.Max(1, size);
            if (profileFoldouts != null && profileFoldouts.Length == safeSize)
                return;

            bool[] resized = new bool[safeSize];
            for (int i = 0; i < safeSize; i++)
                resized[i] = profileFoldouts == null || i >= profileFoldouts.Length || profileFoldouts[i];
            profileFoldouts = resized;
        }

        private void SetDefaultProfile(int index)
        {
            for (int i = 0; i < profileIsDefault.arraySize; i++)
                profileIsDefault.GetArrayElementAtIndex(i).boolValue = i == index;
        }

        private void EnsureSingleDefaultProfile()
        {
            int defaultIndex = -1;
            for (int i = 0; i < profileIsDefault.arraySize; i++)
            {
                if (!profileIsDefault.GetArrayElementAtIndex(i).boolValue)
                    continue;
                if (defaultIndex < 0)
                {
                    defaultIndex = i;
                    continue;
                }
                profileIsDefault.GetArrayElementAtIndex(i).boolValue = false;
            }

            if (defaultIndex < 0 && profileIsDefault.arraySize > 0)
                profileIsDefault.GetArrayElementAtIndex(0).boolValue = true;
        }

        private void SetProfileArraySizes(int size)
        {
            int safeSize = Mathf.Max(1, size);
            profileMaterials.arraySize = safeSize;
            profileBaseMaterials.arraySize = safeSize;
            profileNames.arraySize = safeSize;
            profileIsDefault.arraySize = safeSize;
            profileMirrorEnabled.arraySize = safeSize;
            profileDisablePixelLights.arraySize = safeSize;
            profileTurnOffMirrorOcclusion.arraySize = safeSize;
            profileReflectLayers.arraySize = safeSize;
            profileCameraClearFlags.arraySize = safeSize;
            profileCustomSkyboxes.arraySize = safeSize;
            profileCustomClearColors.arraySize = safeSize;
            profileHideBackground.arraySize = safeSize;
            profileTransparency.arraySize = safeSize;
            profilePcOnly.arraySize = safeSize;
            profileIconSprites.arraySize = safeSize;
            profileIconColors.arraySize = safeSize;
            profileIconObjects.arraySize = safeSize;
        }

        private int GetSerializedProfileArrayMaxSize()
        {
            int size = 1;
            size = Mathf.Max(size, profileMaterials.arraySize);
            size = Mathf.Max(size, profileBaseMaterials.arraySize);
            size = Mathf.Max(size, profileNames.arraySize);
            size = Mathf.Max(size, profileIsDefault.arraySize);
            size = Mathf.Max(size, profileMirrorEnabled.arraySize);
            size = Mathf.Max(size, profileDisablePixelLights.arraySize);
            size = Mathf.Max(size, profileTurnOffMirrorOcclusion.arraySize);
            size = Mathf.Max(size, profileReflectLayers.arraySize);
            size = Mathf.Max(size, profileCameraClearFlags.arraySize);
            size = Mathf.Max(size, profileCustomSkyboxes.arraySize);
            size = Mathf.Max(size, profileCustomClearColors.arraySize);
            size = Mathf.Max(size, profileHideBackground.arraySize);
            size = Mathf.Max(size, profileTransparency.arraySize);
            size = Mathf.Max(size, profilePcOnly.arraySize);
            size = Mathf.Max(size, profileIconSprites.arraySize);
            size = Mathf.Max(size, profileIconColors.arraySize);
            size = Mathf.Max(size, profileIconObjects.arraySize);
            return size;
        }

        private void InsertOffSlotForLegacySerializedProfiles()
        {
            if (profileMaterials.arraySize == 0)
                return;
            if (profileMaterials.GetArrayElementAtIndex(0).objectReferenceValue == null)
                return;
            if (profileMirrorEnabled.arraySize != 0 || profileIsDefault.arraySize != 0)
                return;

            InsertDefaultElementAtStart(profileMaterials);
            InsertDefaultElementAtStart(profileBaseMaterials);
            InsertDefaultElementAtStart(profileNames);
            InsertDefaultElementAtStart(profileDisablePixelLights);
            InsertDefaultElementAtStart(profileTurnOffMirrorOcclusion);
            InsertDefaultElementAtStart(profileReflectLayers);
            InsertDefaultElementAtStart(profileCameraClearFlags);
            InsertDefaultElementAtStart(profileCustomSkyboxes);
            InsertDefaultElementAtStart(profileCustomClearColors);
            InsertDefaultElementAtStart(profileHideBackground);
            InsertDefaultElementAtStart(profileTransparency);
            InsertDefaultElementAtStart(profilePcOnly);
            InsertDefaultElementAtStart(profileIconSprites);
            InsertDefaultElementAtStart(profileIconColors);
            profileMaterials.GetArrayElementAtIndex(0).objectReferenceValue = null;
            if (profileNames.arraySize > 0)
                profileNames.GetArrayElementAtIndex(0).stringValue = GetFallbackProfileName(0);
            if (profileIconSprites.arraySize > 0)
                profileIconSprites.GetArrayElementAtIndex(0).objectReferenceValue =
                    GetDefaultProfileIconSprite(0);
            if (profileIconColors.arraySize > 0)
                profileIconColors.GetArrayElementAtIndex(0).colorValue =
                    GetDefaultProfileIconColor(0);
        }

        private static void InsertDefaultElementAtStart(SerializedProperty property)
        {
            if (property.arraySize == 0)
                return;
            property.InsertArrayElementAtIndex(0);
            ClearArrayElement(property.GetArrayElementAtIndex(0));
        }

        private static void ClearArrayElement(SerializedProperty element)
        {
            switch (element.propertyType)
            {
                case SerializedPropertyType.ObjectReference:
                    element.objectReferenceValue = null;
                    break;
                case SerializedPropertyType.Boolean:
                    element.boolValue = false;
                    break;
                case SerializedPropertyType.String:
                    element.stringValue = string.Empty;
                    break;
                case SerializedPropertyType.Integer:
                case SerializedPropertyType.Enum:
                    element.intValue = 0;
                    break;
                case SerializedPropertyType.Float:
                    element.floatValue = 0f;
                    break;
                case SerializedPropertyType.Color:
                    element.colorValue = Color.black;
                    break;
            }
        }

        private void EnsureOffProfileDefaults()
        {
            if (profileMaterials.arraySize == 0)
                return;
            profileMaterials.GetArrayElementAtIndex(0).objectReferenceValue = null;
            if (profileBaseMaterials.arraySize > 0)
                profileBaseMaterials.GetArrayElementAtIndex(0).objectReferenceValue = null;
            if (profileMirrorEnabled.arraySize > 0)
                profileMirrorEnabled.GetArrayElementAtIndex(0).boolValue = false;
            if (profileTransparency.arraySize > 0)
                profileTransparency.GetArrayElementAtIndex(0).floatValue = 0f;
            if (profilePcOnly.arraySize > 0)
                profilePcOnly.GetArrayElementAtIndex(0).boolValue = false;
            if (profileIconSprites.arraySize > 0)
                profileIconSprites.GetArrayElementAtIndex(0).objectReferenceValue =
                    GetDefaultProfileIconSprite(0);
            if (profileIconColors.arraySize > 0)
                profileIconColors.GetArrayElementAtIndex(0).colorValue =
                    GetDefaultProfileIconColor(0);
        }

        private void EnsureProfileObjectArraySizes()
        {
            int activeSize = Mathf.Min(profileActiveObjects.arraySize, profileActiveObjectModes.arraySize);
            profileActiveObjects.arraySize = activeSize;
            profileActiveObjectModes.arraySize = activeSize;

            int inactiveSize = Mathf.Min(profileInactiveObjects.arraySize, profileInactiveObjectModes.arraySize);
            profileInactiveObjects.arraySize = inactiveSize;
            profileInactiveObjectModes.arraySize = inactiveSize;
        }

        private void AddProfileObject(int profileIndex, bool isActiveList)
        {
            SerializedProperty objects = isActiveList ? profileActiveObjects : profileInactiveObjects;
            SerializedProperty modes = isActiveList ? profileActiveObjectModes : profileInactiveObjectModes;
            int newIndex = objects.arraySize;
            objects.arraySize++;
            modes.arraySize++;
            objects.GetArrayElementAtIndex(newIndex).objectReferenceValue = null;
            modes.GetArrayElementAtIndex(newIndex).intValue = profileIndex;
        }

        private void DeleteProfileObjectAt(int index, bool isActiveList)
        {
            DeleteArrayElement(isActiveList ? profileActiveObjects : profileInactiveObjects, index);
            DeleteArrayElement(isActiveList ? profileActiveObjectModes : profileInactiveObjectModes, index);
        }

        private void RemoveProfileObjectsForRemovedProfile(int removedProfileIndex, bool isActiveList)
        {
            SerializedProperty objects = isActiveList ? profileActiveObjects : profileInactiveObjects;
            SerializedProperty modes = isActiveList ? profileActiveObjectModes : profileInactiveObjectModes;
            int validSize = Mathf.Min(objects.arraySize, modes.arraySize);
            objects.arraySize = validSize;
            modes.arraySize = validSize;
            for (int i = validSize - 1; i >= 0; i--)
            {
                int mode = modes.GetArrayElementAtIndex(i).intValue;
                if (mode == removedProfileIndex)
                {
                    DeleteArrayElement(objects, i);
                    DeleteArrayElement(modes, i);
                }
                else if (mode > removedProfileIndex)
                {
                    modes.GetArrayElementAtIndex(i).intValue = mode - 1;
                }
            }
        }

        private static void DeleteArrayElement(SerializedProperty property, int index)
        {
            if (index < 0 || index >= property.arraySize)
                return;
            int oldSize = property.arraySize;
            property.DeleteArrayElementAtIndex(index);
            if (property.arraySize == oldSize)
                property.DeleteArrayElementAtIndex(index);
        }

        private void ApplyProfileAssetFromInspector(
            int profileIndex,
            ScriptableObject profileAsset,
            bool regenerateMaterials
        )
        {
            if (profileAsset == null)
                return;

            serializedObject.ApplyModifiedProperties();
            for (int i = 0; i < targets.Length; i++)
            {
                EzMirror mirror = targets[i] as EzMirror;
                if (mirror == null)
                    continue;

                Undo.RecordObject(mirror, "Apply EzMirror プロファイルアセット");
                ApplyProfileAssetToMirror(mirror, profileIndex, profileAsset, regenerateMaterials);
                UdonSharpEditorUtility.CopyProxyToUdon(mirror);
                EditorUtility.SetDirty(mirror);
                PrefabUtility.RecordPrefabInstancePropertyModifications(mirror);
            }

            serializedObject.Update();
        }

        private void CreateProfileAssetFromInspector(int profileIndex)
        {
            if (targets.Length != 1)
                return;

            serializedObject.ApplyModifiedProperties();
            EzMirror mirror = target as EzMirror;
            if (mirror == null)
                return;

            string assetPath = GenerateUniqueProfileAssetPath(mirror, profileIndex);
            EzMirrorProfileAsset profileAsset = CreateProfileAssetFromMirror(
                mirror,
                profileIndex,
                assetPath
            );
            if (profileAsset == null)
                return;

            Undo.RecordObject(mirror, "Create EzMirror プロファイルアセット");
            EnsureProfileArraySizesForCount(mirror, profileIndex + 1);
            SetProfileAssetReference(mirror, profileIndex, profileAsset);
            UdonSharpEditorUtility.CopyProxyToUdon(mirror);
            EditorUtility.SetDirty(mirror);
            PrefabUtility.RecordPrefabInstancePropertyModifications(mirror);
            serializedObject.Update();
            SelectAndPing(profileAsset);
        }

        public static void ApplyProfileAssetToMirror(
            EzMirror mirror,
            int profileIndex,
            ScriptableObject profileAsset,
            bool regenerateMaterials,
            bool preserveExistingDefaultIconOverride = false
        )
        {
            if (mirror == null || profileIndex <= OffProfileIndex)
                return;

            EzMirrorProfileAsset typedAsset = profileAsset as EzMirrorProfileAsset;
            if (typedAsset == null)
                return;

            EnsureProfileArraySizesForCount(mirror, profileIndex + 1);
            SetProfileAssetReference(mirror, profileIndex, typedAsset);
            mirror.profileNames[profileIndex] = typedAsset.profileName;
            if (typedAsset.isDefault)
            {
                for (int i = 0; i < mirror.profileIsDefault.Length; i++)
                    mirror.profileIsDefault[i] = i == profileIndex;
            }
            else
            {
                mirror.profileIsDefault[profileIndex] = false;
            }

            mirror.profileMirrorEnabled[profileIndex] = typedAsset.mirrorEnabled;
            mirror.profilePcOnly[profileIndex] = typedAsset.pcOnly;
            mirror.profileDisablePixelLights[profileIndex] = typedAsset.disablePixelLights;
            mirror.profileTurnOffMirrorOcclusion[profileIndex] =
                typedAsset.turnOffMirrorOcclusion;
            mirror.profileReflectLayers[profileIndex] = typedAsset.reflectLayers;
            mirror.profileCameraClearFlags[profileIndex] = typedAsset.cameraClearFlags;
            mirror.profileCustomSkyboxes[profileIndex] = typedAsset.customSkybox;
            mirror.profileCustomClearColors[profileIndex] = typedAsset.customClearColor;
            mirror.profileHideBackground[profileIndex] = typedAsset.hideBackground;
            mirror.profileTransparency[profileIndex] = Mathf.Clamp01(typedAsset.transparency);
            mirror.profileBaseMaterials[profileIndex] = typedAsset.baseMaterial;
            mirror.profileIconSprites[profileIndex] = ResolveProfileIconSprite(
                mirror,
                profileIndex,
                typedAsset,
                preserveExistingDefaultIconOverride
            );
            mirror.profileIconColors[profileIndex] = ResolveProfileIconColor(
                mirror,
                profileIndex,
                typedAsset,
                GetDefaultProfileIconColor(profileIndex)
            );

            if (regenerateMaterials)
            {
                EnsureGeneratedProfileMaterials(mirror, true, true);
            }
            else if (
                mirror.profileMaterials != null
                && profileIndex < mirror.profileMaterials.Length
                && mirror.profileMaterials[profileIndex] != null
            )
            {
                ApplyDefaultProfileMaterialParameters(
                    mirror.profileMaterials[profileIndex],
                    mirror,
                    profileIndex,
                    true,
                    false
                );
            }

            EditorUtility.SetDirty(mirror);
        }

        public static void GenerateDefaultProfileAssets()
        {
            EnsureProfileAssetFolder();
            UpsertDefaultProfileAsset(EzMirrorAssetResolver.GetDefaultProfileAssetPath(1), 1);
            UpsertDefaultProfileAsset(EzMirrorAssetResolver.GetDefaultProfileAssetPath(2), 2);
            UpsertDefaultProfileAsset(EzMirrorAssetResolver.GetDefaultProfileAssetPath(3), 3);
            AssetDatabase.SaveAssets();
        }

        private static void RegenerateAndAssignDefaultProfileAssets(EzMirror mirror)
        {
            GenerateDefaultProfileAssets();
            AssignDefaultProfileAssets(mirror);
        }

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

            EnsureProfileArraySizesForCount(mirror, 4);
            for (int profileIndex = 1; profileIndex <= 3; profileIndex++)
            {
                ApplyProfileAssetToMirror(
                    mirror,
                    profileIndex,
                    EzMirrorAssetResolver.LoadDefaultProfileAsset(profileIndex),
                    false,
                    true
                );
            }
        }

        private static EzMirrorProfileAsset CreateProfileAssetFromMirror(
            EzMirror mirror,
            int profileIndex,
            string assetPath
        )
        {
            if (mirror == null || profileIndex <= OffProfileIndex || string.IsNullOrEmpty(assetPath))
                return null;

            EnsureProfileAssetFolder();
            EzMirrorProfileAsset profileAsset =
                ScriptableObject.CreateInstance<EzMirrorProfileAsset>();
            CopyMirrorProfileToAsset(mirror, profileIndex, profileAsset);
            AssetDatabase.CreateAsset(profileAsset, assetPath);
            Undo.RegisterCreatedObjectUndo(profileAsset, "Create EzMirror プロファイルアセット");
            AssetDatabase.SaveAssets();
            return AssetDatabase.LoadAssetAtPath<EzMirrorProfileAsset>(assetPath);
        }

        private static EzMirrorProfileAsset UpsertDefaultProfileAsset(
            string assetPath,
            int profileIndex
        )
        {
            EzMirrorProfileAsset profileAsset =
                AssetDatabase.LoadAssetAtPath<EzMirrorProfileAsset>(assetPath);
            bool created = false;
            if (profileAsset == null)
            {
                if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetPath) != null)
                {
                    Debug.LogError(
                        "[EzMirror] Default profile asset path already contains a different asset: "
                            + assetPath
                    );
                    return null;
                }

                profileAsset = ScriptableObject.CreateInstance<EzMirrorProfileAsset>();
                AssetDatabase.CreateAsset(profileAsset, assetPath);
                Undo.RegisterCreatedObjectUndo(
                    profileAsset,
                    "Create Default EzMirror プロファイルアセット"
                );
                created = true;
            }

            if (!created)
                Undo.RecordObject(profileAsset, "Update Default EzMirror プロファイルアセット");
            FillDefaultProfileAsset(profileAsset, profileIndex);
            EditorUtility.SetDirty(profileAsset);
            return profileAsset;
        }

        private static void FillDefaultProfileAsset(
            EzMirrorProfileAsset profileAsset,
            int profileIndex
        )
        {
            if (profileAsset == null)
                return;

            profileAsset.profileName = GetFallbackProfileName(profileIndex);
            profileAsset.isDefault = false;
            profileAsset.mirrorEnabled = profileIndex > OffProfileIndex;
            profileAsset.pcOnly = profileIndex == 1 || profileIndex == 2;
            profileAsset.disablePixelLights = profileIndex > OffProfileIndex;
            profileAsset.turnOffMirrorOcclusion = profileIndex > OffProfileIndex;
            profileAsset.reflectLayers = GetDefaultProfileReflectLayers(profileIndex);
            profileAsset.cameraClearFlags = (MirrorClearFlags)(
                profileIndex == LqProfileIndex ? 2 : 0
            );
            profileAsset.customSkybox = null;
            profileAsset.customClearColor = profileIndex == LqProfileIndex
                ? new Color(0f, 0f, 0f, 0f)
                : new Color(0f, 0f, 0f, 1f);
            profileAsset.hideBackground = profileIndex == LqProfileIndex;
            profileAsset.transparency = GetDefaultProfileTransparency(profileIndex);
            profileAsset.baseMaterial = GetDefaultBaseMaterialForProfile(profileIndex);
            profileAsset.iconSprite = EzMirrorAssetResolver.LoadDefaultIconSprite(profileIndex);
            profileAsset.iconColor = GetDefaultProfileIconColor(profileIndex);
        }

        private static void CopyMirrorProfileToAsset(
            EzMirror mirror,
            int profileIndex,
            EzMirrorProfileAsset profileAsset
        )
        {
            profileAsset.profileName = GetProfileName(mirror, profileIndex);
            profileAsset.isDefault = GetArrayValue(mirror.profileIsDefault, profileIndex, false);
            profileAsset.mirrorEnabled = GetArrayValue(
                mirror.profileMirrorEnabled,
                profileIndex,
                profileIndex > OffProfileIndex
            );
            profileAsset.pcOnly = GetArrayValue(mirror.profilePcOnly, profileIndex, false);
            profileAsset.disablePixelLights = GetArrayValue(
                mirror.profileDisablePixelLights,
                profileIndex,
                profileIndex > OffProfileIndex
            );
            profileAsset.turnOffMirrorOcclusion = GetArrayValue(
                mirror.profileTurnOffMirrorOcclusion,
                profileIndex,
                profileIndex > OffProfileIndex
            );
            profileAsset.reflectLayers = GetArrayValue(
                mirror.profileReflectLayers,
                profileIndex,
                GetDefaultProfileReflectLayers(profileIndex)
            );
            profileAsset.cameraClearFlags = GetArrayValue(
                mirror.profileCameraClearFlags,
                profileIndex,
                (MirrorClearFlags)(profileIndex == LqProfileIndex ? 2 : 0)
            );
            profileAsset.customSkybox = GetArrayValue<Material>(
                mirror.profileCustomSkyboxes,
                profileIndex,
                null
            );
            profileAsset.customClearColor = GetArrayValue(
                mirror.profileCustomClearColors,
                profileIndex,
                profileIndex == LqProfileIndex
                    ? new Color(0f, 0f, 0f, 0f)
                    : new Color(0f, 0f, 0f, 1f)
            );
            profileAsset.hideBackground = GetArrayValue(
                mirror.profileHideBackground,
                profileIndex,
                profileIndex == LqProfileIndex
            );
            profileAsset.transparency = GetArrayValue(
                mirror.profileTransparency,
                profileIndex,
                GetDefaultProfileTransparency(profileIndex)
            );
            profileAsset.baseMaterial = GetArrayValue(
                mirror.profileBaseMaterials,
                profileIndex,
                GetDefaultBaseMaterialForProfile(profileIndex)
            );
            profileAsset.iconSprite = GetArrayValue<Sprite>(
                mirror.profileIconSprites,
                profileIndex,
                EzMirrorAssetResolver.LoadDefaultIconSprite(profileIndex)
            );
            profileAsset.iconColor = GetArrayValue(
                mirror.profileIconColors,
                profileIndex,
                GetDefaultProfileIconColor(profileIndex)
            );
            EditorUtility.SetDirty(profileAsset);
        }

        private static void EnsureProfileArraySizesForCount(EzMirror mirror, int minimumSize)
        {
            if (mirror == null)
                return;

            int count = Mathf.Max(Mathf.Max(1, minimumSize), GetEditorProfileCount(mirror));
            int oldNameCount = mirror.profileNames == null ? 0 : mirror.profileNames.Length;
            int oldDefaultCount = mirror.profileIsDefault == null ? 0 : mirror.profileIsDefault.Length;
            int oldMirrorEnabledCount = mirror.profileMirrorEnabled == null
                ? 0
                : mirror.profileMirrorEnabled.Length;
            int oldDisablePixelLightsCount = mirror.profileDisablePixelLights == null
                ? 0
                : mirror.profileDisablePixelLights.Length;
            int oldTurnOffMirrorOcclusionCount = mirror.profileTurnOffMirrorOcclusion == null
                ? 0
                : mirror.profileTurnOffMirrorOcclusion.Length;
            int oldReflectLayersCount = mirror.profileReflectLayers == null
                ? 0
                : mirror.profileReflectLayers.Length;
            int oldCameraClearFlagsCount = mirror.profileCameraClearFlags == null
                ? 0
                : mirror.profileCameraClearFlags.Length;
            int oldCustomClearColorsCount = mirror.profileCustomClearColors == null
                ? 0
                : mirror.profileCustomClearColors.Length;
            int oldHideBackgroundCount = mirror.profileHideBackground == null
                ? 0
                : mirror.profileHideBackground.Length;
            int oldTransparencyCount = mirror.profileTransparency == null
                ? 0
                : mirror.profileTransparency.Length;
            int oldPcOnlyCount = mirror.profilePcOnly == null ? 0 : mirror.profilePcOnly.Length;
            int oldBaseMaterialCount = mirror.profileBaseMaterials == null
                ? 0
                : mirror.profileBaseMaterials.Length;
            int oldIconColorCount = mirror.profileIconColors == null
                ? 0
                : mirror.profileIconColors.Length;

            mirror.profileMaterials = ResizeCopy(mirror.profileMaterials, count);
            mirror.profileBaseMaterials = ResizeCopy(mirror.profileBaseMaterials, count);
            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);
            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;
            EnsureProfileAssetReferencesSize(mirror, count);
            EnsureDefaultProfileNames(mirror.profileNames, oldNameCount);
            if (oldDefaultCount == 0)
                mirror.profileIsDefault[0] = true;
            for (int i = oldMirrorEnabledCount; i < count; i++)
                mirror.profileMirrorEnabled[i] = i > OffProfileIndex;
            for (int i = oldDisablePixelLightsCount; i < count; i++)
                mirror.profileDisablePixelLights[i] = i > OffProfileIndex;
            for (int i = oldTurnOffMirrorOcclusionCount; i < count; i++)
                mirror.profileTurnOffMirrorOcclusion[i] = i > OffProfileIndex;
            for (int i = oldReflectLayersCount; i < count; i++)
                mirror.profileReflectLayers[i] = GetDefaultProfileReflectLayers(i);
            for (int i = oldCameraClearFlagsCount; i < count; i++)
                mirror.profileCameraClearFlags[i] = (MirrorClearFlags)(
                    i == LqProfileIndex ? 2 : 0
                );
            for (int i = oldCustomClearColorsCount; i < count; i++)
            {
                mirror.profileCustomClearColors[i] = i == LqProfileIndex
                    ? new Color(0f, 0f, 0f, 0f)
                    : new Color(0f, 0f, 0f, 1f);
            }
            for (int i = oldHideBackgroundCount; i < count; i++)
                mirror.profileHideBackground[i] = i == LqProfileIndex;
            for (int i = oldTransparencyCount; i < count; i++)
                mirror.profileTransparency[i] = GetDefaultProfileTransparency(i);
            for (int i = oldPcOnlyCount; i < count; i++)
                mirror.profilePcOnly[i] = i == 1 || i == 2;
            for (int i = oldBaseMaterialCount; i < count; i++)
                mirror.profileBaseMaterials[i] =
                    i == OffProfileIndex ? null : GetDefaultBaseMaterialForProfile(i);
            for (int i = oldIconColorCount; i < count; i++)
                mirror.profileIconColors[i] = GetDefaultProfileIconColor(i);
            EnsureProfileIconSprites(mirror);
            EnsureProfileIconColors(mirror);
        }

        private static void EnsureProfileAssetFolder()
        {
            EzMirrorAssetResolver.GetProfileAssetFolder();
        }

        private static string GenerateUniqueProfileAssetPath(EzMirror mirror, int profileIndex)
        {
            EnsureProfileAssetFolder();
            string fileName = SanitizeAssetFileName(GetProfileName(mirror, profileIndex));
            if (string.IsNullOrEmpty(fileName))
                fileName = "EzMirrorProfile" + profileIndex;
            return EzMirrorAssetResolver.GenerateProfileAssetPath(fileName);
        }

        private static string SanitizeAssetFileName(string value)
        {
            string fileName = string.IsNullOrEmpty(value) ? string.Empty : value.Trim();
            char[] invalidChars = Path.GetInvalidFileNameChars();
            for (int i = 0; i < invalidChars.Length; i++)
                fileName = fileName.Replace(invalidChars[i], '_');
            return fileName;
        }

        private static string GetProfileName(EzMirror mirror, int profileIndex)
        {
            string name = GetArrayValue<string>(mirror.profileNames, profileIndex, null);
            return string.IsNullOrEmpty(name) ? GetFallbackProfileName(profileIndex) : name;
        }

        private static T GetArrayValue<T>(T[] values, int index, T fallback)
        {
            if (values == null || index < 0 || index >= values.Length)
                return fallback;
            return values[index];
        }

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

            int count = Mathf.Max(1, GetEditorProfileCount(mirror));
            mirror.profileIconSprites = ResizeCopy(mirror.profileIconSprites, count);
            for (int i = 0; i < count; i++)
                mirror.profileIconSprites[i] = ResolveProfileIconSprite(mirror, i, null, true);
        }

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

            int count = Mathf.Max(1, GetEditorProfileCount(mirror));
            mirror.profileIconColors = ResizeCopy(mirror.profileIconColors, count);
            for (int i = 0; i < count; i++)
            {
                mirror.profileIconColors[i] = ResolveProfileIconColor(
                    mirror,
                    i,
                    null,
                    GetDefaultProfileIconColor(i)
                );
            }
        }

        private static Sprite ResolveProfileIconSprite(
            EzMirror mirror,
            int profileIndex,
            EzMirrorProfileAsset profileAsset,
            bool preserveExistingDefaultIconOverride = false
        )
        {
            bool hasProfileAsset = profileAsset != null;
            if (profileAsset == null)
            {
                profileAsset = GetProfileAssetReference(mirror, profileIndex);
                hasProfileAsset = profileAsset != null;
            }
            Sprite existingIcon = GetArrayValue<Sprite>(
                mirror == null ? null : mirror.profileIconSprites,
                profileIndex,
                null
            );
            if (
                preserveExistingDefaultIconOverride
                && ShouldPreserveExistingDefaultProfileIcon(
                    existingIcon,
                    profileIndex,
                    profileAsset
                )
            )
            {
                return existingIcon;
            }

            if (profileAsset != null && profileAsset.iconSprite != null)
                return profileAsset.iconSprite;

            if (hasProfileAsset)
                return EzMirrorAssetResolver.LoadDefaultIconSprite(profileIndex)
                    ?? EzMirrorAssetResolver.LoadDefaultIconSprite(int.MaxValue);

            if (existingIcon != null)
                return existingIcon;

            Sprite defaultIcon = EzMirrorAssetResolver.LoadDefaultIconSprite(profileIndex);
            if (defaultIcon != null)
                return defaultIcon;

            return EzMirrorAssetResolver.LoadDefaultIconSprite(int.MaxValue);
        }

        private static bool ShouldPreserveExistingDefaultProfileIcon(
            Sprite existingIcon,
            int profileIndex,
            EzMirrorProfileAsset profileAsset
        )
        {
            if (existingIcon == null || profileAsset == null || !IsDefaultProfileAsset(profileAsset, profileIndex))
                return false;
            if (existingIcon == profileAsset.iconSprite)
                return false;

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

            Sprite genericIcon = EzMirrorAssetResolver.LoadDefaultIconSprite(int.MaxValue);
            return existingIcon != genericIcon;
        }

        private static bool IsDefaultProfileAsset(
            EzMirrorProfileAsset profileAsset,
            int profileIndex
        )
        {
            if (profileAsset == null || profileIndex <= OffProfileIndex)
                return false;
            string assetPath = AssetDatabase.GetAssetPath(profileAsset);
            return !string.IsNullOrEmpty(assetPath)
                && assetPath == EzMirrorAssetResolver.GetDefaultProfileAssetPath(profileIndex);
        }

        private static EzMirrorProfileAssetReferences GetProfileAssetReferences(
            EzMirror mirror,
            bool createIfMissing
        )
        {
            if (mirror == null)
                return null;

            EzMirrorProfileAssetReferences references =
                mirror.GetComponent<EzMirrorProfileAssetReferences>();
            if (references != null || !createIfMissing)
                return references;

            references = Undo.AddComponent<EzMirrorProfileAssetReferences>(mirror.gameObject);
            MarkProfileAssetReferencesDirty(references);
            return references;
        }

        private static EzMirrorProfileAsset GetProfileAssetReference(
            EzMirror mirror,
            int profileIndex
        )
        {
            if (profileIndex <= OffProfileIndex)
                return null;

            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, false);
            if (
                references == null
                || references.profileAssets == null
                || profileIndex >= references.profileAssets.Length
            )
            {
                return null;
            }

            return references.profileAssets[profileIndex];
        }

        private static int GetProfileAssetReferencesCount(EzMirror mirror)
        {
            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, false);
            return references == null || references.profileAssets == null
                ? 0
                : references.profileAssets.Length;
        }

        private static void EnsureProfileAssetReferencesSize(EzMirror mirror, int size)
        {
            int safeSize = Mathf.Max(1, size);
            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, true);
            if (references == null)
                return;

            EzMirrorProfileAsset[] resized = ResizeCopy(references.profileAssets, safeSize);
            bool changed = references.profileAssets == null
                || references.profileAssets.Length != safeSize;
            bool offSlotChanged = resized.Length > 0 && resized[0] != null;
            if (!changed && !offSlotChanged)
                return;

            Undo.RecordObject(references, "Resize EzMirror プロファイルアセット References");
            references.profileAssets = resized;
            if (offSlotChanged)
            {
                references.profileAssets[0] = null;
            }
            MarkProfileAssetReferencesDirty(references);
        }

        private static void SetProfileAssetReference(
            EzMirror mirror,
            int profileIndex,
            EzMirrorProfileAsset profileAsset
        )
        {
            if (profileIndex <= OffProfileIndex)
                return;

            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, false);
            if (profileAsset == null && references == null)
                return;

            EnsureProfileAssetReferencesSize(mirror, profileIndex + 1);
            references = GetProfileAssetReferences(mirror, false);
            if (references == null || references.profileAssets == null)
                return;
            if (references.profileAssets[profileIndex] == profileAsset)
                return;

            Undo.RecordObject(references, "Assign EzMirror プロファイルアセット");
            references.profileAssets[profileIndex] = profileAsset;
            MarkProfileAssetReferencesDirty(references);
        }

        private static void DeleteProfileAssetReferenceAt(EzMirror mirror, int index)
        {
            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, false);
            if (
                references == null
                || references.profileAssets == null
                || index <= OffProfileIndex
                || index >= references.profileAssets.Length
            )
            {
                return;
            }

            EzMirrorProfileAsset[] values =
                new EzMirrorProfileAsset[references.profileAssets.Length - 1];
            for (int source = 0, dest = 0; source < references.profileAssets.Length; source++)
            {
                if (source == index)
                    continue;
                values[dest] = references.profileAssets[source];
                dest++;
            }

            Undo.RecordObject(references, "Remove EzMirror プロファイルアセット Reference");
            references.profileAssets = values;
            references.profileAssets[0] = null;
            MarkProfileAssetReferencesDirty(references);
        }

        private static void InsertOffProfileAssetReference(EzMirror mirror)
        {
            EzMirrorProfileAssetReferences references =
                GetProfileAssetReferences(mirror, false);
            if (references == null || references.profileAssets == null)
                return;

            Undo.RecordObject(references, "Insert EzMirror Off プロファイルアセット Reference");
            references.profileAssets = InsertDefaultAtStart(references.profileAssets);
            references.profileAssets[0] = null;
            MarkProfileAssetReferencesDirty(references);
        }

        private static void MarkProfileAssetReferencesDirty(
            EzMirrorProfileAssetReferences references
        )
        {
            if (references == null)
                return;

            EditorUtility.SetDirty(references);
            PrefabUtility.RecordPrefabInstancePropertyModifications(references);
        }
    }
}
#endif
