using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

namespace ServiceSim
{
    /// <summary>
    /// 店の形から NavMesh を焼く。com.unity.ai.navigation を入れずに、
    /// 組み込みの UnityEngine.AI の低レベル API だけで焼く（NavMeshSurface の代わり）。
    ///
    /// 焼く対象は bakeRoot の下だけ。人とロボットは別の親の下に置くので、
    /// 障害物として焼き込まれない。
    /// 数字（半径・高さ・細かさ）は StoreLayout の .asset に置く。
    /// </summary>
    public class NavMeshBaker : MonoBehaviour
    {
        [Header("設定")]
        public StoreLayout layout;

        [Tooltip("この下にあるコライダーだけを焼く。ふつうは Store の親")]
        public Transform bakeRoot;

        [Tooltip("焼く範囲を、組み立てた店の広さから取る")]
        public StoreBuilder store;

        [Tooltip("人用の NavMesh を焼く半径（m）。PeopleSpec の人の半径に合わせる。" +
                 "0 以下、または navAgentRadius 以上なら人用は焼かず、人もロボット用の面を歩く")]
        public float peopleRadius = 0.2f;

        [Tooltip("レジの内側（店員側）に付ける NavMesh の区画の番号。PeopleSpec に合わせる。" +
                 "客の NavMeshAgent はこの区画を外すので、店員側に入れない。0 なら区切らない")]
        public int registerBackArea = 3;

        // v5b: 狭い通路の区画（narrowAisleArea）はやめた。
        // NavMesh に区画を書き込むと、同じ面が細かく割れて道がつながらない卓が出たため
        // （⑦で 11 卓）。ロボットの道順はサイトの routes を受け取ってたどる。
        // NavMesh は段階1 と同じ（レジの内側の区画だけ残す）。

        NavMeshData _data;
        NavMeshDataInstance _instance;

        // ---- 人用の NavMesh（半径が人の分だけ小さい）----
        // ロボット用（navAgentRadius）と人用（人の半径）を同時に置き、
        // それぞれの通行者の種類（agentTypeID）で引き分ける。
        NavMeshData _peopleData;
        NavMeshDataInstance _peopleInstance;
        int _peopleTypeId = -1;

        /// <summary>ロボット用の通行者の種類（既定の 0）</summary>
        public int RobotAgentTypeId { get { return 0; } }

        /// <summary>人用の通行者の種類。焼いていなければロボット用と同じ</summary>
        public int PeopleAgentTypeId { get { return _peopleTypeId >= 0 ? _peopleTypeId : 0; } }

        /// <summary>人用の NavMesh を置けているか</summary>
        public bool HasPeopleNavMesh { get { return _peopleInstance.valid; } }

        /// <summary>人用の NavMesh を焼いた半径（m）</summary>
        public float PeopleRadius { get { return peopleRadius; } }

        /// <summary>その種類で経路を引くための絞り込み</summary>
        public NavMeshQueryFilter FilterFor(int agentTypeId)
        {
            NavMeshQueryFilter f = new NavMeshQueryFilter();
            f.agentTypeID = agentTypeId;
            f.areaMask = NavMesh.AllAreas;
            return f;
        }
        readonly List<NavMeshBuildSource> _sources = new List<NavMeshBuildSource>();
        readonly List<NavMeshBuildMarkup> _markups = new List<NavMeshBuildMarkup>();

        public bool HasNavMesh { get { return _instance.valid; } }

        void OnDisable()
        {
            Remove();
        }

        /// <summary>
        /// いまの店の形で焼き直す。人の半径を渡すと、人用の NavMesh も一緒に焼く。
        /// 人の半径が 0 以下、またはロボットの半径以上なら、人用は焼かない（1枚だけ）。
        /// </summary>
        public void Bake(float radius)
        {
            peopleRadius = radius;
            Bake();
        }

        /// <summary>いまの店の形で焼き直す</summary>
        public void Bake()
        {
            if (layout == null)
            {
                Debug.LogError("[ServiceSim] NavMeshBaker に StoreLayout が入っていません");
                return;
            }
            Transform root = bakeRoot != null ? bakeRoot : transform;

            _sources.Clear();
            BuildMarkups();
            NavMeshBuilder.CollectSources(
                root,
                ~0,                                        // すべてのレイヤー（レイヤーを増やさないため）
                NavMeshCollectGeometry.PhysicsColliders,    // コライダーの形で焼く
                0,                                         // 既定のエリア（Walkable）
                _markups,
                _sources);

            AddRegisterBackArea();

            if (_sources.Count == 0)
            {
                Debug.LogError("[ServiceSim] 焼く形が見つかりません。先に Build Store を実行してください");
                return;
            }

            NavMeshBuildSettings settings = BuildSettings();
            Bounds bounds = BakeBounds();

            // v5b: スクリプトの読み込み直し（ドメインリロード）をまたぐと、前に焼いた NavMesh が
            // システムに残ったままで、この baker の _instance では外せない。
            // 2026-10-22 の ⑦ で、前の作り（狭い通路の区画入り）の面が残り、
            // 道がつながらない卓が出た。焼く前に、入っているものを全部外す。
            NavMesh.RemoveAllNavMeshData();
            Remove();
            _data = NavMeshBuilder.BuildNavMeshData(settings, _sources, bounds, Vector3.zero, Quaternion.identity);
            if (_data == null)
            {
                Debug.LogError("[ServiceSim] NavMesh を焼けませんでした");
                return;
            }
            _data.name = "ServiceSim_NavMesh";
            _instance = NavMesh.AddNavMeshData(_data);

            BakePeople(settings, bounds);
        }

        /// <summary>
        /// 人用の NavMesh を焼いて一緒に置く（半径だけが違う）。
        /// 通行者の種類を1つ作り、人の NavMeshAgent にその番号を入れて使い分ける。
        /// ロボットはこれまでどおり navAgentRadius の面を歩く。
        /// </summary>
        void BakePeople(NavMeshBuildSettings robotSettings, Bounds bounds)
        {
            RemovePeople();
            if (peopleRadius <= 0.0005f || peopleRadius >= layout.navAgentRadius - 0.0005f) return;

            if (_peopleTypeId < 0)
            {
                NavMeshBuildSettings made = NavMesh.CreateSettings();
                _peopleTypeId = made.agentTypeID;
            }

            NavMeshBuildSettings s = robotSettings;
            s.agentTypeID = _peopleTypeId;
            s.agentRadius = peopleRadius;

            _peopleData = NavMeshBuilder.BuildNavMeshData(s, _sources, bounds,
                                                          Vector3.zero, Quaternion.identity);
            if (_peopleData == null)
            {
                Debug.LogWarning("[ServiceSim] 人用の NavMesh を焼けませんでした。人もロボット用の面を歩きます");
                return;
            }
            _peopleData.name = "ServiceSim_NavMesh_People";
            _peopleInstance = NavMesh.AddNavMeshData(_peopleData);
            PeopleNav.SetPeopleAgentType(_peopleTypeId);
        }

        void RemovePeople()
        {
            PeopleNav.SetPeopleAgentType(0);
            if (_peopleInstance.valid) NavMesh.RemoveNavMeshData(_peopleInstance);
            _peopleInstance = new NavMeshDataInstance();
            _peopleData = null;
        }

        /// <summary>焼いた NavMesh を外す</summary>
        public void Remove()
        {
            if (_instance.valid) NavMesh.RemoveNavMeshData(_instance);
            _instance = new NavMeshDataInstance();
            _data = null;
            RemovePeople();
        }

        /// <summary>
        /// 壁・ベンチ・仕切り・卓・椅子・カウンターを Not Walkable にする印を作る。
        /// 焼く形としては残るので通れないままだが、上面は歩ける場所にならない。
        /// </summary>
        void BuildMarkups()
        {
            _markups.Clear();
            if (store == null) return;

            int notWalkable = NavMesh.GetAreaFromName("Not Walkable");
            if (notWalkable < 0) notWalkable = 1;

            IList<Transform> roots = store.NotWalkableRoots;
            for (int i = 0; i < roots.Count; i++)
            {
                if (roots[i] == null) continue;
                NavMeshBuildMarkup m = new NavMeshBuildMarkup();
                m.root = roots[i];
                m.overrideArea = true;
                m.area = notWalkable;
                _markups.Add(m);
            }
        }

        /// <summary>焼く範囲。組み立てた店の広さに余白を足す</summary>
        public Bounds BakeBounds()
        {
            if (store != null && store.Plan != null)
                return store.Plan.NavBounds(layout.navBoundsPadding);

            // 店がまだ組み立てられていないときは、この下のコライダーの広がりを使う
            Transform root = bakeRoot != null ? bakeRoot : transform;
            Collider[] cols = root.GetComponentsInChildren<Collider>(false);
            if (cols.Length == 0) return new Bounds(Vector3.zero, new Vector3(20f, 4f, 20f));
            Bounds b = cols[0].bounds;
            for (int i = 1; i < cols.Length; i++) b.Encapsulate(cols[i].bounds);
            b.Expand(layout.navBoundsPadding * 2f);
            return b;
        }

        /// <summary>
        /// レジの内側（店員側）を別の区画にする。
        /// 形は足さず、その箱の中の区画の番号だけを変える（ModifierBox）。
        /// 客の NavMeshAgent がこの区画を外して歩くので、客は店員側に入れない。
        /// </summary>
        void AddRegisterBackArea()
        {
            if (registerBackArea <= 2) return;        // 0・1・2 は決まった区画なので使わない
            if (store == null) return;

            PlanRect r = store.RegisterBackRect;
            if (r.IsEmpty) return;

            NavMeshBuildSource src = new NavMeshBuildSource();
            src.shape = NavMeshBuildSourceShape.ModifierBox;
            src.transform = Matrix4x4.Translate(new Vector3(r.CenterX, 0.5f, r.CenterZ));
            src.size = new Vector3(r.SizeX, 2f, r.SizeZ);
            src.area = registerBackArea;
            _sources.Add(src);
        }

        /// <summary>
        /// 通行半径を変えて焼き直し、その歩ける面の三角形を返す（見た目の塗り分けに使う）。
        /// いま入れてある NavMesh を外してから焼き、読み終わったら元に戻すので、
        /// 計算に使う NavMesh（navAgentRadius で焼いたもの）は変わらない。
        /// </summary>
        public NavMeshTriangulation TriangulateFor(float agentRadius)
        {
            NavMeshTriangulation empty = new NavMeshTriangulation();
            if (layout == null) return empty;

            Transform root = bakeRoot != null ? bakeRoot : transform;
            _sources.Clear();
            BuildMarkups();
            NavMeshBuilder.CollectSources(root, ~0, NavMeshCollectGeometry.PhysicsColliders,
                                          0, _markups, _sources);
            AddRegisterBackArea();
            if (_sources.Count == 0) return empty;

            NavMeshBuildSettings s = BuildSettings();
            s.agentRadius = agentRadius;

            // いまの NavMesh を一時的に全部外す（CalculateTriangulation は入れてある全部を返すため）。
            // v5b: 読み込み直しをまたいで残った面もここで外す（RemoveAllNavMeshData）。
            // そうしないと、前の作りで焼いた面が混ざった三角形を読んでしまう。
            bool had = _instance.valid;
            NavMeshData keep = _data;
            bool hadPeople = _peopleInstance.valid;
            NavMeshData keepPeople = _peopleData;
            NavMesh.RemoveAllNavMeshData();
            _instance = new NavMeshDataInstance();
            _peopleInstance = new NavMeshDataInstance();

            NavMeshTriangulation tri = empty;
            NavMeshData tmp = NavMeshBuilder.BuildNavMeshData(
                s, _sources, BakeBounds(), Vector3.zero, Quaternion.identity);
            NavMeshDataInstance tmpInst = new NavMeshDataInstance();
            if (tmp != null)
            {
                tmpInst = NavMesh.AddNavMeshData(tmp);
                tri = NavMesh.CalculateTriangulation();
            }
            if (tmpInst.valid) NavMesh.RemoveNavMeshData(tmpInst);

            // 元に戻す
            _data = keep;
            if (had && keep != null) _instance = NavMesh.AddNavMeshData(keep);
            _peopleData = keepPeople;
            if (hadPeople && keepPeople != null) _peopleInstance = NavMesh.AddNavMeshData(keepPeople);
            return tri;
        }

        NavMeshBuildSettings BuildSettings()
        {
            NavMeshBuildSettings s = NavMesh.GetSettingsByID(0);
            s.agentRadius = layout.navAgentRadius;
            s.agentHeight = layout.navAgentHeight;
            s.agentClimb = layout.navAgentClimb;
            s.agentSlope = layout.navAgentSlope;
            s.overrideVoxelSize = true;
            s.voxelSize = layout.navVoxelSize;
            s.minRegionArea = layout.navMinRegionArea;
            return s;
        }

        /// <summary>
        /// 受け渡し口から1点までの NavMesh 上の道のり（m）。
        /// つながっていなければ -1 を返す。
        /// </summary>
        public static float PathLength(Vector3 from, Vector3 to, float sampleRadius, out string status)
        {
            NavMeshQueryFilter f = new NavMeshQueryFilter();
            f.agentTypeID = 0;
            f.areaMask = NavMesh.AllAreas;
            return PathLength(from, to, sampleRadius, f, out status);
        }

        /// <summary>通行者の種類を指定して道のりを測る（人用・ロボット用を分けて確かめる）</summary>
        public static float PathLength(Vector3 from, Vector3 to, float sampleRadius,
                                       NavMeshQueryFilter filter, out string status)
        {
            NavMeshHit hitFrom, hitTo;
            if (!NavMesh.SamplePosition(from, out hitFrom, sampleRadius, filter))
            {
                status = "出発点が NavMesh の上にない";
                return -1f;
            }
            if (!NavMesh.SamplePosition(to, out hitTo, sampleRadius, filter))
            {
                status = "行き先が NavMesh の上にない";
                return -1f;
            }

            NavMeshPath path = new NavMeshPath();
            if (!NavMesh.CalculatePath(hitFrom.position, hitTo.position, filter, path))
            {
                status = "経路が見つからない";
                return -1f;
            }
            if (path.status != NavMeshPathStatus.PathComplete)
            {
                status = path.status == NavMeshPathStatus.PathPartial ? "経路が途中まで" : "経路が無効";
                return -1f;
            }

            float len = 0f;
            Vector3[] c = path.corners;
            for (int i = 1; i < c.Length; i++) len += Vector3.Distance(c[i - 1], c[i]);
            status = "ok";
            return len;
        }

        void OnDrawGizmosSelected()
        {
            if (layout == null) return;
            Bounds b = BakeBounds();
            Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.5f);
            Gizmos.DrawWireCube(b.center, b.size);
        }
    }
}
