using System.Collections.Generic;
using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// 床の塗り分け（見た目だけ。2026-10-23 に決め直した）。
    ///
    /// 色は **サイトの JSON から決める**（Unity の NavMesh の形からは決めない）。
    /// 使っていない NavMesh の形を塗ると、ロボットが道順で入る細い通路が塗られず、
    /// 「入れない」ように見えてしまうため。
    ///
    ///   水色　　：ロボットがふだん走る床
    ///             サイトの道順（routes）に沿って、ロボットの体（navAgentRadius）が通る範囲
    ///   濃い水色：細い通路（狭い道で入る）＝ 店の形の aisles の区画
    ///
    /// use=narrow の道も「床」として塗る。狭い道が狭いのは通路の中だけなので、
    /// 通路の外（広い通路を通っている部分）まで濃い水色にすると、
    /// ⑦ではほとんどの床が「細い通路」になってしまう（水色 8.6 m2 / 濃い水色 79.1 m2）。
    /// 区画で分けると ⑦は 水色 64.1 / 濃い水色 23.5 m2 になる。
    ///
    /// どちらも体が通る範囲いっぱいまで塗る（道の中心線だけにしない）。
    /// 置いてあるもの（壁・卓・椅子・ベンチ・カウンター・通れない部屋）の下は塗らない。
    /// 2つが重なるところは、濃い水色（細い通路）を出す。
    ///
    /// 塗りは当たりを持たない板（1色につき1枚のメッシュ）なので、
    /// NavMesh には焼かれず、計算に使う NavMesh そのものは変わらない。
    ///
    /// この script は見た目だけを扱う。店の形も人もロボットも動かさない。
    /// </summary>
    public class FloorPaint : MonoBehaviour
    {
        /// <summary>塗り分けの結果（報告に出す）</summary>
        public struct Result
        {
            public float robotAreaM2;    // ロボットがふだん走る床の広さ（水色）
            public float peopleAreaM2;   // 細い通路の広さ（濃い水色）
            public int robotQuads;       // 塗りに使った四角の数
            public int peopleQuads;
            public float cellSize;
            public bool ok;
            public string note;

            /// <summary>読んだ道順の本数（ふだんの道／狭い道）</summary>
            public int normalRoutes, narrowRoutes;

            /// <summary>細い通路の区画の数</summary>
            public int aisleZones;
        }

        /// <summary>
        /// 塗り分けを作り直す（サイトの道順と細い通路の区画から）。
        /// robotRadius はロボットの体の半径（navAgentRadius）。
        /// </summary>
        public static Result Rebuild(Transform parent, StoreLayout layout, Bounds bounds,
                                     RouteBook routes, StorePlan plan, float robotRadius,
                                     Material normalMat, Material narrowMat)
        {
            Result res = new Result();
            res.ok = false;

            Clear(parent);
            if (layout == null) { res.note = "StoreLayout がありません"; return res; }
            if (plan == null) { res.note = "店をまだ建てていません"; return res; }
            if (routes == null) { res.note = "サイトの道順（routes）がありません"; return res; }

            float cell = Mathf.Max(0.02f, layout.floorPaintCellSize);
            res.cellSize = cell;

            float x0 = bounds.min.x, z0 = bounds.min.z;
            int nx = Mathf.CeilToInt(bounds.size.x / cell) + 1;
            int nz = Mathf.CeilToInt(bounds.size.z / cell) + 1;
            if (nx <= 0 || nz <= 0 || (long)nx * nz > 4000000)
            {
                res.note = "塗る範囲が大きすぎます（floorPaintCellSize を大きくしてください）";
                return res;
            }

            bool[] normal = new bool[nx * nz];
            bool[] narrow = new bool[nx * nz];

            // 道順に沿って、体が通る範囲を塗る。
            // use=narrow の道も「床」として塗る。狭い道は通路の中だけなので、
            // 通路の外（広い通路を通っている部分）まで濃い水色にはしない
            List<RouteBook.Route> all = routes.AllRoutes();
            for (int i = 0; i < all.Count; i++)
            {
                RouteBook.Route r = all[i];
                if (r == null || r.points.Count < 2) continue;
                if (r.IsNarrow) res.narrowRoutes++; else res.normalRoutes++;
                for (int k = 1; k < r.points.Count; k++)
                    StrokeSegment(normal, r.points[k - 1], r.points[k], robotRadius, x0, z0, cell, nx, nz);
            }

            // 細い通路の区画（狭い道で入る所）。区画いっぱいが、体が通る範囲
            for (int i = 0; i < plan.aisles.Count; i++)
            {
                if (!plan.aisles[i].narrow) continue;
                res.aisleZones++;
                Fill(narrow, plan.aisles[i].rect, x0, z0, cell, nx, nz);
            }

            // 置いてあるものの下は塗らない
            bool[] blocked = RasterizeSolids(plan, x0, z0, cell, nx, nz);
            if (blocked != null)
            {
                for (int i = 0; i < normal.Length; i++)
                    if (blocked[i]) { normal[i] = false; narrow[i] = false; }
            }

            // 重なるところは、濃い水色（細い通路）を出す
            for (int i = 0; i < normal.Length; i++) if (narrow[i]) normal[i] = false;

            float y = Mathf.Max(0.001f, layout.floorPaintHeight);
            res.robotQuads = Build(parent, "FloorPaint_Robot", normal, nx, nz, x0, z0, cell, y, normalMat);
            res.peopleQuads = Build(parent, "FloorPaint_Narrow", narrow, nx, nz, x0, z0, cell, y, narrowMat);

            res.robotAreaM2 = Count(normal) * cell * cell;
            res.peopleAreaM2 = Count(narrow) * cell * cell;
            res.ok = true;
            res.note = "ok";
            return res;
        }

        /// <summary>
        /// 道順の1区間に沿って、体（半径 radius）が通る範囲を塗る。
        /// 区間の囲み箱の中だけを見て、点から線分までの距離で決める
        /// </summary>
        static void StrokeSegment(bool[] g, Vector3 a, Vector3 b, float radius,
                                  float x0, float z0, float cell, int nx, int nz)
        {
            float r = Mathf.Max(cell * 0.5f, radius);
            float minX = Mathf.Min(a.x, b.x) - r, maxX = Mathf.Max(a.x, b.x) + r;
            float minZ = Mathf.Min(a.z, b.z) - r, maxZ = Mathf.Max(a.z, b.z) + r;

            int ix0 = Mathf.Max(0, Mathf.FloorToInt((minX - x0) / cell));
            int ix1 = Mathf.Min(nx - 1, Mathf.CeilToInt((maxX - x0) / cell));
            int iz0 = Mathf.Max(0, Mathf.FloorToInt((minZ - z0) / cell));
            int iz1 = Mathf.Min(nz - 1, Mathf.CeilToInt((maxZ - z0) / cell));

            float r2 = r * r;
            for (int iz = iz0; iz <= iz1; iz++)
            {
                float pz = z0 + (iz + 0.5f) * cell;
                for (int ix = ix0; ix <= ix1; ix++)
                {
                    int k = iz * nx + ix;
                    if (g[k]) continue;
                    float px = x0 + (ix + 0.5f) * cell;
                    if (DistanceSqToSegment(px, pz, a, b) <= r2) g[k] = true;
                }
            }
        }

        /// <summary>点から線分までの距離の2乗（XZ）</summary>
        static float DistanceSqToSegment(float px, float pz, Vector3 a, Vector3 b)
        {
            float ax = a.x, az = a.z, bx = b.x, bz = b.z;
            float dx = bx - ax, dz = bz - az;
            float len2 = dx * dx + dz * dz;
            float t = len2 > 1e-8f ? ((px - ax) * dx + (pz - az) * dz) / len2 : 0f;
            t = Mathf.Clamp01(t);
            float cx = ax + dx * t, cz = az + dz * t;
            float ex = px - cx, ez = pz - cz;
            return ex * ex + ez * ez;
        }

        /// <summary>塗りを消す</summary>
        public static void Clear(Transform parent)
        {
            if (parent == null) return;
            for (int i = parent.childCount - 1; i >= 0; i--)
            {
                Transform t = parent.GetChild(i);
                if (!t.name.StartsWith("FloorPaint_")) continue;
                if (Application.isPlaying) Destroy(t.gameObject);
                else DestroyImmediate(t.gameObject);
            }
        }

        // ---------------- 格子に落とす ----------------

        /// <summary>
        /// 置いてあるもの（通れないもの）の footprint を格子に塗る。
        /// 床・通路の色板・座る場所の見た目は通れるので入れない。
        /// </summary>
        static bool[] RasterizeSolids(StorePlan plan, float x0, float z0, float cell, int nx, int nz)
        {
            if (plan == null) return null;
            bool[] g = new bool[nx * nz];

            for (int i = 0; i < plan.boxes.Count; i++)
            {
                PlanBox b = plan.boxes[i];
                if (b.style == BoxStyle.Floor || b.style == BoxStyle.AisleMark ||
                    b.style == BoxStyle.BenchSeat) continue;
                Fill(g, b.rect, x0, z0, cell, nx, nz);
            }
            for (int i = 0; i < plan.places.Count; i++)
            {
                PlanPlace p = plan.places[i];
                if (!p.solid || p.rect.IsEmpty) continue;
                Fill(g, p.rect, x0, z0, cell, nx, nz);
            }
            return g;
        }

        static void Fill(bool[] g, PlanRect r, float x0, float z0, float cell, int nx, int nz)
        {
            int ix0 = Mathf.Max(0, Mathf.FloorToInt((r.xMin - x0) / cell));
            int ix1 = Mathf.Min(nx - 1, Mathf.CeilToInt((r.xMax - x0) / cell) - 1);
            int iz0 = Mathf.Max(0, Mathf.FloorToInt((r.zMin - z0) / cell));
            int iz1 = Mathf.Min(nz - 1, Mathf.CeilToInt((r.zMax - z0) / cell) - 1);
            for (int iz = iz0; iz <= iz1; iz++)
                for (int ix = ix0; ix <= ix1; ix++) g[iz * nx + ix] = true;
        }




        static int Count(bool[] g)
        {
            int n = 0;
            for (int i = 0; i < g.Length; i++) if (g[i]) n++;
            return n;
        }

        // ---------------- 四角にまとめてメッシュにする ----------------

        /// <summary>
        /// 塗るマスを大きな四角にまとめて、1枚のメッシュにする。
        /// 当たりは付けない（NavMesh に焼かれないようにする）。四角の数を返す。
        /// </summary>
        static int Build(Transform parent, string name, bool[] grid, int nx, int nz,
                         float x0, float z0, float cell, float y, Material mat)
        {
            bool[] used = new bool[grid.Length];
            List<Vector3> verts = new List<Vector3>();
            List<int> tris = new List<int>();
            int quads = 0;

            for (int iz = 0; iz < nz; iz++)
            {
                for (int ix = 0; ix < nx; ix++)
                {
                    int k = iz * nx + ix;
                    if (!grid[k] || used[k]) continue;

                    // 右へ伸ばす
                    int w = 1;
                    while (ix + w < nx && grid[iz * nx + ix + w] && !used[iz * nx + ix + w]) w++;

                    // 上へ伸ばす（同じ幅ぶん続くあいだ）
                    int h = 1;
                    while (iz + h < nz)
                    {
                        bool full = true;
                        for (int i = 0; i < w; i++)
                        {
                            int kk = (iz + h) * nx + ix + i;
                            if (!grid[kk] || used[kk]) { full = false; break; }
                        }
                        if (!full) break;
                        h++;
                    }

                    for (int j = 0; j < h; j++)
                        for (int i = 0; i < w; i++) used[(iz + j) * nx + ix + i] = true;

                    float ax = x0 + ix * cell, az = z0 + iz * cell;
                    float bx = ax + w * cell, bz = az + h * cell;
                    int v = verts.Count;
                    verts.Add(new Vector3(ax, 0f, az));
                    verts.Add(new Vector3(bx, 0f, az));
                    verts.Add(new Vector3(bx, 0f, bz));
                    verts.Add(new Vector3(ax, 0f, bz));
                    tris.Add(v); tris.Add(v + 2); tris.Add(v + 1);
                    tris.Add(v); tris.Add(v + 3); tris.Add(v + 2);
                    quads++;
                }
            }

            if (quads == 0) return 0;

            GameObject go = new GameObject(name);
            go.transform.SetParent(parent, false);
            go.transform.position = new Vector3(0f, y, 0f);

            Mesh mesh = new Mesh();
            mesh.name = name;
            mesh.indexFormat = verts.Count > 65000
                ? UnityEngine.Rendering.IndexFormat.UInt32
                : UnityEngine.Rendering.IndexFormat.UInt16;
            mesh.SetVertices(verts);
            mesh.SetTriangles(tris, 0);
            mesh.RecalculateNormals();
            mesh.RecalculateBounds();

            go.AddComponent<MeshFilter>().sharedMesh = mesh;
            MeshRenderer mr = go.AddComponent<MeshRenderer>();
            if (mat != null) mr.sharedMaterial = mat;
            mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
            mr.receiveShadows = false;
            return quads;
        }
    }
}
