using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

namespace ServiceSim.EditorTools
{
    /// <summary>
    /// Assets/ServiceSim/ の中の .asset・prefab・シーンを作る。
    /// すでにあるものは作り直さないが、マテリアルのシェーダーだけは毎回確かめて、
    /// いまの描画方式に合わせ直す（合っていないとピンクになるため）。
    /// Assets/ServiceSim/ の外のファイルは一切作らない・変えない。
    /// 新しいシーンはビルド設定に足さない。
    /// </summary>
    public static class ServiceSimSetup
    {
        public const string Root = "Assets/ServiceSim";
        public const string ScenePath = Root + "/Scenes/ServiceSim_Family.unity";
        public const string FamilyLayoutPath = Root + "/Store/StoreLayout_Family.asset";
        public const string GridLayoutPath = Root + "/Store/StoreLayout_Grid.asset";
        public const string RobotSpecPath = Root + "/Robot/RobotSpec.asset";
        public const string PeopleSpecPath = Root + "/People/PeopleSpec.asset";
        public const string ExperimentPlanPath = Root + "/Experiment/ExperimentPlan.asset";
        public const string ServiceSpecPath = Root + "/Service/ServiceSpec.asset";

        /// <summary>サイトの試算の値（v5）。serving-robot-params.json を読む .asset</summary>
        public const string SimParamsPath = Root + "/Experiment/SimParams.asset";

        /// <summary>座っている客の形（見た目だけ）の prefab</summary>
        public const string SeatedBodyPrefabPath = Root + "/People/SeatedGuestBody.prefab";

        /// <summary>客の黄色（立っている客・座っている客で同じ色を使う）</summary>
        static readonly Color GuestColor = new Color(0.98f, 0.80f, 0.13f);

        /// <summary>スタッフの青・待合の客の紫（見た目を作り直すときにも使う）</summary>
        static readonly Color StaffColor = new Color(0.09f, 0.38f, 0.90f);
        static readonly Color WaitingColor = new Color(0.55f, 0.30f, 0.75f);

        const string MatDir = Root + "/Store/Materials";
        const string StorePrefabDir = Root + "/Store/Prefabs";
        const string OldDir = Root + "/Store/old~";

        enum WalkerKind { Staff, Guest, Waiting }

        static Shader _shader;
        static int _materialsFixed;

        [MenuItem("ServiceSim/Check Render Pipeline", false, 1)]
        public static void CheckRenderPipeline()
        {
            string how;
            Shader shader = ServiceSimRender.FindOpaqueShader(out how);
            string body = ServiceSimRender.Describe() + "使うシェーダー: " + how;
            if (shader == null) Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません\n" + body);
            else Debug.Log("[ServiceSim] 描画方式のしらべ\n" + body);
        }

        [MenuItem("ServiceSim/Use Store Layout/Family (ファミレス型)", false, 30)]
        public static void UseFamilyLayout() { SwitchLayout(FamilyLayoutPath); }

        [MenuItem("ServiceSim/Use Store Layout/Grid (格子型・比べる用)", false, 31)]
        public static void UseGridLayout() { SwitchLayout(GridLayoutPath); }

        static void SwitchLayout(string path)
        {
            StoreLayout layout = AssetDatabase.LoadAssetAtPath<StoreLayout>(path);
            if (layout == null)
            {
                Debug.LogError("[ServiceSim] " + path + " が読めません。先に ServiceSim/Setup を実行してください");
                return;
            }
            StoreBuilder builder = UnityEngine.Object.FindObjectOfType<StoreBuilder>();
            NavMeshBaker baker = UnityEngine.Object.FindObjectOfType<NavMeshBaker>();
            if (builder == null)
            {
                Debug.LogError("[ServiceSim] シーンに StoreBuilder がありません。ServiceSim_Family シーンを開いてください");
                return;
            }
            builder.layout = layout;
            if (baker != null) baker.layout = layout;
            EditorUtility.SetDirty(builder);
            if (baker != null) EditorUtility.SetDirty(baker);
            EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
            Debug.Log("[ServiceSim] 店の形を切り替えました: " + layout.name + "（" + layout.StoreKindName + "）" +
                      "\n  このあと ServiceSim/Build Store を実行してください");
        }

        [MenuItem("ServiceSim/Setup (アセット・prefab・シーンを作る)", false, 10)]
        public static void Setup()
        {
            // まず描画方式を確かめる。合うシェーダーが無ければ何も作らずに止める
            string how;
            _shader = ServiceSimRender.FindOpaqueShader(out how);
            if (_shader == null)
            {
                Debug.LogError("[ServiceSim] この描画方式に合うシェーダーが見つかりません。" +
                               "マテリアルを作らずに止めました。\n" +
                               ServiceSimRender.Describe() + "探した結果: " + how);
                return;
            }
            _materialsFixed = 0;

            if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
            {
                Debug.Log("[ServiceSim] Setup を取りやめました");
                return;
            }

            EnsureFolders();

            // 前の版の StoreLayout_Family.asset（格子型）は種類が変わったので、
            // Unity が見ないフォルダ（old~）へ寄せておく。消さない
            string moved = MoveStaleFamilyLayoutAside();

            Scene scene;
            if (File.Exists(ScenePath))
            {
                scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
            }
            else
            {
                scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
                EditorSceneManager.SaveScene(scene, ScenePath);
                scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
            }

            // 設定ファイル（数字の置き場所）
            BandStoreLayout family = LoadOrCreate<BandStoreLayout>(FamilyLayoutPath);
            LoadOrCreate<GridStoreLayout>(GridLayoutPath);
            RobotSpec robotSpec = LoadOrCreate<RobotSpec>(RobotSpecPath);
            PeopleSpec peopleSpec = LoadOrCreate<PeopleSpec>(PeopleSpecPath);
            ExperimentPlan plan = LoadOrCreate<ExperimentPlan>(ExperimentPlanPath);
            ServiceSpec serviceSpec = LoadOrCreate<ServiceSpec>(ServiceSpecPath);

            // 見た目。真上から見て、床・通路・卓・椅子・ベンチ・人・ロボットが見分けられる色にする
            Material matFloor = Mat("Mat_Floor", new Color(0.86f, 0.84f, 0.79f));        // 床：明るいクリーム
            Material matAisle = Mat("Mat_Aisle", new Color(0.52f, 0.58f, 0.67f));        // 通路：青灰色
            Material matWall = Mat("Mat_Wall", new Color(0.40f, 0.43f, 0.48f));          // 壁・厨房・事務所
            Material matBench = Mat("Mat_Bench", new Color(0.55f, 0.29f, 0.26f));        // ベンチ・仕切り：暗い赤茶
            Material matTable = Mat("Mat_Table", new Color(0.86f, 0.62f, 0.30f));        // 卓：明るい木の色
            Material matChair = Mat("Mat_Chair", new Color(0.13f, 0.37f, 0.42f));        // 椅子：濃い青緑
            Material matCounter = Mat("Mat_Counter", new Color(0.20f, 0.22f, 0.26f));    // カウンター類：ほぼ黒
            Material matRobot = Mat("Mat_Robot", new Color(0.96f, 0.96f, 0.97f));        // ロボット：白
            Material matRobotFront = Mat("Mat_RobotFront", new Color(1.00f, 0.33f, 0.04f)); // ロボットの前：橙
            Material matStaff = Mat("Mat_Staff", StaffColor);                           // スタッフ：青
            Material matGuest = Mat("Mat_Guest", GuestColor);                           // 歩く客：黄
            Material matWaiting = Mat("Mat_WaitingGuest", WaitingColor);                // 待合の客：紫
            // ベンチの見た目（当たりなし）。色と素材はここ1か所で変えられる
            Material matBenchSeat = Mat("Mat_BenchSeat", new Color(0.62f, 0.33f, 0.29f));  // 座面：赤茶（明るめ）
            Material matBenchBack = Mat("Mat_BenchBack", new Color(0.48f, 0.25f, 0.22f));  // 背もたれ：赤茶（暗め）
            // 床の塗り分け（見た目だけ）。家具の色が透けて見える程度にして、光と影も乗せる
            Material matRobotFloor = TransparentMat("Mat_RobotFloor", new Color(0.45f, 0.78f, 0.95f, 0.35f));
            Material matPeopleFloor = TransparentMat("Mat_PeopleFloor", new Color(1.00f, 0.90f, 0.35f, 0.45f));

            // 部品（prefab）。無ければ作る
            EnsureBoxPrefab(StorePrefabDir + "/Floor.prefab", "Floor", matFloor, true);
            EnsureBoxPrefab(StorePrefabDir + "/Wall.prefab", "Wall", matWall, true);
            EnsureBoxPrefab(StorePrefabDir + "/Bench.prefab", "Bench", matBench, true);
            EnsureBoxPrefab(StorePrefabDir + "/Table_Booth4.prefab", "Table_Booth4", matTable, true);
            EnsureBoxPrefab(StorePrefabDir + "/KitchenPass.prefab", "KitchenPass", matCounter, true);
            // 通路の色板。当たりは付けない（NavMesh に焼かれないようにする）
            EnsureBoxPrefab(StorePrefabDir + "/AisleMark.prefab", "AisleMark", matAisle, false);
            // ベンチの見た目（座面・背もたれ）。当たりは付けない
            // （NavMesh・前のセンサー・左右の空きの測りに効かないようにする）
            EnsureBoxPrefab(StorePrefabDir + "/BenchSeat.prefab", "BenchSeat", matBenchSeat, false);
            EnsureBoxPrefab(StorePrefabDir + "/BenchBack.prefab", "BenchBack", matBenchBack, false);
            // ベンチの当たりの箱。大きさ・位置・高さはベンチのまま、**見た目を持たない**
            EnsureColliderOnlyBoxPrefab(StorePrefabDir + "/BenchBody.prefab", "BenchBody");
            // 色を変えたときに追いつくよう、すでにある prefab のマテリアルも合わせ直す
            SetPrefabMaterial(StorePrefabDir + "/BenchSeat.prefab", matBenchSeat);
            SetPrefabMaterial(StorePrefabDir + "/BenchBack.prefab", matBenchBack);
            EnsureChairPrefab(StorePrefabDir + "/Chair.prefab", matChair);
            EnsureRobotPrefab(Root + "/Robot/ServingRobot.prefab", robotSpec, matRobot, matRobotFront);
            EnsureWalkerPrefab(Root + "/People/Staff.prefab", "Staff", peopleSpec, matStaff, WalkerKind.Staff);
            EnsureWalkerPrefab(Root + "/People/Guest.prefab", "Guest", peopleSpec, matGuest, WalkerKind.Guest);
            EnsureWalkerPrefab(Root + "/People/WaitingGuest.prefab", "WaitingGuest", peopleSpec, matWaiting, WalkerKind.Waiting);
            EnsureSeatedGuestPrefab(Root + "/People/SeatedGuest.prefab", peopleSpec);
            EnsureSeatedBodyPrefab(SeatedBodyPrefabPath, peopleSpec, matGuest);

            // すでにある人の prefab の見た目を、胴＋頭にそろえる（当たり判定などは触らない）
            EnsurePersonLook(peopleSpec);

            // 種類の印（Tag）。あとで映像から見分ける学習に使う。いまは付けるだけ
            int tagsAdded = EnsureTags();
            SetPrefabTag(Root + "/Robot/ServingRobot.prefab", SimTags.Robot);
            SetPrefabTag(Root + "/People/Staff.prefab", SimTags.Staff);
            SetPrefabTag(Root + "/People/Guest.prefab", SimTags.Guest);
            SetPrefabTag(Root + "/People/WaitingGuest.prefab", SimTags.Guest);
            SetPrefabTag(SeatedBodyPrefabPath, SimTags.SeatedGuest);
            SetPrefabTag(StorePrefabDir + "/Chair.prefab", SimTags.Chair);
            SetPrefabTag(StorePrefabDir + "/BenchSeat.prefab", SimTags.Bench);
            SetPrefabTag(StorePrefabDir + "/BenchBack.prefab", SimTags.Bench);
            SetPrefabTag(StorePrefabDir + "/BenchBody.prefab", SimTags.Bench);

            // すでに作ってある prefab も直す。
            //  ・NavMeshAgent は切る（置く前に有効だと「NavMesh に近くないので作れません」の警告が出る）
            //  ・通路の色板の当たりは外す（NavMesh に焼かれないようにする）
            int agentsDisabled = 0;
            agentsDisabled += FixPrefab(Root + "/Robot/ServingRobot.prefab", true, false) ? 1 : 0;
            agentsDisabled += FixPrefab(Root + "/People/Staff.prefab", true, false) ? 1 : 0;
            agentsDisabled += FixPrefab(Root + "/People/Guest.prefab", true, false) ? 1 : 0;
            agentsDisabled += FixPrefab(Root + "/People/WaitingGuest.prefab", true, false) ? 1 : 0;
            int collidersRemoved = FixPrefab(StorePrefabDir + "/AisleMark.prefab", false, true) ? 1 : 0;
            collidersRemoved += FixPrefab(StorePrefabDir + "/BenchSeat.prefab", false, true) ? 1 : 0;
            collidersRemoved += FixPrefab(Root + "/People/SeatedGuestBody.prefab", false, true) ? 1 : 0;

            // 人の種類（止まった原因を分けるのに使う）を、すでにある prefab にも入れ直す
            int kindsFixed = 0;
            kindsFixed += FixPersonKind(Root + "/People/Staff.prefab", PersonKind.Staff) ? 1 : 0;
            kindsFixed += FixPersonKind(Root + "/People/Guest.prefab", PersonKind.Guest) ? 1 : 0;
            kindsFixed += FixPersonKind(Root + "/People/WaitingGuest.prefab", PersonKind.Waiting) ? 1 : 0;
            kindsFixed += FixPersonKind(Root + "/People/SeatedGuest.prefab", PersonKind.Seated) ? 1 : 0;

            // 直したあとに読み直す（保存し直すと、前に取った参照が使えなくなるため）
            AssetDatabase.Refresh();
            GameObject floorPrefab = Load(StorePrefabDir + "/Floor.prefab");
            GameObject wallPrefab = Load(StorePrefabDir + "/Wall.prefab");
            GameObject benchPrefab = Load(StorePrefabDir + "/Bench.prefab");
            GameObject tablePrefab = Load(StorePrefabDir + "/Table_Booth4.prefab");
            GameObject passPrefab = Load(StorePrefabDir + "/KitchenPass.prefab");
            GameObject aisleMarkPrefab = Load(StorePrefabDir + "/AisleMark.prefab");
            GameObject benchSeatPrefab = Load(StorePrefabDir + "/BenchSeat.prefab");
            GameObject chairPrefab = Load(StorePrefabDir + "/Chair.prefab");
            GameObject robotPrefab = Load(Root + "/Robot/ServingRobot.prefab");
            GameObject staffPrefab = Load(Root + "/People/Staff.prefab");
            GameObject guestPrefab = Load(Root + "/People/Guest.prefab");
            GameObject waitingPrefab = Load(Root + "/People/WaitingGuest.prefab");
            GameObject seatedPrefab = Load(Root + "/People/SeatedGuest.prefab");
            GameObject seatedBodyPrefab = Load(SeatedBodyPrefabPath);

            if (floorPrefab == null || wallPrefab == null || benchPrefab == null || tablePrefab == null ||
                passPrefab == null || aisleMarkPrefab == null || benchSeatPrefab == null ||
                chairPrefab == null || robotPrefab == null ||
                staffPrefab == null || guestPrefab == null || waitingPrefab == null || seatedPrefab == null)
            {
                Debug.LogError("[ServiceSim] prefab を用意できませんでした。止めます");
                return;
            }

            // シーンの組み立て（置くだけ。中身は持たない）
            GameObject root = FindOrCreateRoot(scene, "ServiceSim");

            GameObject storeGo = FindOrCreateChild(root.transform, "Store");
            StoreBuilder builder = Ensure<StoreBuilder>(storeGo);
            if (builder.layout == null) builder.layout = family;
            builder.floorPrefab = floorPrefab;
            builder.wallPrefab = wallPrefab;
            builder.benchPrefab = benchPrefab;
            builder.benchSeatPrefab = benchSeatPrefab;
            builder.benchBackPrefab = Load(StorePrefabDir + "/BenchBack.prefab");
            builder.benchBodyPrefab = Load(StorePrefabDir + "/BenchBody.prefab");
            builder.tablePrefab = tablePrefab;
            builder.chairPrefab = chairPrefab;
            builder.kitchenPassPrefab = passPrefab;
            builder.aisleMarkPrefab = aisleMarkPrefab;
            builder.robotFloorMaterial = matRobotFloor;
            builder.peopleFloorMaterial = matPeopleFloor;

            NavMeshBaker baker = Ensure<NavMeshBaker>(storeGo);
            baker.layout = builder.layout;
            baker.bakeRoot = storeGo.transform;
            baker.store = builder;
            baker.peopleRadius = peopleSpec.personRadius;   // 人用の NavMesh を焼く半径
            baker.registerBackArea = peopleSpec.registerBackArea;   // 店員側の区画（客は入れない）

            GameObject peopleGo = FindOrCreateChild(root.transform, "People");
            PeopleSpawner spawner = Ensure<PeopleSpawner>(peopleGo);
            spawner.spec = peopleSpec;
            spawner.store = builder;
            spawner.staffPrefab = staffPrefab;
            spawner.seatedGuestPrefab = seatedPrefab;
            spawner.seatedBodyPrefab = seatedBodyPrefab;
            spawner.service = serviceSpec;

            GameObject robotsGo = FindOrCreateChild(root.transform, "Robots");
            robotsGo.transform.position = Vector3.zero;

            // 店の回し方（卓の空き・料理の待ち行列・仕事板）と、1組ごとの流れ
            GameObject svcGo = FindOrCreateChild(root.transform, "Service");
            ServiceOps opsC = Ensure<ServiceOps>(svcGo);
            opsC.spec = serviceSpec;
            opsC.robotSpec = robotSpec;
            opsC.store = builder;

            GroupManager groupsC = Ensure<GroupManager>(svcGo);
            groupsC.spec = serviceSpec;
            groupsC.people = peopleSpec;
            groupsC.ops = opsC;
            groupsC.store = builder;
            groupsC.guestPrefab = guestPrefab;
            groupsC.waitingGuestPrefab = waitingPrefab;
            groupsC.bodyRoot = svcGo.transform;

            JamWatcher jamsC = Ensure<JamWatcher>(svcGo);
            jamsC.spec = peopleSpec;
            jamsC.ops = opsC;
            jamsC.store = builder;

            spawner.ops = opsC;

            GameObject expGo = FindOrCreateChild(root.transform, "Experiment");
            ExperimentRunner runner = Ensure<ExperimentRunner>(expGo);
            runner.plan = plan;
            runner.service = serviceSpec;
            // サイトの試算の値（v5 3章）。無くてもこれまでの回し方はできる
            runner.simParams = AssetDatabase.LoadAssetAtPath<SimParams>(SimParamsPath);
            runner.ops = opsC;
            runner.groups = groupsC;
            runner.jams = jamsC;
            runner.store = builder;
            runner.baker = baker;
            runner.people = spawner;
            runner.robotSpec = robotSpec;
            runner.robotPrefab = robotPrefab;
            runner.robotsRoot = robotsGo.transform;
            runner.runOnPlay = false;      // メニューから回すときだけ立てる
            EditorUtility.SetDirty(runner);

            EnsureLighting(root.transform);
            EnsureCameras(root.transform);

            EditorUtility.SetDirty(builder);
            EditorUtility.SetDirty(baker);
            EditorUtility.SetDirty(spawner);
            EditorUtility.SetDirty(opsC);
            EditorUtility.SetDirty(groupsC);
            EditorUtility.SetDirty(jamsC);
            EditorSceneManager.MarkSceneDirty(scene);
            EditorSceneManager.SaveScene(scene, ScenePath);

            // .asset に書かれていない項目を、いまの初期値で書きそろえる。
            // すでに書かれている値は、読み込んだときにそのまま入っているので上書きされない。
            string added = FillMissingAssetFields(new ScriptableObject[]
            {
                family, robotSpec, peopleSpec, plan, serviceSpec,
                AssetDatabase.LoadAssetAtPath<StoreLayout>(GridLayoutPath)
            });

            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            Debug.Log("[ServiceSim] Setup おわり。" +
                      "\n  .asset に書き足した項目: " + (added.Length > 0 ? added : "なし") +
                      "\n  描画方式: " + (CurrentPipeline() == null ? "Built-in" : CurrentPipeline().GetType().Name) +
                      "\n  使うシェーダー: " + _shader.name + "（" + how + "）" +
                      "\n  シェーダーを直したマテリアル: " + _materialsFixed + " 個" +
                      "\n  NavMeshAgent を切った prefab: " + agentsDisabled + " 個" +
                      "\n  当たりを外した通路板: " + collidersRemoved + " 個" +
                      "\n  人の種類を直した prefab: " + kindsFixed + " 個" +
                      (moved.Length > 0 ? "\n  前の版の店の設定を寄せました: " + moved : "") +
                      "\n  いまの店の形: " + builder.layout.name + "（" + builder.layout.StoreKindName + "）" +
                      "\n  店の設定: " + FamilyLayoutPath + " / " + GridLayoutPath +
                      "\n  シーン: " + ScenePath + "（run_name=" + plan.runName + "）" +
                      "\n  ※ このシーンはビルド設定に足していません" +
                      "\n  種類の印（Tag）: " + SimTags.All.Length + " 種（新しく作った " + tagsAdded + " 種）");
        }

        // ---------------- .asset の項目を書きそろえる ----------------

        /// <summary>
        /// .asset に書かれていない項目を、いまの初期値で書き出す。
        /// Unity は .asset を読むとき、書かれていない項目に C# の初期値を入れる。
        /// そのまま SetDirty して保存すると、読み込んだ値はそのまま・足りない項目だけが増える。
        ///
        /// これをしないと、設計書の「数字はすべて .asset に置く」が守られず、
        /// 実際の値が .cs の初期値になってしまう。
        /// 何を書き足したかを返す（報告に出す）。
        /// </summary>
        static string FillMissingAssetFields(ScriptableObject[] assets)
        {
            List<string> added = new List<string>();

            for (int i = 0; i < assets.Length; i++)
            {
                ScriptableObject a = assets[i];
                if (a == null) continue;

                string path = AssetDatabase.GetAssetPath(a);
                if (string.IsNullOrEmpty(path)) continue;

                HashSet<string> before = TopLevelKeys(path);
                EditorUtility.SetDirty(a);
                AssetDatabase.SaveAssets();

                HashSet<string> after = TopLevelKeys(path);
                foreach (string k in after)
                    if (!before.Contains(k)) added.Add(a.name + "." + k);
            }

            if (added.Count == 0) return "";
            added.Sort();
            return string.Join(", ", added.ToArray());
        }

        /// <summary>.asset（YAML）の、いちばん外側の項目の名前を集める</summary>
        static HashSet<string> TopLevelKeys(string path)
        {
            HashSet<string> keys = new HashSet<string>();
            if (!File.Exists(path)) return keys;

            string[] lines = File.ReadAllLines(path);
            for (int i = 0; i < lines.Length; i++)
            {
                string line = lines[i];
                // MonoBehaviour の中身は半角スペース2つ下げ。さらに下げた行は中の要素なので見ない
                if (!line.StartsWith("  ") || line.StartsWith("   ")) continue;
                int colon = line.IndexOf(':');
                if (colon <= 2) continue;
                string name = line.Substring(2, colon - 2).Trim();
                if (name.Length == 0 || name.StartsWith("-")) continue;
                keys.Add(name);
            }
            return keys;
        }

        static UnityEngine.Rendering.RenderPipelineAsset CurrentPipeline()
        {
            return UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
        }

        // ---------------- 前の版の .asset を寄せる ----------------

        /// <summary>
        /// StoreLayout_Family.asset が前の版（格子型）のままなら、Unity が見ないフォルダへ寄せる。
        /// 消さない。寄せたら、そのパスを返す。
        /// </summary>
        static string MoveStaleFamilyLayoutAside()
        {
            if (!File.Exists(FamilyLayoutPath)) return "";
            if (AssetDatabase.LoadAssetAtPath<BandStoreLayout>(FamilyLayoutPath) != null) return "";

            try
            {
                Directory.CreateDirectory(OldDir);
                string dst = OldDir + "/StoreLayout_Family_v0_grid.asset";
                int n = 1;
                while (File.Exists(dst))
                {
                    dst = OldDir + "/StoreLayout_Family_v0_grid_" + n.ToString() + ".asset";
                    n++;
                }
                File.Move(FamilyLayoutPath, dst);
                if (File.Exists(FamilyLayoutPath + ".meta")) File.Move(FamilyLayoutPath + ".meta", dst + ".meta");
                AssetDatabase.Refresh();
                return dst;
            }
            catch (Exception e)
            {
                Debug.LogError("[ServiceSim] 前の版の " + FamilyLayoutPath + " を寄せられませんでした。" +
                               "手で消すか動かしてから、もう一度 Setup を実行してください\n" + e.Message);
                return "";
            }
        }

        // ---------------- フォルダ ----------------

        static void EnsureFolders()
        {
            EnsureFolder("Assets", "ServiceSim");
            EnsureFolder(Root, "Scenes");
            EnsureFolder(Root, "Store");
            EnsureFolder(Root + "/Store", "Prefabs");
            EnsureFolder(Root + "/Store", "Materials");
            EnsureFolder(Root, "Robot");
            EnsureFolder(Root, "People");
            EnsureFolder(Root, "Experiment");
            EnsureFolder(Root, "Service");
            EnsureFolder(Root, "Recording");
        }

        static void EnsureFolder(string parent, string name)
        {
            string path = parent + "/" + name;
            if (!AssetDatabase.IsValidFolder(path)) AssetDatabase.CreateFolder(parent, name);
        }

        // ---------------- 設定ファイル ----------------

        static T LoadOrCreate<T>(string path) where T : ScriptableObject
        {
            T asset = AssetDatabase.LoadAssetAtPath<T>(path);
            if (asset != null) return asset;
            asset = ScriptableObject.CreateInstance<T>();
            AssetDatabase.CreateAsset(asset, path);
            return asset;
        }

        // ---------------- 見た目 ----------------

        /// <summary>
        /// マテリアルを用意する。新しく作るときは色も付ける。
        /// すでにあるときはシェーダーだけ確かめて、合っていなければ直し、
        /// そのときだけ色も付け直す（手で変えた色を毎回消さないため）。
        /// </summary>
        static Material Mat(string name, Color color)
        {
            string path = MatDir + "/" + name + ".mat";
            Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

            if (mat == null)
            {
                mat = new Material(_shader);
                ServiceSimRender.ApplyLook(mat, color);
                AssetDatabase.CreateAsset(mat, path);
                return mat;
            }

            bool wrong = mat.shader == null
                         || mat.shader != _shader
                         || mat.shader.name == ServiceSimRender.ErrorShaderName
                         || !mat.shader.isSupported;
            if (wrong)
            {
                mat.shader = _shader;
                ServiceSimRender.ApplyLook(mat, color);
                EditorUtility.SetDirty(mat);
                _materialsFixed++;
            }
            return mat;
        }

        // ---------------- 部品 ----------------

        /// <summary>
        /// 透けて光が乗るマテリアルを作る（床の塗り分け用）。
        /// すでにあればシェーダーと色だけ合わせ直す（止まっているときと再生中で同じ見た目にする）。
        /// </summary>
        static Material TransparentMat(string name, Color color)
        {
            string how;
            Shader sh = ServiceSimRender.FindTransparentLitShader(out how);
            string path = MatDir + "/" + name + ".mat";
            Material mat = AssetDatabase.LoadAssetAtPath<Material>(path);

            if (mat == null)
            {
                mat = new Material(sh != null ? sh : _shader);
                AssetDatabase.CreateAsset(mat, path);
            }
            if (sh != null && mat.shader != sh) { mat.shader = sh; _materialsFixed++; }

            mat.color = color;
            if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color);

            // Standard しか無いときは、透過の設定も入れる
            if (mat.shader != null && mat.shader.name == "Standard")
            {
                mat.SetFloat("_Mode", 3f);
                mat.SetOverrideTag("RenderType", "Transparent");
                mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
                mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                mat.SetInt("_ZWrite", 0);
                mat.DisableKeyword("_ALPHATEST_ON");
                mat.EnableKeyword("_ALPHABLEND_ON");
                mat.renderQueue = 3000;
            }
            EditorUtility.SetDirty(mat);
            return mat;
        }

        static void EnsureBoxPrefab(string path, string name, Material mat, bool withCollider)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            go.name = name;
            SetMaterial(go, mat);
            if (!withCollider)
            {
                Collider col = go.GetComponent<Collider>();
                if (col != null) UnityEngine.Object.DestroyImmediate(col);
            }
            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        /// <summary>
        /// 当たりだけの箱（見た目を持たない）。ベンチの当たりの箱に使う。
        /// 大きさ・位置・高さは今までのベンチと同じなので、NavMesh も前のセンサーも変わらない。
        /// </summary>
        static void EnsureColliderOnlyBoxPrefab(string path, string name)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            go.name = name;
            // 見た目だけ外す（当たりは残す）
            MeshRenderer mr = go.GetComponent<MeshRenderer>();
            if (mr != null) UnityEngine.Object.DestroyImmediate(mr);
            MeshFilter mf = go.GetComponent<MeshFilter>();
            if (mf != null) UnityEngine.Object.DestroyImmediate(mf);

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        /// <summary>prefab のマテリアルを合わせ直す（色を1か所で変えられるように）</summary>
        static void SetPrefabMaterial(string path, Material mat)
        {
            if (mat == null || !File.Exists(path)) return;

            GameObject contents = PrefabUtility.LoadPrefabContents(path);
            if (contents == null) return;
            try
            {
                bool changed = false;
                MeshRenderer[] rs = contents.GetComponentsInChildren<MeshRenderer>(true);
                for (int i = 0; i < rs.Length; i++)
                {
                    if (rs[i].sharedMaterial == mat) continue;
                    rs[i].sharedMaterial = mat;
                    changed = true;
                }
                if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
            }
            finally { PrefabUtility.UnloadPrefabContents(contents); }
        }

        /// <summary>prefab に種類の印（Tag）を付ける</summary>
        static void SetPrefabTag(string path, string tag)
        {
            if (string.IsNullOrEmpty(tag) || !File.Exists(path)) return;

            GameObject contents = PrefabUtility.LoadPrefabContents(path);
            if (contents == null) return;
            try
            {
                if (contents.tag == tag) return;
                contents.tag = tag;
                PrefabUtility.SaveAsPrefabAsset(contents, path);
            }
            catch (UnityException)
            {
                Debug.LogWarning("[ServiceSim] Tag \"" + tag + "\" を付けられませんでした: " + path);
            }
            finally { PrefabUtility.UnloadPrefabContents(contents); }
        }

        /// <summary>
        /// 種類の印（Tag）を ProjectSettings/TagManager.asset に足す。
        /// Tag は物理にも NavMesh にも効かないので、測る数字は変わらない。
        /// **Assets/ServiceSim の外に出るのはここだけ**（LABEL_RULES.md に書いてある）。
        /// </summary>
        static int EnsureTags()
        {
            int added = 0;
            for (int i = 0; i < SimTags.All.Length; i++)
            {
                string tag = SimTags.All[i];
                if (System.Array.IndexOf(UnityEditorInternal.InternalEditorUtility.tags, tag) >= 0) continue;
                UnityEditorInternal.InternalEditorUtility.AddTag(tag);
                added++;
            }
            return added;
        }

        static void EnsureChairPrefab(string path, Material mat)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            go.name = "Chair";
            SetMaterial(go, mat);

            NavMeshObstacle obstacle = go.AddComponent<NavMeshObstacle>();
            obstacle.shape = NavMeshObstacleShape.Box;
            obstacle.center = Vector3.zero;
            obstacle.size = Vector3.one;
            obstacle.carving = true;
            obstacle.carveOnlyStationary = false;
            obstacle.enabled = false;        // 引かれているあいだだけ有効にする

            go.AddComponent<Chair>();

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        static void EnsureRobotPrefab(string path, RobotSpec spec, Material body, Material front)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            float r = spec.Radius;
            float h = spec.height;

            GameObject go = new GameObject("ServingRobot");

            GameObject bodyGo = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
            bodyGo.name = "Body";
            bodyGo.transform.SetParent(go.transform, false);
            bodyGo.transform.localPosition = new Vector3(0f, h * 0.5f, 0f);
            bodyGo.transform.localScale = new Vector3(spec.diameter, h * 0.5f, spec.diameter);
            UnityEngine.Object.DestroyImmediate(bodyGo.GetComponent<Collider>());
            SetMaterial(bodyGo, body);

            GameObject frontGo = GameObject.CreatePrimitive(PrimitiveType.Cube);
            frontGo.name = "FrontMark";
            frontGo.transform.SetParent(go.transform, false);
            frontGo.transform.localPosition = new Vector3(0f, h * 0.86f, r * 0.9f);
            frontGo.transform.localScale = new Vector3(spec.diameter * 0.7f, 0.08f, 0.08f);
            UnityEngine.Object.DestroyImmediate(frontGo.GetComponent<Collider>());
            SetMaterial(frontGo, front);

            CapsuleCollider col = go.AddComponent<CapsuleCollider>();
            col.radius = r;
            col.height = h;
            col.center = new Vector3(0f, h * 0.5f, 0f);

            NavMeshAgent agent = go.AddComponent<NavMeshAgent>();
            agent.radius = r;
            agent.height = h;
            agent.speed = spec.maxSpeed;
            agent.acceleration = spec.acceleration;
            agent.angularSpeed = spec.turnSpeedDegPerSec;
            agent.obstacleAvoidanceType = ObstacleAvoidanceType.NoObstacleAvoidance;
            agent.avoidancePriority = spec.avoidancePriority;
            agent.autoRepath = false;
            agent.updatePosition = false;
            agent.updateRotation = false;
            agent.enabled = false;   // 置いたあとに有効にする（警告を出さないため）

            ServingRobotController controller = go.AddComponent<ServingRobotController>();
            FrontSensor sensor = go.AddComponent<FrontSensor>();
            sensor.spec = spec;
            sensor.owner = controller;
            controller.spec = spec;
            controller.sensor = sensor;

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        /// <summary>
        /// 人の見た目（胴＋頭）を作る。立っている人も座っている客も同じ作りにする。
        /// 当たり判定は付けない（親が持っているものには触れない）。
        ///   bottomY … 足もと（立っている人は 0、座っている客は座面の高さ）
        ///   topY    … 頭の先（立っている人は背の高さ、座っている客は 1.35 m）
        /// すでにある "Body" と "Head" は作り直す。ほかの子どもと親の部品には触れない。
        /// </summary>
        static void BuildPersonLook(GameObject root, float radius, float bottomY, float topY, Material mat)
        {
            if (root == null) return;

            for (int i = root.transform.childCount - 1; i >= 0; i--)
            {
                Transform c = root.transform.GetChild(i);
                if (c.name == "Body" || c.name == "Head")
                    UnityEngine.Object.DestroyImmediate(c.gameObject);
            }

            float span = Mathf.Max(0.2f, topY - bottomY);
            float headR = Mathf.Min(0.11f, span * 0.25f);
            float headCenter = topY - headR;
            float bodyH = Mathf.Max(0.1f, headCenter - bottomY);   // 頭は胴に少し食い込ませる

            GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
            body.name = "Body";
            body.transform.SetParent(root.transform, false);
            body.transform.localPosition = new Vector3(0f, bottomY + bodyH * 0.5f, 0f);
            body.transform.localScale = new Vector3(radius * 2f, bodyH * 0.5f, radius * 2f);
            UnityEngine.Object.DestroyImmediate(body.GetComponent<Collider>());
            SetMaterial(body, mat);

            GameObject head = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            head.name = "Head";
            head.transform.SetParent(root.transform, false);
            head.transform.localPosition = new Vector3(0f, headCenter, 0f);
            head.transform.localScale = new Vector3(headR * 2f, headR * 2f, headR * 2f);
            UnityEngine.Object.DestroyImmediate(head.GetComponent<Collider>());
            SetMaterial(head, mat);
        }

        /// <summary>
        /// すでにある人の prefab の **見た目だけ** を胴＋頭に取り替える。
        /// 当たり判定・NavMeshAgent・PersonBody・よける処理・Tag には触れない。
        /// もう胴＋頭になっていれば何もしない。
        /// </summary>
        static bool FixWalkerLook(string path, PeopleSpec spec, Material mat)
        {
            if (!File.Exists(path) || spec == null || mat == null) return false;

            GameObject contents = PrefabUtility.LoadPrefabContents(path);
            if (contents == null) return false;
            try
            {
                float r = spec.personRadius;
                float h = spec.personHeight;

                Transform head = contents.transform.Find("Head");
                if (head != null &&
                    Mathf.Abs(head.localPosition.y + head.localScale.y * 0.5f - h) < 0.001f)
                    return false;                       // もう同じ形

                BuildPersonLook(contents, r, 0f, h, mat);
                PrefabUtility.SaveAsPrefabAsset(contents, path);
                return true;
            }
            finally { PrefabUtility.UnloadPrefabContents(contents); }
        }

        static void EnsureWalkerPrefab(string path, string name, PeopleSpec spec, Material mat, WalkerKind kind)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            float r = spec.personRadius;
            float h = spec.personHeight;

            GameObject go = new GameObject(name);

            // 見た目は胴＋頭（座っている客と同じ作り）。当たり判定はこの下で別に付ける
            BuildPersonLook(go, r, 0f, h, mat);

            CapsuleCollider col = go.AddComponent<CapsuleCollider>();
            col.radius = r;
            col.height = h;
            col.center = new Vector3(0f, h * 0.5f, 0f);

            NavMeshAgent agent = go.AddComponent<NavMeshAgent>();
            agent.radius = r;
            agent.height = h;
            agent.speed = kind == WalkerKind.Staff ? spec.staffSpeed : spec.guestSpeed;
            agent.acceleration = spec.acceleration;
            agent.angularSpeed = spec.angularSpeedDegPerSec;
            agent.stoppingDistance = spec.stoppingDistance;
            agent.obstacleAvoidanceType = spec.avoidanceQuality;
            agent.avoidancePriority = spec.avoidancePriority;
            agent.enabled = false;   // 置いたあとに有効にする（警告を出さないため）

            PersonBody personBody = go.AddComponent<PersonBody>();
            personBody.radius = r;
            personBody.walksInAisle = kind != WalkerKind.Waiting;   // 待合に立つ客は通路を歩かない
            personBody.kind = kind == WalkerKind.Staff ? PersonKind.Staff
                            : kind == WalkerKind.Waiting ? PersonKind.Waiting
                            : PersonKind.Guest;

            if (kind == WalkerKind.Staff) { StaffAgent a = go.AddComponent<StaffAgent>(); a.spec = spec; }
            else if (kind == WalkerKind.Guest) { GuestAgent a = go.AddComponent<GuestAgent>(); a.spec = spec; }
            else { WaitingGuest a = go.AddComponent<WaitingGuest>(); a.spec = spec; }

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        /// <summary>
        /// 座っている客の形（見た目だけ）。立っている客と同じ黄色で、背を低くする。
        /// 当たり判定・PersonBody・NavMeshAgent は付けない
        /// （NavMesh にも、前のセンサーにも、人の数の記録にも効かないようにする）。
        /// </summary>
        /// <summary>
        /// 立っている人（スタッフ・歩く客・待合の客）の見た目を、胴＋頭にそろえる。
        /// Setup を押していなくても Build Store だけで揃うようにするための出口。
        /// 取り替えるのは見た目（Renderer）だけで、
        /// 当たり判定・NavMeshAgent・PersonBody・よける処理・Tag には触れない。
        /// 作り直した prefab の数を返す。
        /// </summary>
        public static int EnsurePersonLook(PeopleSpec spec)
        {
            if (spec == null) return 0;
            if (_shader == null)
            {
                string how;
                _shader = ServiceSimRender.FindOpaqueShader(out how);
                if (_shader == null) return 0;
            }

            int fixedCount = 0;
            fixedCount += FixWalkerLook(Root + "/People/Staff.prefab",
                                        spec, Mat("Mat_Staff", StaffColor)) ? 1 : 0;
            fixedCount += FixWalkerLook(Root + "/People/Guest.prefab",
                                        spec, Mat("Mat_Guest", GuestColor)) ? 1 : 0;
            fixedCount += FixWalkerLook(Root + "/People/WaitingGuest.prefab",
                                        spec, Mat("Mat_WaitingGuest", WaitingColor)) ? 1 : 0;
            if (fixedCount > 0)
                Debug.Log("[ServiceSim] 立っている人の見た目を胴＋頭にそろえました（" + fixedCount +
                          " 体ぶん・背の高さ " + spec.personHeight.ToString("0.##") + " m）。" +
                          "当たり判定・NavMeshAgent・PersonBody は触っていません");
            return fixedCount;
        }

        /// <summary>
        /// ベンチの見た目（座面・背もたれ）と、当たりだけの箱を用意して StoreBuilder に入れる。
        /// Setup を押していなくても Build Store だけで揃うようにするための出口（v5）。
        /// 当たりの箱の大きさ・位置・高さは今までのベンチと同じなので、
        /// NavMesh も前のセンサーも、通れる床も卓までの道のりも変わらない。
        /// </summary>
        public static bool EnsureBenchLook(StoreBuilder builder)
        {
            if (builder == null) return false;
            if (_shader == null)
            {
                string how;
                _shader = ServiceSimRender.FindOpaqueShader(out how);
                if (_shader == null)
                {
                    Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません。" +
                                   "ベンチの見た目は作りませんでした（探した結果: " + how + "）");
                    return false;
                }
            }

            EnsureTags();

            Material seatMat = Mat("Mat_BenchSeat", new Color(0.62f, 0.33f, 0.29f));
            Material backMat = Mat("Mat_BenchBack", new Color(0.48f, 0.25f, 0.22f));
            EnsureBoxPrefab(StorePrefabDir + "/BenchSeat.prefab", "BenchSeat", seatMat, false);
            EnsureBoxPrefab(StorePrefabDir + "/BenchBack.prefab", "BenchBack", backMat, false);
            EnsureColliderOnlyBoxPrefab(StorePrefabDir + "/BenchBody.prefab", "BenchBody");
            SetPrefabMaterial(StorePrefabDir + "/BenchSeat.prefab", seatMat);
            SetPrefabMaterial(StorePrefabDir + "/BenchBack.prefab", backMat);
            SetPrefabTag(StorePrefabDir + "/BenchSeat.prefab", SimTags.Bench);
            SetPrefabTag(StorePrefabDir + "/BenchBack.prefab", SimTags.Bench);
            SetPrefabTag(StorePrefabDir + "/BenchBody.prefab", SimTags.Bench);
            SetPrefabTag(StorePrefabDir + "/Chair.prefab", SimTags.Chair);
            SetPrefabTag(Root + "/Robot/ServingRobot.prefab", SimTags.Robot);
            SetPrefabTag(Root + "/People/Staff.prefab", SimTags.Staff);
            SetPrefabTag(Root + "/People/Guest.prefab", SimTags.Guest);
            SetPrefabTag(Root + "/People/WaitingGuest.prefab", SimTags.Guest);
            SetPrefabTag(SeatedBodyPrefabPath, SimTags.SeatedGuest);

            bool changed = false;
            changed |= Assign(ref builder.benchSeatPrefab, StorePrefabDir + "/BenchSeat.prefab");
            changed |= Assign(ref builder.benchBackPrefab, StorePrefabDir + "/BenchBack.prefab");
            changed |= Assign(ref builder.benchBodyPrefab, StorePrefabDir + "/BenchBody.prefab");
            if (changed) EditorUtility.SetDirty(builder);
            return true;
        }

        static bool Assign(ref GameObject field, string path)
        {
            GameObject want = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            if (want == null || field == want) return false;
            field = want;
            return true;
        }

        /// <summary>
        /// 座っている客の形を用意して返す（Build Store からも呼ぶ）。
        /// 10/21 の分では、この prefab を作る前から参照だけしていたので、席に何も出ていなかった。
        /// </summary>
        public static GameObject EnsureSeatedBodyPrefab(PeopleSpec spec)
        {
            if (spec == null) return null;
            if (_shader == null)
            {
                string how;
                _shader = ServiceSimRender.FindOpaqueShader(out how);
                if (_shader == null)
                {
                    Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません。" +
                                   "座っている客の形は作りませんでした（探した結果: " + how + "）");
                    return null;
                }
            }
            EnsureSeatedBodyPrefab(SeatedBodyPrefabPath, spec, Mat("Mat_Guest", GuestColor));
            return AssetDatabase.LoadAssetAtPath<GameObject>(SeatedBodyPrefabPath);
        }

        static void EnsureSeatedBodyPrefab(string path, PeopleSpec spec, Material mat)
        {
            float r = spec.personRadius;
            float h = spec.seatedBodyHeight;

            // すでにあっても、高さを変えたら作り直す（PeopleSpec.seatedBodyHeight が正）
            GameObject had = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            if (had != null)
            {
                // 胴と頭の2つになっていて、頭の先が h と合っていれば、そのまま使う
                Transform head0 = had.transform.Find("Head");
                bool sameShape = had.transform.childCount == 2 && head0 != null &&
                                 Mathf.Abs(head0.localPosition.y + head0.localScale.y * 0.5f - h) < 0.001f;
                if (sameShape) return;
                AssetDatabase.DeleteAsset(path);
            }

            // 座面の高さから立ち上がる胴と、その上の頭。h は床から頭の先までの高さ
            float seat = Mathf.Clamp(spec.seatedSeatHeight, 0f, h - 0.3f);

            GameObject go = new GameObject("SeatedGuestBody");
            BuildPersonLook(go, r, seat, h, mat);

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        static void EnsureSeatedGuestPrefab(string path, PeopleSpec spec)
        {
            if (AssetDatabase.LoadAssetAtPath<GameObject>(path) != null) return;

            // 座っている客は、椅子を引く仕掛けだけ。見た目は持たない（計測に効かないため）
            GameObject go = new GameObject("SeatedGuest");

            PersonBody body = go.AddComponent<PersonBody>();
            body.radius = spec.personRadius;
            body.walksInAisle = false;
            body.kind = PersonKind.Seated;

            SeatedGuest seated = go.AddComponent<SeatedGuest>();
            seated.spec = spec;

            PrefabUtility.SaveAsPrefabAsset(go, path);
            UnityEngine.Object.DestroyImmediate(go);
        }

        /// <summary>
        /// すでにある prefab を直す（NavMeshAgent を切る／当たりを外す）。直したら true。
        /// 中身を開いて保存し直す公式の手順を使う。
        /// </summary>
        static bool FixPrefab(string path, bool disableAgent, bool removeCollider)
        {
            if (!File.Exists(path)) return false;

            GameObject contents = PrefabUtility.LoadPrefabContents(path);
            if (contents == null) return false;

            bool changed = false;
            try
            {
                if (disableAgent)
                {
                    NavMeshAgent agent = contents.GetComponent<NavMeshAgent>();
                    if (agent != null && agent.enabled) { agent.enabled = false; changed = true; }
                }
                if (removeCollider)
                {
                    Collider[] cols = contents.GetComponentsInChildren<Collider>(true);
                    for (int i = 0; i < cols.Length; i++)
                    {
                        UnityEngine.Object.DestroyImmediate(cols[i]);
                        changed = true;
                    }
                }
                if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
            }
            finally
            {
                PrefabUtility.UnloadPrefabContents(contents);
            }
            return changed;
        }

        /// <summary>すでにある人の prefab に、人の種類を入れ直す。直したら true</summary>
        static bool FixPersonKind(string path, PersonKind kind)
        {
            if (!File.Exists(path)) return false;

            GameObject contents = PrefabUtility.LoadPrefabContents(path);
            if (contents == null) return false;

            bool changed = false;
            try
            {
                PersonBody body = contents.GetComponent<PersonBody>();
                if (body != null && body.kind != kind)
                {
                    body.kind = kind;
                    changed = true;
                }
                if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
            }
            finally
            {
                PrefabUtility.UnloadPrefabContents(contents);
            }
            return changed;
        }

        static GameObject Load(string path)
        {
            return AssetDatabase.LoadAssetAtPath<GameObject>(path);
        }

        static void SetMaterial(GameObject go, Material mat)
        {
            if (mat == null) return;
            Renderer rend = go.GetComponent<Renderer>();
            if (rend != null) rend.sharedMaterial = mat;
        }

        // ---------------- シーンの骨 ----------------

        static GameObject FindOrCreateRoot(Scene scene, string name)
        {
            GameObject[] roots = scene.GetRootGameObjects();
            for (int i = 0; i < roots.Length; i++)
                if (roots[i].name == name) return roots[i];
            GameObject go = new GameObject(name);
            SceneManager.MoveGameObjectToScene(go, scene);
            return go;
        }

        static GameObject FindOrCreateChild(Transform parent, string name)
        {
            Transform t = parent.Find(name);
            if (t != null) return t.gameObject;
            GameObject go = new GameObject(name);
            go.transform.SetParent(parent, false);
            return go;
        }

        static T Ensure<T>(GameObject go) where T : Component
        {
            T c = go.GetComponent<T>();
            if (c == null) c = go.AddComponent<T>();
            return c;
        }

        static void EnsureLighting(Transform root)
        {
            GameObject lightsGo = FindOrCreateChild(root, "Lighting");

            GameObject sunGo = FindOrCreateChild(lightsGo.transform, "Sun");
            Light sun = Ensure<Light>(sunGo);
            sun.type = LightType.Directional;
            sun.intensity = 1.1f;
            sun.shadows = LightShadows.Soft;
            sun.color = new Color(1f, 0.98f, 0.94f);
            sunGo.transform.rotation = Quaternion.Euler(52f, -34f, 0f);

            GameObject fillGo = FindOrCreateChild(lightsGo.transform, "Fill");
            Light fill = Ensure<Light>(fillGo);
            fill.type = LightType.Directional;
            fill.intensity = 0.35f;
            fill.shadows = LightShadows.None;
            fill.color = new Color(0.85f, 0.9f, 1f);
            fillGo.transform.rotation = Quaternion.Euler(28f, 160f, 0f);

            RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
            RenderSettings.ambientLight = new Color(0.46f, 0.47f, 0.50f);
            RenderSettings.fog = false;
        }

        static void EnsureCameras(Transform root)
        {
            GameObject camsGo = FindOrCreateChild(root, "Cameras");

            GameObject mainGo = FindOrCreateChild(camsGo.transform, "Main Camera");
            Camera main = Ensure<Camera>(mainGo);
            main.clearFlags = CameraClearFlags.SolidColor;
            main.backgroundColor = new Color(0.13f, 0.14f, 0.17f);
            mainGo.tag = "MainCamera";

            GameObject topGo = FindOrCreateChild(camsGo.transform, "Cam_Top");
            Camera top = Ensure<Camera>(topGo);
            top.enabled = false;
            top.clearFlags = CameraClearFlags.SolidColor;
            top.backgroundColor = new Color(0.13f, 0.14f, 0.17f);

            GameObject obliqueGo = FindOrCreateChild(camsGo.transform, "Cam_Oblique");
            Camera oblique = Ensure<Camera>(obliqueGo);
            oblique.enabled = false;
            oblique.clearFlags = CameraClearFlags.SolidColor;
            oblique.backgroundColor = new Color(0.13f, 0.14f, 0.17f);

            // SRP が有効なときに必要な付属データを先に付けておく
            EnsureSrpCameraData(mainGo);
            EnsureSrpCameraData(topGo);
            EnsureSrpCameraData(obliqueGo);
        }

        /// <summary>
        /// URP / HDRP のカメラ付属データを、型名で探して付ける。
        /// URP が入っていなくてもコンパイルできるように、直接の参照は持たない。
        /// </summary>
        static void EnsureSrpCameraData(GameObject camGo)
        {
            if (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline == null) return;

            string[] typeNames =
            {
                "UnityEngine.Rendering.Universal.UniversalAdditionalCameraData, Unity.RenderPipelines.Universal.Runtime",
                "UnityEngine.Rendering.HighDefinition.HDAdditionalCameraData, Unity.RenderPipelines.HighDefinition.Runtime"
            };
            for (int i = 0; i < typeNames.Length; i++)
            {
                Type t = Type.GetType(typeNames[i]);
                if (t == null) continue;
                if (camGo.GetComponent(t) == null) camGo.AddComponent(t);
                return;
            }
        }
    }
}
