using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.SceneManagement;
using UnityEngine;

namespace CatsUdonTools.Editor
{
    /// <summary>
    /// UnityPackage Publisher の「アニメーション（複数のクリップ）」の欄。
    /// クリップの表から、テンプレート MultiClip の AnimatorController を作って Animator に付ける。付いている Controller が MultiClip なら表に読み戻す。
    /// 表の状態はウィンドウと一緒にドメインリロードをまたいで残すため Serializable にし、ウィンドウの Undo に載せる
    /// </summary>
    [Serializable]
    internal sealed class MultiClipAnimationSection
    {
        private const string ToolsFolder = "Assets/CatsUdonTools";
        private const string ControllerExtension = ".controller";
        private const string ControllerFileSuffix = "_MultiClip";
        private const float RowNumberWidth = 24f;
        private const float RowButtonWidth = 24f;
        private const float FpsPopupWidth = 64f;
        private const float SectionLabelWidth = 130f;

        private static readonly string[] ModeLabels = { "Loop（ループ）", "Oneshot（1 回）", "IntroLoop（イントロ→ループ）" };
        private static readonly string[] FpsLabels = { "30 fps", "15 fps" };

        [SerializeField] private bool expanded = true;
        [SerializeField] private Animator animator;
        [SerializeField] private List<MultiClipRow> rows = new List<MultiClipRow>();
        [SerializeField] private string controllerPath = "";

        // 表を最後に読み戻した（または作った）時の Controller。Animator の Controller が外で変わったことに気付くため
        [SerializeField] private RuntimeAnimatorController shownController;
        [SerializeField] private bool rowsEdited;
        [SerializeField] private string statusMessage = "";
        [SerializeField] private MessageType statusType = MessageType.None;

        private static GUIStyle headerStyle;

        // 行の数が変わる操作は、描画の途中で行うと Layout と食い違うので、欄を描き終えてから行う
        [NonSerialized] private List<Action> deferredActions;

        internal void Draw(EditorWindow owner, GameObject exportTarget)
        {
            if (headerStyle == null)
            {
                headerStyle = new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold };
            }

            if (deferredActions == null)
            {
                deferredActions = new List<Action>();
            }

            expanded = EditorGUILayout.Foldout(expanded, "アニメーション（複数のクリップ）", true, headerStyle);
            if (!expanded)
            {
                return;
            }

            if (Event.current.type == EventType.Layout)
            {
                FollowControllerChange(false);
            }

            var previousLabelWidth = EditorGUIUtility.labelWidth;
            EditorGUIUtility.labelWidth = SectionLabelWidth;
            try
            {
                EditorGUILayout.HelpBox("1 つのモデルに複数のアニメーション（クリップ）を持たせ、CatsUdon のコマンドで切り替えたいときに使います。" +
                    "1 種類だけなら、今までどおり AnimationTemplates の Controller を複製して使えます。", MessageType.None);

                var issues = MultiClipAnimatorTemplate.Validate(rows);
                issues.AddRange(ValidateTrees(exportTarget));
                var animatorBlocked = DrawAnimatorField(owner, exportTarget);
                EditorGUILayout.Space(4);
                DrawTable(owner, issues);
                EditorGUILayout.Space(4);
                var pathError = DrawControllerPath(owner);
                EditorGUILayout.Space(4);
                DrawCreateButton(owner, issues, animatorBlocked, pathError);
            }
            finally
            {
                EditorGUIUtility.labelWidth = previousLabelWidth;
            }

            if (deferredActions.Count > 0)
            {
                foreach (var action in deferredActions)
                {
                    action();
                }

                deferredActions.Clear();
                owner.Repaint();
            }
        }

        // ---- Animator ----

        // 戻り値: Controller を作れない理由があれば true
        private bool DrawAnimatorField(EditorWindow owner, GameObject exportTarget)
        {
            EditorGUI.BeginChangeCheck();
            var picked = (Animator)EditorGUILayout.ObjectField("アニメさせる Animator", animator, typeof(Animator), true);
            if (EditorGUI.EndChangeCheck())
            {
                deferredActions.Add(() => SetAnimator(owner, picked));
            }

            if (animator == null)
            {
                EditorGUILayout.HelpBox("アニメさせる Animator（アバターなどのルートの Animator）を選んでください。表は先に作っておくこともできます。", MessageType.Info);
                return true;
            }

            if (EditorUtility.IsPersistent(animator))
            {
                EditorGUILayout.HelpBox("シーン上の Animator を選んでください（Project のプレハブは選べません）。", MessageType.Error);
                return true;
            }

            var blocked = false;
            var templateAncestor = FindTemplateAncestor(animator);
            if (templateAncestor != null)
            {
                EditorGUILayout.HelpBox($"この Animator は、テンプレートの Animator「{templateAncestor.name}」の中にあります。入れ子の子には複数のクリップを持たせられません（子は OneShot / Loop / IntroLoop のどれか 1 つにしてください）。", MessageType.Error);
                blocked = true;
            }

            var multiClipDescendant = FindMultiClipDescendant(animator);
            if (multiClipDescendant != null)
            {
                EditorGUILayout.HelpBox($"この Animator の中の「{multiClipDescendant.name}」にも複数のクリップの Controller が付いています。入れ子の子は OneShot / Loop / IntroLoop のどれか 1 つにしてください。", MessageType.Warning);
            }

            if (exportTarget != null && !animator.transform.IsChildOf(exportTarget.transform))
            {
                EditorGUILayout.HelpBox($"この Animator は Export GameObject（{exportTarget.name}）の中にありません。このままでは書き出しに含まれません。", MessageType.Warning);
            }

            using (new EditorGUI.DisabledScope(true))
            {
                EditorGUILayout.ObjectField("今の Controller", animator.runtimeAnimatorController, typeof(RuntimeAnimatorController), false);
            }

            var read = MultiClipAnimatorTemplate.Read(animator.runtimeAnimatorController);
            DrawControllerKind(read);

            var readable = read.Kind == MultiClipControllerKind.MultiClip || read.Kind == MultiClipControllerKind.SingleTemplate;
            using (new EditorGUI.DisabledScope(!readable))
            {
                if (GUILayout.Button(new GUIContent("Controller から表に読み直す", "今の Controller の中身で表を置き換えます（Ctrl+Z で戻せます）")))
                {
                    deferredActions.Add(() => LoadRows(owner, read, true));
                }
            }

            return blocked;
        }

        private static void DrawControllerKind(MultiClipReadResult read)
        {
            switch (read.Kind)
            {
                case MultiClipControllerKind.None:
                    EditorGUILayout.HelpBox("Controller が付いていません。［Controller を作る］で作って付けます。", MessageType.None);
                    break;
                case MultiClipControllerKind.NotTemplate:
                    EditorGUILayout.HelpBox("テンプレートではない Controller です。［Controller を作る］で MultiClip の Controller に付け替えます（今の Controller のファイルは消しません）。", MessageType.None);
                    break;
                case MultiClipControllerKind.OverrideController:
                    EditorGUILayout.HelpBox("テンプレートの Override Controller です。表には読み戻せません。［Controller を作る］で MultiClip の Controller に付け替えます。", MessageType.Warning);
                    break;
                case MultiClipControllerKind.SingleTemplate:
                    EditorGUILayout.HelpBox($"今までの 1 種類のテンプレート（{read.Rows[0].mode}）です。［Controller から表に読み直す］で 1 行目に読み込めます。今の Controller のファイルは消しません。", MessageType.None);
                    break;
                case MultiClipControllerKind.MultiClip:
                    EditorGUILayout.HelpBox($"複数のクリップの Controller（MultiClip・{read.Rows.Count} 個）です。", MessageType.None);
                    break;
                case MultiClipControllerKind.Broken:
                    EditorGUILayout.HelpBox("テンプレートの形になっていません。［Controller を作る］で作り直すと直ります。\n" + string.Join("\n", read.Problems), MessageType.Warning);
                    return;
            }

            if (read.Kind != MultiClipControllerKind.OverrideController)
            {
                foreach (var problem in read.Problems)
                {
                    EditorGUILayout.HelpBox(problem, MessageType.Warning);
                }
            }
        }

        private void SetAnimator(EditorWindow owner, Animator picked)
        {
            Undo.RecordObject(owner, "アニメさせる Animator を選ぶ");
            animator = picked;
            statusMessage = "";
            FollowControllerChange(true);
        }

        // Animator の Controller が変わったら（選び直した・手で付け替えた・Undo など）、表を読み戻せるなら読み戻し、保存先を決め直す。
        // 表を手で変えている途中なら、表は消さずに知らせるだけにする
        private void FollowControllerChange(bool force)
        {
            if (animator == null)
            {
                return;
            }

            var current = animator.runtimeAnimatorController;
            if (!force && current == shownController)
            {
                return;
            }

            shownController = current;
            var read = MultiClipAnimatorTemplate.Read(current);
            var replaceTable = read.Kind == MultiClipControllerKind.MultiClip
                || (read.Kind == MultiClipControllerKind.SingleTemplate && rows.Count == 0);
            if (replaceTable && !rowsEdited)
            {
                rows = read.Rows;
                statusMessage = read.Kind == MultiClipControllerKind.MultiClip ? "Animator の Controller を表に読み込みました。" : "今までのテンプレートを 1 行目に読み込みました。";
                statusType = MessageType.Info;
            }
            else if (replaceTable)
            {
                statusMessage = "Animator の Controller が変わりました。表は変えていません（読み込むには［Controller から表に読み直す］）。";
                statusType = MessageType.Info;
            }

            controllerPath = GetDefaultControllerPath(read);
        }

        private void LoadRows(EditorWindow owner, MultiClipReadResult read, bool announce)
        {
            Undo.RecordObject(owner, "Controller から表に読み直す");
            rows = read.Rows.Select(row => row.Clone()).ToList();
            rowsEdited = false;
            shownController = animator != null ? animator.runtimeAnimatorController : null;
            if (announce)
            {
                statusMessage = $"Controller からクリップを {rows.Count} 個読み込みました。";
                statusType = MessageType.Info;
            }
        }

        // 同じモデル（Export GameObject。指定が無いか外にあればシーンのルート）の、いちばん外側のテンプレートを並び順に集め、
        // 選んでいる Animator は表の内容に置き換えて、ツリー間の 1 番目の決まりを確かめる
        private List<MultiClipIssue> ValidateTrees(GameObject exportTarget)
        {
            if (animator == null || EditorUtility.IsPersistent(animator))
            {
                return new List<MultiClipIssue>();
            }

            var modelRoot = exportTarget != null && animator.transform.IsChildOf(exportTarget.transform) ? exportTarget.transform : animator.transform.root;
            var trees = new List<MultiClipTree>();
            foreach (var candidate in modelRoot.GetComponentsInChildren<Animator>(true))
            {
                var isTable = candidate == animator;
                if (!isTable && !MultiClipAnimatorTemplate.IsTemplateController(candidate.runtimeAnimatorController))
                {
                    continue;
                }

                if (HasTemplateAncestorWithin(candidate, modelRoot) || IsExcludedFromExport(candidate.transform, modelRoot))
                {
                    continue;
                }

                IList<string> names = null;
                if (isTable)
                {
                    names = rows.Select(row => row.name ?? "").ToList();
                }
                else
                {
                    var read = MultiClipAnimatorTemplate.Read(candidate.runtimeAnimatorController);
                    if (read.Kind == MultiClipControllerKind.MultiClip)
                    {
                        names = read.Rows.Select(row => row.name).ToList();
                    }
                }

                trees.Add(new MultiClipTree { Label = isTable ? $"この表（{animator.name}）" : candidate.name, ClipNames = names, IsTable = isTable });
            }

            return MultiClipAnimatorTemplate.ValidateFirstClipAcrossTrees(trees);
        }

        private static bool HasTemplateAncestorWithin(Animator target, Transform modelRoot)
        {
            if (target.transform == modelRoot)
            {
                return false;
            }

            for (var parent = target.transform.parent; parent != null; parent = parent.parent)
            {
                var parentAnimator = parent.GetComponent<Animator>();
                if (parentAnimator != null && MultiClipAnimatorTemplate.IsTemplateController(parentAnimator.runtimeAnimatorController))
                {
                    return true;
                }

                if (parent == modelRoot)
                {
                    break;
                }
            }

            return false;
        }

        // 書き出しで消される所（_IgnoreCatsUdonGuides・EditorOnly タグの下）のテンプレートは数えない
        private static bool IsExcludedFromExport(Transform target, Transform modelRoot)
        {
            for (var current = target; current != null && current != modelRoot; current = current.parent)
            {
                if (CatsUdonExporterBuilder.IsExcluded(current.gameObject))
                {
                    return true;
                }
            }

            return false;
        }

        private static Animator FindTemplateAncestor(Animator target)
        {
            for (var parent = target.transform.parent; parent != null; parent = parent.parent)
            {
                var parentAnimator = parent.GetComponent<Animator>();
                if (parentAnimator != null && MultiClipAnimatorTemplate.IsTemplateController(parentAnimator.runtimeAnimatorController))
                {
                    return parentAnimator;
                }
            }

            return null;
        }

        private static Animator FindMultiClipDescendant(Animator target)
        {
            return target.GetComponentsInChildren<Animator>(true)
                .FirstOrDefault(child => child != target && MultiClipAnimatorTemplate.LooksLikeMultiClip(child.runtimeAnimatorController));
        }

        // ---- クリップの表 ----

        private void DrawTable(EditorWindow owner, List<MultiClipIssue> issues)
        {
            EditorGUILayout.LabelField("クリップ（上から順に 1 番・2 番…。1 番が最初に流れます）", EditorStyles.miniBoldLabel);

            for (var index = 0; index < rows.Count; index++)
            {
                DrawRow(owner, index, issues.Where(issue => issue.RowIndex == index));
            }

            using (new EditorGUI.DisabledScope(rows.Count >= MultiClipAnimatorTemplate.MaxClipCount))
            {
                if (GUILayout.Button($"クリップを足す（{MultiClipAnimatorTemplate.MaxClipCount} 個まで）"))
                {
                    deferredActions.Add(() =>
                    {
                        Undo.RecordObject(owner, "クリップを足す");
                        rows.Add(new MultiClipRow());
                        rowsEdited = true;
                    });
                }
            }

            if (rows.Count > 0)
            {
                var length = rows.Sum(row => MultiClipAnimatorTemplate.LengthSeconds(row));
                var counted = rows.Sum(row => MultiClipAnimatorTemplate.CountedSeconds(row));
                EditorGUILayout.LabelField($"{rows.Count} 個・合計 {length:0.0} 秒（上限の目安に数える長さ {counted:0.0} 秒 / {MultiClipAnimatorTemplate.TotalSecondsWarning:0} 秒。15 fps は半分に数えます）", EditorStyles.wordWrappedMiniLabel);
            }

            foreach (var issue in issues.Where(issue => issue.RowIndex < 0))
            {
                EditorGUILayout.HelpBox(issue.Message, ToMessageType(issue.Level));
            }
        }

        private void DrawRow(EditorWindow owner, int index, IEnumerable<MultiClipIssue> rowIssues)
        {
            var row = rows[index];

            // 種類を変えた直後も、この描画の間は変える前の種類で並べる（Layout と食い違わないように）
            var mode = row.mode;

            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                using (new EditorGUILayout.HorizontalScope())
                {
                    GUILayout.Label($"{index + 1}.", GUILayout.Width(RowNumberWidth));

                    EditorGUI.BeginChangeCheck();
                    var name = EditorGUILayout.TextField(row.name);
                    if (EditorGUI.EndChangeCheck())
                    {
                        Undo.RecordObject(owner, "クリップ名を変える");
                        row.name = name;
                        rowsEdited = true;
                    }

                    using (new EditorGUI.DisabledScope(index == 0))
                    {
                        if (GUILayout.Button(new GUIContent("↑", "1 つ上へ"), GUILayout.Width(RowButtonWidth)))
                        {
                            deferredActions.Add(() => MoveRow(owner, index, index - 1));
                        }
                    }

                    using (new EditorGUI.DisabledScope(index == rows.Count - 1))
                    {
                        if (GUILayout.Button(new GUIContent("↓", "1 つ下へ"), GUILayout.Width(RowButtonWidth)))
                        {
                            deferredActions.Add(() => MoveRow(owner, index, index + 1));
                        }
                    }

                    if (GUILayout.Button(new GUIContent("×", "この行を消す（Ctrl+Z で戻せます）"), GUILayout.Width(RowButtonWidth)))
                    {
                        deferredActions.Add(() =>
                        {
                            Undo.RecordObject(owner, "クリップを消す");
                            rows.RemoveAt(index);
                            rowsEdited = true;
                        });
                    }
                }

                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUI.BeginChangeCheck();
                    var pickedMode = (MultiClipMode)EditorGUILayout.Popup("種類", (int)mode, ModeLabels);
                    var pickedFps = EditorGUILayout.Popup(row.fps == MultiClipAnimatorTemplate.HalfFps ? 1 : 0, FpsLabels, GUILayout.Width(FpsPopupWidth)) == 1
                        ? MultiClipAnimatorTemplate.HalfFps
                        : MultiClipAnimatorTemplate.DefaultFps;
                    if (EditorGUI.EndChangeCheck())
                    {
                        Undo.RecordObject(owner, "クリップの種類を変える");
                        row.mode = pickedMode;
                        row.fps = pickedFps;
                        rowsEdited = true;
                    }
                }

                if (mode == MultiClipMode.IntroLoop)
                {
                    DrawClipField(owner, index, "イントロのクリップ", row.introClip, clip => row.introClip = clip);
                    DrawClipField(owner, index, "ループのクリップ", row.clip, clip => row.clip = clip);
                }
                else
                {
                    DrawClipField(owner, index, "クリップ", row.clip, clip => row.clip = clip);
                }

                EditorGUILayout.LabelField($"長さ {MultiClipAnimatorTemplate.LengthSeconds(row):0.00} 秒", EditorStyles.miniLabel);

                foreach (var issue in rowIssues)
                {
                    EditorGUILayout.HelpBox(issue.Message, ToMessageType(issue.Level));
                }
            }
        }

        private void DrawClipField(EditorWindow owner, int index, string label, AnimationClip current, Action<AnimationClip> assign)
        {
            EditorGUI.BeginChangeCheck();
            var picked = (AnimationClip)EditorGUILayout.ObjectField(label, current, typeof(AnimationClip), false);
            if (!EditorGUI.EndChangeCheck())
            {
                return;
            }

            Undo.RecordObject(owner, "クリップを選ぶ");
            assign(picked);
            var row = rows[index];
            if (string.IsNullOrEmpty(row.name) && picked != null)
            {
                row.name = MultiClipAnimatorTemplate.MakeNameFromClip(picked, rows.Where(other => other != row).Select(other => other.name));
            }

            rowsEdited = true;
        }

        private void MoveRow(EditorWindow owner, int from, int to)
        {
            if (to < 0 || to >= rows.Count)
            {
                return;
            }

            Undo.RecordObject(owner, "クリップの順番を変える");
            var moved = rows[from];
            rows.RemoveAt(from);
            rows.Insert(to, moved);
            rowsEdited = true;
        }

        private static MessageType ToMessageType(MultiClipIssueLevel level)
        {
            switch (level)
            {
                case MultiClipIssueLevel.Error:
                    return MessageType.Error;
                case MultiClipIssueLevel.Warning:
                    return MessageType.Warning;
                default:
                    return MessageType.Info;
            }
        }

        // ---- 保存先 ----

        // 戻り値: 保存先に使えない理由（使えるなら null）
        private string DrawControllerPath(EditorWindow owner)
        {
            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.LabelField("Controller の保存先", string.IsNullOrEmpty(controllerPath) ? "（Animator を選ぶと決まります）" : controllerPath);
                if (GUILayout.Button("変える…", GUILayout.Width(64f)))
                {
                    // ファイルの窓は OnGUI の外で開く（開いている間に Layout が崩れないように）
                    EditorApplication.delayCall += () => PickControllerPath(owner);
                }
            }

            var error = CheckControllerPath(controllerPath);
            if (error != null && !string.IsNullOrEmpty(controllerPath))
            {
                EditorGUILayout.HelpBox(error, MessageType.Error);
            }

            return error;
        }

        private void PickControllerPath(EditorWindow owner)
        {
            var folder = string.IsNullOrEmpty(controllerPath) ? "Assets" : Path.GetDirectoryName(controllerPath)?.Replace('\\', '/');
            var defaultName = string.IsNullOrEmpty(controllerPath)
                ? ToSafeFileName(animator != null ? animator.gameObject.name : "Model") + ControllerFileSuffix
                : Path.GetFileNameWithoutExtension(controllerPath);
            var picked = EditorUtility.SaveFilePanelInProject("Controller の保存先", defaultName, "controller",
                "複数のクリップの Controller を保存する場所を選んでください（入稿フォルダ CatsUdon_Submission と CatsUdonTools の中は選べません）。", folder);
            if (string.IsNullOrEmpty(picked))
            {
                return;
            }

            // ファイルの窓を開いている間にウィンドウが閉じられていたら何もしない
            if (owner == null)
            {
                return;
            }

            Undo.RecordObject(owner, "Controller の保存先を変える");
            controllerPath = picked;
            owner.Repaint();
        }

        private string GetDefaultControllerPath(MultiClipReadResult read)
        {
            var currentPath = animator.runtimeAnimatorController != null ? AssetDatabase.GetAssetPath(animator.runtimeAnimatorController) : "";
            if (read.Kind == MultiClipControllerKind.MultiClip && CheckControllerPath(currentPath) == null)
            {
                return currentPath;
            }

            var folder = FindFolderNearAnimator(currentPath);
            return AssetDatabase.GenerateUniqueAssetPath($"{folder}/{ToSafeFileName(animator.gameObject.name)}{ControllerFileSuffix}{ControllerExtension}");
        }

        // 「Animator の近く」: 今の Controller のフォルダ → Animator のプレハブのフォルダ → シーンのフォルダ → Assets の順で、使える所
        private string FindFolderNearAnimator(string currentControllerPath)
        {
            var candidates = new[]
            {
                currentControllerPath,
                PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(animator.gameObject),
                animator.gameObject.scene.path
            };

            foreach (var candidate in candidates)
            {
                if (string.IsNullOrEmpty(candidate))
                {
                    continue;
                }

                var folder = Path.GetDirectoryName(candidate)?.Replace('\\', '/');
                if (!string.IsNullOrEmpty(folder) && AssetDatabase.IsValidFolder(folder) && CheckFolder(folder) == null)
                {
                    return folder;
                }
            }

            return "Assets";
        }

        private static string CheckControllerPath(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return "Controller の保存先を選んでください。";
            }

            if (!path.EndsWith(ControllerExtension, StringComparison.OrdinalIgnoreCase))
            {
                return "保存先のファイル名は .controller で終わるようにしてください。";
            }

            var folder = Path.GetDirectoryName(path)?.Replace('\\', '/') ?? "";
            var folderError = CheckFolder(folder);
            if (folderError != null)
            {
                return folderError;
            }

            return AssetDatabase.IsValidFolder(folder) ? null : $"保存先のフォルダ「{folder}」がありません。";
        }

        private static string CheckFolder(string folder)
        {
            if (folder != "Assets" && !folder.StartsWith("Assets/", StringComparison.Ordinal))
            {
                return "保存先は Assets の中にしてください（Packages の中には保存できません）。";
            }

            if (IsUnder(folder, CatsUdonSubmissionPackager.SubmissionFolderPath.TrimEnd('/')))
            {
                return "入稿フォルダ（CatsUdon_Submission）の中は、書き出すたびに作り直されるので保存先にできません。";
            }

            if (IsUnder(folder, ToolsFolder))
            {
                return "入稿ツールのフォルダ（CatsUdonTools）の中は、ツールを入れ直すと消えるので保存先にできません。";
            }

            return null;
        }

        private static bool IsUnder(string folder, string parent)
        {
            return string.Equals(folder, parent, StringComparison.OrdinalIgnoreCase)
                || folder.StartsWith(parent + "/", StringComparison.OrdinalIgnoreCase);
        }

        private static string ToSafeFileName(string fileName)
        {
            foreach (var invalidCharacter in Path.GetInvalidFileNameChars())
            {
                fileName = fileName.Replace(invalidCharacter, '_');
            }

            return fileName;
        }

        // ---- 作る ----

        private void DrawCreateButton(EditorWindow owner, List<MultiClipIssue> issues, bool animatorBlocked, string pathError)
        {
            var hasError = issues.Any(issue => issue.Level == MultiClipIssueLevel.Error);
            var blocked = animator == null || animatorBlocked || hasError || pathError != null || EditorApplication.isPlayingOrWillChangePlaymode;

            if (rowsEdited && rows.Count > 0)
            {
                EditorGUILayout.HelpBox("表を変えました。［Controller を作る］を押すと Controller に反映されます。", MessageType.Info);
            }

            using (new EditorGUI.DisabledScope(blocked))
            {
                if (GUILayout.Button(new GUIContent("Controller を作る", "表から複数のクリップの Controller を作り、Animator に付けます"), GUILayout.Height(24f)))
                {
                    // 上書きの確かめの窓は OnGUI の外で開く
                    EditorApplication.delayCall += () => CreateController(owner);
                }
            }

            if (!string.IsNullOrEmpty(statusMessage))
            {
                EditorGUILayout.HelpBox(statusMessage, statusType);
            }
        }

        private void CreateController(EditorWindow owner)
        {
            if (animator == null || MultiClipAnimatorTemplate.Validate(rows).Any(issue => issue.Level == MultiClipIssueLevel.Error))
            {
                return;
            }

            var pathError = CheckControllerPath(controllerPath);
            if (pathError != null)
            {
                SetStatus(owner, pathError, MessageType.Error);
                return;
            }

            var existing = AssetDatabase.LoadMainAssetAtPath(controllerPath);
            if (existing != null && !(existing is AnimatorController))
            {
                SetStatus(owner, $"「{controllerPath}」は Animator Controller ではないので上書きできません。別の保存先を選んでください。", MessageType.Error);
                return;
            }

            if (existing != null && !EditorUtility.DisplayDialog("Controller を作り直す",
                    $"「{controllerPath}」を表の内容で作り直します。\n手で変えた設定（ステートの Speed・遷移の長さなど）は消えます。", "作り直す", "やめる"))
            {
                return;
            }

            try
            {
                var controller = MultiClipAnimatorTemplate.Build(controllerPath, rows.Select(row => row.Clone()).ToList());

                Undo.RecordObject(animator, "複数のクリップの Controller を付ける");
                animator.runtimeAnimatorController = controller;
                if (PrefabUtility.IsPartOfPrefabInstance(animator))
                {
                    PrefabUtility.RecordPrefabInstancePropertyModifications(animator);
                }

                EditorSceneManager.MarkSceneDirty(animator.gameObject.scene);
                shownController = controller;
                rowsEdited = false;
                EditorGUIUtility.PingObject(controller);
                SetStatus(owner, $"「{controllerPath}」を作り、{animator.name} の Animator に付けました（クリップ {rows.Count} 個。1 番目は「{rows[0].name}」）。", MessageType.Info);
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
                SetStatus(owner, "Controller を作れませんでした: " + ex.Message, MessageType.Error);
            }
        }

        private void SetStatus(EditorWindow owner, string message, MessageType type)
        {
            statusMessage = message;
            statusType = type;
            if (owner != null)
            {
                owner.Repaint();
            }
        }
    }
}
