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

namespace ServiceSim
{
    /// <summary>
    /// StoreLayout が作った部品の一覧（StorePlan）を見て、床・壁・卓・椅子・ベンチ・
    /// カウンター・通路の色板を並べる。
    ///
    /// この script は店だけを扱う。ロボットと人は動かさない。
    /// 格子や帯の組み方は知らない（StorePlan だけを見る）。
    /// </summary>
    public class StoreBuilder : MonoBehaviour
    {
        [Serializable]
        public class TableInfo
        {
            public int tableId;
            public SeatKind kind;
            public Transform root;
            public Vector3 center;
            public Vector3 deliveryPoint;   // ロボットが止まる位置（通路の上）
            public Vector3 aisleEntrance;   // その通路に入る前に待つ位置
            public Bounds aisleZone;        // 譲り合いの区画
            public int aisleIndex = -1;

            /// <summary>何人かけの卓か（2人席・4人席）</summary>
            public int seatCount = 4;

            public List<Chair> aisleChairs = new List<Chair>();

            /// <summary>席ごとの「人が立つ点」（席に入れるかの確かめに使う）</summary>
            public List<PlanSeatSpot> spots = new List<PlanSeatSpot>();

            /// <summary>人が実際に座る位置（座っている客の形を出す場所）</summary>
            public List<Vector3> seatPoints = new List<Vector3>();
        }

        [Serializable]
        public class AisleInfo
        {
            public string name;
            public bool narrow;
            public Bounds zone;
            public Vector3 entrance;
            public float width;
        }

        [Header("設定（数字はすべてこの .asset の中）")]
        public StoreLayout layout;

        [Header("部品")]
        public GameObject floorPrefab;
        public GameObject wallPrefab;
        public GameObject benchPrefab;       // ベンチと仕切り（高さ 1.1 m のもの）
        public GameObject benchSeatPrefab;   // ベンチの座面の見た目（当たりなし）
        public GameObject benchBackPrefab;   // ベンチの背もたれの見た目（当たりなし）
        public GameObject benchBodyPrefab;   // ベンチの当たりの箱（見た目なし。NavMesh とセンサーはこれだけを見る）
        public GameObject tablePrefab;
        public GameObject chairPrefab;
        public GameObject kitchenPassPrefab; // カウンター類
        public GameObject aisleMarkPrefab;   // 通路の色板（当たりなし）

        [Header("床の塗り分け（見た目だけ）")]
        [Tooltip("ロボットの体が入れる床の色（水色）")]
        public Material robotFloorMaterial;

        [Tooltip("人だけが入れる床の色（薄い黄色）")]
        public Material peopleFloorMaterial;

        [Header("組み立てた結果（読むだけ）")]
        [SerializeField] List<TableInfo> _tables = new List<TableInfo>();
        [SerializeField] List<AisleInfo> _aisles = new List<AisleInfo>();
        [SerializeField] float _builtAisleWidth;
        [SerializeField] Vector2 _outerSize;

        readonly Dictionary<PlaceKind, Vector3> _points = new Dictionary<PlaceKind, Vector3>();

        /// <summary>
        /// 同じ種類に点がいくつもある場所（ロボットの待機場所・ドリンクバーの立つ点など）。
        /// _points には最初の1つしか入らないので、別に全部を持つ。
        /// </summary>
        readonly Dictionary<PlaceKind, List<Vector3>> _pointLists =
            new Dictionary<PlaceKind, List<Vector3>>();
        readonly List<Transform> _notWalkableRoots = new List<Transform>();
        StorePlan _plan;

        /// <summary>
        /// 上面を歩ける場所にしてはいけないものの親（壁・ベンチ・仕切り・卓・椅子・カウンター）。
        /// NavMeshBaker がここを Not Walkable にして焼く。
        /// </summary>
        public IList<Transform> NotWalkableRoots { get { return _notWalkableRoots; } }

        public IList<TableInfo> Tables { get { return _tables; } }
        public IList<AisleInfo> Aisles { get { return _aisles; } }
        public float BuiltAisleWidth { get { return _builtAisleWidth; } }
        public Vector2 OuterSize { get { return _outerSize; } }
        public StorePlan Plan { get { return _plan; } }

        /// <summary>ロボットの道順（v5b。サイトが決めた曲がり角の点）。無ければ null</summary>
        public RouteBook Routes { get { return _plan != null ? _plan.routes : null; } }

        /// <summary>場所の点。無ければ原点</summary>
        public Vector3 PointOf(PlaceKind kind)
        {
            Vector3 p;
            if (_points.TryGetValue(kind, out p)) return p;
            return Vector3.zero;
        }

        public bool HasPoint(PlaceKind kind) { return _points.ContainsKey(kind); }

        /// <summary>その種類の場所の点をすべて返す（出てきた順）。無ければ空</summary>
        public IList<Vector3> PointsOf(PlaceKind kind)
        {
            List<Vector3> list;
            if (_pointLists.TryGetValue(kind, out list)) return list;
            return new List<Vector3>();
        }

        // ロボットが使う点
        public Vector3 PassPoint { get { return PointOf(PlaceKind.FoodPass); } }
        public Vector3 ReturnPoint { get { return PointOf(PlaceKind.DishReturn); } }
        public Vector3 PantryPoint { get { return PointOf(PlaceKind.Pantry); } }

        /// <summary>ロボットの待機場所（B2）。受け渡し口の前とは別の点</summary>
        public IList<Vector3> RobotStandbyPoints { get { return PointsOf(PlaceKind.RobotStandby); } }

        // 人が使う点
        public Vector3 RegisterPoint { get { return PointOf(PlaceKind.Register); } }
        public Vector3 ToiletPoint { get { return PointOf(PlaceKind.Toilet); } }
        public Vector3 DrinkBarPoint { get { return PointOf(PlaceKind.DrinkBar); } }
        public Vector3 EntrancePoint { get { return PointOf(PlaceKind.Entrance); } }
        public Vector3 WaitingPoint { get { return PointOf(PlaceKind.Waiting); } }

        /// <summary>レジの前に並ぶ点（会計に立つ人が1人ずつ並ぶ）</summary>
        public IList<Vector3> RegisterQueuePoints { get { return PointsOf(PlaceKind.RegisterQueue); } }

        /// <summary>手の空いたスタッフが待つ点（1人に1つ）</summary>
        public IList<Vector3> StaffStandbyPoints { get { return PointsOf(PlaceKind.StaffStandby); } }

        /// <summary>レジの内側（店員側）の広がり。無ければ空</summary>
        public PlanRect RegisterBackRect
        {
            get { return _plan != null ? _plan.RectOf(PlaceKind.RegisterBack) : new PlanRect(0f, 0f, 0f, 0f); }
        }

        /// <summary>レジの内側（スタッフが会計に立つ点）。無ければレジの前の点</summary>
        public Vector3 RegisterBackPoint
        {
            get { return HasPoint(PlaceKind.RegisterBack) ? PointOf(PlaceKind.RegisterBack) : RegisterPoint; }
        }

        /// <summary>待合の広さ（待つ客を散らすのに使う）</summary>
        public Bounds WaitingArea { get; private set; }

        readonly List<Vector3> _refuges = new List<Vector3>();

        /// <summary>人がロボットをよけて下がれる場所（通路の外）。壁ぞいの卓のすき間など</summary>
        public IList<Vector3> Refuges { get { return _refuges; } }

        /// <summary>床の塗り分けを入れる親（当たりなし。NavMesh には焼かれない）</summary>
        public Transform FloorPaintRoot
        {
            get
            {
                Transform t = transform.Find("FloorPaint");
                if (t == null)
                {
                    GameObject go = new GameObject("FloorPaint");
                    go.transform.SetParent(transform, false);
                    t = go.transform;
                }
                return t;
            }
        }

        /// <summary>
        /// 床を塗り分け直す（見た目だけ）。
        /// 水色＝ロボットの体が入れる床、薄い黄色＝人だけが入れる床。
        /// NavMesh も当たりも変えない。
        /// </summary>
        public FloorPaint.Result PaintFloor(NavMeshBaker baker, float personRadius)
        {
            FloorPaint.Result res = new FloorPaint.Result();
            if (layout == null || baker == null || _plan == null) return res;

            Transform root = FloorPaintRoot;
            if (!layout.showFloorPaint) { FloorPaint.Clear(root); return res; }

            UnityEngine.AI.NavMeshTriangulation robotTri =
                UnityEngine.AI.NavMesh.CalculateTriangulation();
            UnityEngine.AI.NavMeshTriangulation peopleTri = baker.TriangulateFor(personRadius);

            return FloorPaint.Rebuild(root, layout, _plan.NavBounds(layout.navBoundsPadding),
                                      robotTri, peopleTri, personRadius, _plan,
                                      robotFloorMaterial, peopleFloorMaterial);
        }

        /// <summary>
        /// その点がある場所の名前（レジ・待合・ドリンクバーなど）。どこでもなければ空。
        /// 記録で「どこで起きたか」を通路の名前だけでなく場所の名前でも言うために使う。
        /// </summary>
        public string PlaceNameAt(Vector3 p)
        {
            if (_plan == null) return "";
            for (int i = 0; i < _plan.places.Count; i++)
            {
                PlanPlace pl = _plan.places[i];
                if (pl.rect.IsEmpty) continue;
                if (pl.rect.Contains(p)) return PlaceName(pl.kind);
            }
            return "";
        }

        /// <summary>場所の種類の名前（記録に出す）</summary>
        public static string PlaceName(PlaceKind kind)
        {
            switch (kind)
            {
                case PlaceKind.Kitchen: return "厨房";
                case PlaceKind.Pantry: return "パントリー";
                case PlaceKind.FoodPass: return "受け渡し口";
                case PlaceKind.DishReturn: return "返却口";
                case PlaceKind.Entrance: return "入口";
                case PlaceKind.Vestibule: return "風除室";
                case PlaceKind.Waiting: return "待合";
                case PlaceKind.Register: return "レジ";
                case PlaceKind.Toilet: return "トイレ";
                case PlaceKind.DrinkBar: return "ドリンクバー";
                case PlaceKind.Office: return "事務所";
                case PlaceKind.RobotStandby: return "ロボット待機";
                case PlaceKind.DrinkBarStand: return "ドリンクバーの前";
                case PlaceKind.DrinkBarQueue: return "ドリンクバーの列";
                case PlaceKind.RegisterQueue: return "レジの列";
                case PlaceKind.StaffStandby: return "スタッフ待機";
                case PlaceKind.RegisterBack: return "レジの内側";
            }
            return "";
        }

        /// <summary>いまの layout.aisleWidthIndex で店を組み立て直す</summary>
        public void Build()
        {
            if (layout == null)
            {
                Debug.LogError("[ServiceSim] StoreBuilder に StoreLayout が入っていません");
                return;
            }

            Clear();

            _plan = layout.NewPlan();
            if (_plan == null || _plan.seats.Count == 0)
            {
                Debug.LogError("[ServiceSim] StoreLayout が部品の一覧を作れませんでした（席が0）");
                return;
            }

            _builtAisleWidth = _plan.aisleWidth;
            _outerSize = new Vector2(_plan.floor.SizeX, _plan.floor.SizeZ);

            BuildFloor(_plan);
            BuildBoxes(_plan);
            BuildPlaces(_plan);
            BuildSeats(_plan);
            BuildAisles(_plan);
            BuildAisleMarks(_plan);
            _refuges.AddRange(_plan.refuges);
        }

        /// <summary>組み立てたものを消す</summary>
        public void Clear()
        {
            _tables.Clear();
            _aisles.Clear();
            _points.Clear();
            _pointLists.Clear();
            _refuges.Clear();
            _notWalkableRoots.Clear();
            _plan = null;
            WaitingArea = new Bounds();
            for (int i = transform.childCount - 1; i >= 0; i--)
                DestroyChild(transform.GetChild(i).gameObject);
        }

        /// <summary>細い通路の幅を選び直して組み立て直す</summary>
        public void SetAisleWidthIndex(int index)
        {
            if (layout == null) return;
            layout.aisleWidthIndex = index;
            Build();
        }

        // ---------------- 床 ----------------

        void BuildFloor(StorePlan plan)
        {
            Transform g = NewGroup("Floor", true);

            if (plan.floorPieces.Count == 0)
            {
                PlaceFloor(g, plan, plan.floor, "Floor");
                return;
            }
            for (int i = 0; i < plan.floorPieces.Count; i++)
                PlaceFloor(g, plan, plan.floorPieces[i], "Floor" + (i + 1).ToString("00"));
        }

        void PlaceFloor(Transform parent, StorePlan plan, PlanRect r, string name)
        {
            GameObject floor = Box(floorPrefab, parent, name, r, plan.floorThickness);
            if (floor != null)
                floor.transform.position = new Vector3(r.CenterX, -plan.floorThickness * 0.5f, r.CenterZ);
        }

        // ---------------- 箱（壁・仕切り・ベンチ）----------------

        void BuildBoxes(StorePlan plan)
        {
            Transform walls = NewGroup("Walls", false);
            Transform seats = NewGroup("SeatStructure", false);

            // 座る場所の見た目は、当たりを持たない prefab でしか置かない。
            // 当たりのある prefab で代わりに置くと NavMesh が変わってしまうため、
            // prefab が無いときは置かずに知らせる（Setup を実行すれば作られる）。
            int skippedSeatLooks = 0;

            for (int i = 0; i < plan.boxes.Count; i++)
            {
                PlanBox b = plan.boxes[i];

                // 見た目だけの箱は、当たりを持たない prefab でしか置かない。
                // 当たりのある prefab で代えると NavMesh が変わってしまう
                bool lookOnly = b.style == BoxStyle.BenchSeat || b.style == BoxStyle.BenchBack;
                if (lookOnly && PrefabFor(b.style) == null) { skippedSeatLooks++; continue; }

                Transform parent = (b.style == BoxStyle.Bench || b.style == BoxStyle.Divider ||
                                    b.style == BoxStyle.BenchBody || lookOnly) ? seats : walls;
                GameObject go = Box(PrefabFor(b.style), parent, b.name, b.rect, b.height);
                if (go == null) continue;
                if (b.baseY != 0f) go.transform.position += Vector3.up * b.baseY;
                SimTags.Apply(go, TagFor(b.style));
            }

            if (skippedSeatLooks > 0)
                Debug.LogWarning("[ServiceSim] ベンチの見た目（座面・背もたれ）" + skippedSeatLooks +
                                 " 個を置きませんでした（BenchSeat.prefab / BenchBack.prefab がありません）。" +
                                 "ServiceSim/Setup を実行してください。" +
                                 "当たりの箱はそのまま置いているので、測る数字は変わりません");
        }

        /// <summary>
        /// 種類の印（Tag）。あとで映像から見分ける学習に使う。いまは付けるだけ（v5）。
        /// Tag は物理にも NavMesh にも効かないので、測る数字は変わらない
        /// </summary>
        static string TagFor(BoxStyle style)
        {
            switch (style)
            {
                case BoxStyle.Bench:
                case BoxStyle.BenchBody:
                case BoxStyle.BenchSeat:
                case BoxStyle.BenchBack: return SimTags.Bench;
                case BoxStyle.Table: return SimTags.Table;
                case BoxStyle.Chair: return SimTags.Chair;
                default: return null;
            }
        }

        // ---------------- 場所 ----------------

        void BuildPlaces(StorePlan plan)
        {
            Transform g = NewGroup("Places", false);

            for (int i = 0; i < plan.places.Count; i++)
            {
                PlanPlace p = plan.places[i];
                if (p.solid && p.height > 0.0005f)
                    Box(PrefabFor(p.kind), g, p.name, p.rect, p.height);

                NewChild(g, p.name + "_Point").position = p.point;
                if (!_points.ContainsKey(p.kind)) _points.Add(p.kind, p.point);

                List<Vector3> list;
                if (!_pointLists.TryGetValue(p.kind, out list))
                {
                    list = new List<Vector3>();
                    _pointLists.Add(p.kind, list);
                }
                list.Add(p.point);

                if (p.kind == PlaceKind.Waiting) WaitingArea = p.rect.ToBounds(0f, 2f);
            }
        }

        // ---------------- 席 ----------------

        void BuildSeats(StorePlan plan)
        {
            Transform g = NewGroup("Seats", false);

            for (int i = 0; i < plan.seats.Count; i++)
            {
                PlanSeat s = plan.seats[i];
                Transform unit = NewChild(g, "Table" + s.tableId.ToString("00"));

                Box(tablePrefab, unit, "TableTop", s.tableRect, s.tableHeight);

                TableInfo info = new TableInfo();
                info.tableId = s.tableId;
                info.kind = s.kind;
                info.root = unit;
                info.center = s.tableRect.Center;
                info.deliveryPoint = s.deliveryPoint;
                info.aisleIndex = s.aisleIndex;
                info.seatCount = s.seatCount;
                info.spots.AddRange(s.spots);
                info.seatPoints.AddRange(s.seatPoints);

                PlanAisle aisle = plan.AisleAt(s.aisleIndex);
                if (aisle != null)
                {
                    info.aisleZone = aisle.Zone(-1f, 2f);
                    info.aisleEntrance = aisle.entrance;
                }
                else
                {
                    info.aisleEntrance = s.deliveryPoint;
                }

                for (int c = 0; c < s.chairs.Count; c++)
                {
                    PlanChair pc = s.chairs[c];
                    GameObject go = Box(chairPrefab, unit, "Chair", pc.rect, pc.height);
                    if (go == null) continue;
                    Chair chair = go.GetComponent<Chair>();
                    if (chair == null) continue;
                    chair.isAisleSide = pc.pullable;
                    chair.pullDirection = pc.pullDirection;
                    chair.MarkParked();
                    if (pc.pullable) info.aisleChairs.Add(chair);
                }

                _tables.Add(info);
            }
        }

        // ---------------- 通路 ----------------

        void BuildAisles(StorePlan plan)
        {
            for (int i = 0; i < plan.aisles.Count; i++)
            {
                PlanAisle a = plan.aisles[i];
                AisleInfo info = new AisleInfo();
                info.name = a.name;
                info.narrow = a.narrow;
                info.zone = a.Zone(-1f, 2f);
                info.entrance = a.entrance;
                info.width = Mathf.Min(a.rect.SizeX, a.rect.SizeZ);
                _aisles.Add(info);
            }
        }

        /// <summary>
        /// 通路の床に色の板を敷く。当たりは持たないので NavMesh には焼かれず、計測には効かない。
        /// </summary>
        void BuildAisleMarks(StorePlan plan)
        {
            if (!layout.showAisleMarks || aisleMarkPrefab == null) return;

            Transform g = NewGroup("AisleMarks", false);
            for (int i = 0; i < plan.aisles.Count; i++)
            {
                PlanAisle a = plan.aisles[i];
                Box(aisleMarkPrefab, g, "Mark_" + a.name, a.rect, layout.aisleMarkThickness);
            }
        }

        // ---------------- 小さな道具 ----------------

        GameObject PrefabFor(BoxStyle style)
        {
            switch (style)
            {
                case BoxStyle.Floor: return floorPrefab;
                case BoxStyle.Table: return tablePrefab;
                case BoxStyle.Chair: return chairPrefab;
                case BoxStyle.Bench: return benchPrefab != null ? benchPrefab : wallPrefab;
                case BoxStyle.Divider: return benchPrefab != null ? benchPrefab : wallPrefab;
                // 当たりの箱（見た目なし）。prefab が無いときは、これまでどおり見える箱で代える
                // （当たりの大きさは同じなので、NavMesh と測る数字は変わらない）
                case BoxStyle.BenchBody:
                    return benchBodyPrefab != null ? benchBodyPrefab
                         : (benchPrefab != null ? benchPrefab : wallPrefab);
                case BoxStyle.BenchSeat: return benchSeatPrefab;   // 無いときは BuildBoxes が置かない
                case BoxStyle.BenchBack: return benchBackPrefab;   // 同じく
                case BoxStyle.Counter: return kitchenPassPrefab;
                case BoxStyle.AisleMark: return aisleMarkPrefab;
                default: return wallPrefab;
            }
        }

        GameObject PrefabFor(PlaceKind kind)
        {
            switch (kind)
            {
                case PlaceKind.Kitchen:
                case PlaceKind.Office:
                    return wallPrefab;
                default:
                    return kitchenPassPrefab;
            }
        }

        /// <summary>
        /// 組み立てたものを入れる親を作る。
        /// walkable が false の親は、NavMesh で Not Walkable として焼く（上面に乗れないように）。
        /// </summary>
        Transform NewGroup(string name, bool walkable)
        {
            Transform t = NewChild(transform, name);
            if (!walkable) _notWalkableRoots.Add(t);
            return t;
        }

        Transform NewChild(Transform parent, string name)
        {
            GameObject go = new GameObject(name);
            go.transform.SetParent(parent, false);
            return go.transform;
        }

        /// <summary>長方形と高さから箱を1つ置く（下の面が y=0、上の面が y=height）</summary>
        GameObject Box(GameObject prefab, Transform parent, string name, PlanRect rect, float height)
        {
            if (rect.IsEmpty || height <= 0.0005f) return null;

            GameObject go;
            if (prefab != null)
            {
                go = Instantiate(prefab, parent);
            }
            else
            {
                go = GameObject.CreatePrimitive(PrimitiveType.Cube);
                go.transform.SetParent(parent, false);
            }
            go.name = name;
            go.transform.localScale = new Vector3(rect.SizeX, height, rect.SizeZ);
            go.transform.position = new Vector3(rect.CenterX, height * 0.5f, rect.CenterZ);
            return go;
        }

        static void DestroyChild(GameObject go)
        {
            if (Application.isPlaying) Destroy(go);
            else DestroyImmediate(go);
        }
    }
}
