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

namespace ServiceSim
{
    /// <summary>
    /// キッチンのモニター（2026-10-23 エイイチと決めた。設計書 v0 の 0 章）。
    /// ロボット・スタッフのいまの仕事と止まりの理由、店の様子を1つにまとめて出す。
    ///
    /// 出し方は **Scene ビューの画面に貼り付けるパネル1つ**（描くのは ServiceSimLabels）。
    /// カメラを回してもズームしても、画面の上の位置は変わらない。
    /// 屋根の上に板を置く作り・店の上に浮かべる吹き出しは、どちらもやめた
    /// （読めなくなる／通路やほかの札に重なるため）。
    ///
    /// この script は **文を作るだけ**で、見た目は持たない。
    ///   ・当たり判定・NavMesh・ロボットのセンサー・人がよける処理には入らない
    ///   ・言葉と数字は、記録に使っているものだけ（LABEL_RULES.md の表）
    ///   ・文を作り直すのは店の中の 0.5 秒ごと（毎コマ作らない）
    ///   ・頭の上の字幕は測る回では切る（B11）が、このパネル1つだけは出す
    /// </summary>
    public class KitchenMonitor : MonoBehaviour
    {
        [Header("参照（Setup が入れる）")]
        public StoreBuilder store;
        public ServiceOps ops;
        public GroupManager groups;
        public PeopleSpawner people;

        [Header("出し方")]
        [Tooltip("出すか。ExperimentPlan.showKitchenMonitor が入れる")]
        public bool show = true;

        [Tooltip("文を作り直す間隔（店の中の秒）。毎コマ作らない")]
        public float refreshSec = 0.5f;

        [Header("いまの様子（読むだけ）")]
        [SerializeField] int _lines;

        float _nextAtS;
        string _text = "";
        readonly StringBuilder _sb = new StringBuilder();

        /// <summary>この部品を置く親の名前</summary>
        public const string RootName = "KitchenMonitor";

        /// <summary>いま出す文（ServiceSimLabels が読む）。空なら出さない</summary>
        public string Text { get { return _text; } }

        /// <summary>行の数（読むだけ）</summary>
        public int Lines { get { return _lines; } }

        void Update()
        {
            if (!show) { _text = ""; _lines = 0; return; }
            if (ops == null) return;      // 回していないあいだは見本を出す（描く側が決める）

            float now = ops.NowS;
            if (now < _nextAtS) return;
            _nextAtS = now + Mathf.Max(0.05f, refreshSec);
            _text = Compose();
            _lines = CountLines(_text);
        }

        /// <summary>
        /// 店を建てたあと、まだ回していないときに出す見本（ServiceSimLabels が読む）。
        /// 場所と大きさを確かめられるようにするため
        /// </summary>
        public static string SampleText()
        {
            return "ロボット×2\n" +
                   "  1号機 配膳 卓19 ／ 一時停止：人が立ち止まっていた\n" +
                   "  2号機 戻る\n" +
                   "スタッフ×4  案内 1／配膳 1／片付け 1／待機 1\n" +
                   "ロボット待ち スタッフ 0 人 ／ 客 1 人（回り道 0 人）\n" +
                   "組 0（席待ち 0・帰った 0） 皿 0\n" +
                   "受け渡し口でロボットを待っている皿 0\n" +
                   "あきらめた 0 ／ 移した 0 ／ 卓を空けた 0\n" +
                   "立ち往生 0 回　　※見本（回すと本物の数字になります）";
        }

        static int CountLines(string s)
        {
            if (string.IsNullOrEmpty(s)) return 0;
            int n = 1;
            for (int i = 0; i < s.Length; i++) if (s[i] == '\n') n++;
            return n;
        }

        /// <summary>
        /// 前の作り（屋根の上の板）で置いた子を片付ける。
        /// 吹き出しに変えたので、板と文字は要らない
        /// </summary>
        public void RemoveOldBoards()
        {
            RemoveChild("Board");
            RemoveChild("Backing");
        }

        void RemoveChild(string name)
        {
            Transform t = transform.Find(name);
            if (t == null) return;
            if (Application.isPlaying) Destroy(t.gameObject);
            else DestroyImmediate(t.gameObject);
        }

        // ---------------- 文を作る ----------------

        string Compose()
        {
            _sb.Length = 0;

            // ---- ロボット（台ごとに仕事と、止まっているときは理由）----
            IList<ServingRobotController> rs = ops.Robots;
            _sb.Append("ロボット×").Append(rs.Count).Append('\n');
            for (int i = 0; i < rs.Count; i++)
            {
                ServingRobotController r = rs[i];
                if (r == null) continue;
                _sb.Append("  ").Append(r.robotId).Append("号機 ").Append(RobotJob(r));
                string why = StopText(r);
                if (why.Length > 0) _sb.Append(" ／ ").Append(why);
                _sb.Append('\n');
            }

            // ---- スタッフ（仕事ごとの人数）----
            AppendStaff();

            // ---- ロボット待ち（スタッフと客の人数）----
            int waitStaff, waitGuest, detours;
            CountYielding(out waitStaff, out waitGuest, out detours);
            _sb.Append("ロボット待ち スタッフ ").Append(waitStaff)
               .Append(" 人 ／ 客 ").Append(waitGuest).Append(" 人")
               .Append("（回り道 ").Append(detours).Append(" 人）").Append('\n');

            // ---- 店の様子 ----
            if (groups != null)
                _sb.Append("組 ").Append(groups.GroupsArrived)
                   .Append("（席待ち ").Append(groups.WaitingGroups)
                   .Append("・帰った ").Append(groups.GroupsDone).Append("）");
            _sb.Append(" 皿 ").Append(ops.DishesWaiting).Append('\n');

            // ---- 受け渡し口でロボットを待っている皿（サイトの試算 v2.7）----
            // 料理ができてから robot_first_wait_s のあいだは、ロボットだけが運ぶ。
            // そのあいだ、まだ誰も持っていない皿の数（記録と同じ数え方）
            _sb.Append("受け渡し口でロボットを待っている皿 ")
               .Append(ops.DishesWaitingForRobot).Append('\n');

            _sb.Append("あきらめた ").Append(ops.gaveUpCount)
               .Append(" ／ 移した ").Append(ops.warpedCount)
               .Append(" ／ 卓を空けた ").Append(ops.tableFreedCount).Append('\n');
            _sb.Append("立ち往生 ").Append(ops.rescueCalls).Append(" 回");

            return _sb.ToString();
        }

        /// <summary>ロボットの仕事（LABEL_RULES の言葉）</summary>
        static string RobotJob(ServingRobotController r)
        {
            switch (r.CurrentPhase)
            {
                case ServingRobotController.Phase.ToTable: return "配膳 卓" + r.TableId.ToString("00");
                case ServingRobotController.Phase.Dwell: return "配膳 卓" + r.TableId.ToString("00");
                case ServingRobotController.Phase.ToPass: return "戻る";
                case ServingRobotController.Phase.StuckWait: return "立ち往生";
                default: return "待機";
            }
        }

        /// <summary>止まっているときの理由（動いていれば空）</summary>
        static string StopText(ServingRobotController r)
        {
            if (r.CurrentPhase == ServingRobotController.Phase.StuckWait) return "";   // 仕事の側に出ている
            if (r.spec != null && r.SpeedMps > r.spec.stoppedSpeedEpsilon) return "";

            string why = r.StopCauseNow;
            if (why.Length == 0) return "";
            return "一時停止：" + why;
        }

        void AppendStaff()
        {
            IReadOnlyList<StaffAgent> staff = StaffAgent.All;
            _sb.Append("スタッフ×").Append(staff.Count);

            Dictionary<string, int> byJob = new Dictionary<string, int>();
            for (int i = 0; i < staff.Count; i++)
            {
                if (staff[i] == null) continue;
                string job = MonitorJobName(staff[i]);
                int n;
                byJob.TryGetValue(job, out n);
                byJob[job] = n + 1;
            }

            bool first = true;
            foreach (KeyValuePair<string, int> kv in byJob)
            {
                _sb.Append(first ? "  " : "／").Append(kv.Key).Append(' ').Append(kv.Value);
                first = false;
            }
            _sb.Append('\n');
        }

        /// <summary>
        /// スタッフの仕事の名前。記録（staff_time_s）の言葉は過去の記録と比べるため変えないので、
        /// ここだけ LABEL_RULES の表の言葉に直して出す（料理 → 配膳）。
        /// </summary>
        static string MonitorJobName(StaffAgent a)
        {
            if (a.CurrentJob == null) return "待機";
            switch (a.CurrentJob.kind)
            {
                case StaffJobKind.Guide: return "案内";
                case StaffJobKind.Serve: return "配膳";
                case StaffJobKind.Call: return "呼び出し";
                case StaffJobKind.Pay: return "会計";
                case StaffJobKind.Clear: return "片付け";
                case StaffJobKind.Rescue: return "立ち往生";
            }
            return "待機";
        }

        /// <summary>
        /// いまロボットをよけている人を数える。
        ///   ロボット待ち … 入口で待つ・通路の中で下がる（止まっている）
        ///   回り道       … 別の通路へまわっている（止まっていない）
        /// </summary>
        void CountYielding(out int waitStaff, out int waitGuest, out int detours)
        {
            waitStaff = 0; waitGuest = 0; detours = 0;

            IReadOnlyList<PersonBody> all = PersonBody.All;
            for (int i = 0; i < all.Count; i++)
            {
                PersonBody b = all[i];
                if (b == null || b.kind == PersonKind.Seated) continue;

                PersonRobotYield y = b.GetComponent<PersonRobotYield>();
                if (y == null || !y.Intervening) continue;

                if (y.CurrentMode == PersonRobotYield.Mode.Detour) { detours++; continue; }
                if (b.kind == PersonKind.Staff) waitStaff++;
                else waitGuest++;
            }
        }
    }
}
