using System.Collections.Generic;
using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// サイトの試算の値（SimParams）を、その回だけ ServiceSpec / PeopleSpec / RobotSpec に入れる。
    ///
    /// 2026-10-22 にエイイチと決めた「何について正か」（unity_design_v0.md の 0 章）:
    ///   条件（スタッフ・来客・ロボットの台数）と、店の回し方の決まりと時間は **サイトが正**。
    ///   .asset に手で写した値は使わない。
    ///
    /// .asset を書き換えたままにしないため、入れる前の値を覚えて、回し終わりに戻す
    /// （停止点（C1）と同じやり方）。実際に使った値は report で報告に出す。
    ///
    /// role の扱いは SimParams と同じ。unity_measures（robot_speed・pause_rate・pause_s・
    /// stuck_rate）は読まない。ロボットの速さと止まり方は、今の RobotSpec とセンサーのまま。
    ///
    /// v2.7（2026-10-23）で、料理の割り当ての決まりが変わった。
    /// common.robot_first_wait_s（role=site_rule）を ServiceSpec.robotFirstWaitSec に入れる。
    /// 読めなければ Problems に残して回さない（値を手で写さないため）。
    /// 決まりの中身は ServiceOps.PutDish と ReleaseDishesToStaff を見る。
    /// </summary>
    public class SiteValues
    {
        /// <summary>サイトに値はあるが、Unity では形にしていないもの（報告に出す）</summary>
        public static readonly string[] NotModelled =
        {
            "common.load_s（15 秒）: 下げ膳だけで使う値。上げ膳（mode=serve）ではサイトも 0 秒なので、" +
                "PeopleSpec.staffDwellAtPassSec を 0 にして回す",
            "common.guest_load_s（下げ膳で客が皿を載せる 20 秒）: 今回の条件は mode=serve（上げ膳だけ）なので使わない",
            "common.dish_extra_m（0 分）: 0 なので差は出ない",
            "stores.extra_orders（0）・stores.extra_cook: 追加注文が 0 なので使わない",
            "stores.staff_all・stores.arrivals・stores.dist_m・stores.dist_list: 条件の範囲と道のりの確かめに使う値（回し方には入れない）",
        };

        readonly List<string> _lines = new List<string>();
        readonly List<string> _problems = new List<string>();

        ServiceSpec _service;
        PeopleSpec _people;
        RobotSpec _robot;
        bool _saved;

        // ---- 入れる前の値 ----
        int _sArrivals, _sTrays, _sBusTrips;
        bool _sFragileByRobot;
        float _sPeak, _sGuide, _sPay, _sCall, _sPlace, _sClear, _sWipe, _sRescue, _sMaxExtra;
        float _sRobotFirstWait;
        float _sCold, _sCallRate, _sFragilePerGroup;
        Vector3 _sFragileCook, _sEat, _sDish1, _sDish2;
        int _pStaffCount;
        float _pStaffSpeed, _pDwellAtPass;
        float _rTableDwell;

        /// <summary>実際に入れた値（報告に出す）</summary>
        public IList<string> Lines { get { return _lines; } }

        /// <summary>足りない・読めない値（1つでもあれば回さない）</summary>
        public IList<string> Problems { get { return _problems; } }

        public bool Ok { get { return _problems.Count == 0; } }

        /// <summary>
        /// その条件の値を入れる。storeKey は "fam_p1" / "fam_p5"。
        /// 戻り値 false なら、足りない値があるので回してはいけない。
        /// </summary>
        public bool Apply(SimParams p, string storeKey, SimParams.Condition cond,
                          ServiceSpec service, PeopleSpec people, RobotSpec robot)
        {
            _lines.Clear();
            _problems.Clear();

            if (p == null) { _problems.Add("SimParams が入っていません"); return false; }
            if (cond == null) { _problems.Add("条件（unity_conditions の1件）が選ばれていません"); return false; }
            if (service == null || people == null || robot == null)
            {
                _problems.Add("ServiceSpec / PeopleSpec / RobotSpec が入っていません");
                return false;
            }
            if (p.Store(storeKey) == null)
            {
                _problems.Add("stores." + storeKey + " がありません");
                return false;
            }

            Save(service, people, robot);

            // ---- 条件（unity_conditions）----
            service.arrivalsPerHour = cond.arrivalsPerHour;
            service.trays = Mathf.Max(1, cond.trays);
            service.fragileByRobot = cond.fragileBy == "any";
            people.staffCount = cond.staff;
            Line("条件 arrivals_per_h", cond.arrivalsPerHour + " 組/時");
            Line("条件 trays", service.trays + " 卓/往復");
            Line("条件 fragile_by", cond.fragileBy + "（倒れやすい物をロボットが運ぶ: " +
                 (service.fragileByRobot ? "はい" : "いいえ") + "）");
            Line("条件 staff", cond.staff + " 人");
            Line("条件 robots", cond.robots + " 台");
            Line("条件 mode", cond.mode);
            if (cond.mode != "serve")
                _problems.Add("mode が serve ではありません（" + cond.mode +
                              "）。Unity は上げ膳だけを作っています");

            // ---- common（role=site_rule のものだけ）----
            service.guideSec = Rule(p, "guide_s", service.guideSec);
            service.paySec = Rule(p, "pay_s", service.paySec);
            service.callSec = Rule(p, "call_s", service.callSec);
            service.staffPlaceSec = Rule(p, "staff_place_s", service.staffPlaceSec);
            service.robotFirstWaitSec = Rule(p, "robot_first_wait_s", service.robotFirstWaitSec);
            service.clearSec = Rule(p, "clear_s", service.clearSec);
            service.wipeSec = Rule(p, "wipe_s", service.wipeSec);
            service.rescueSec = Rule(p, "rescue_s", service.rescueSec);
            service.maxExtraMin = Rule(p, "max_extra_min", service.maxExtraMin);
            service.coldMin = Rule(p, "cold_min", service.coldMin);
            people.staffSpeed = Rule(p, "staff_speed", people.staffSpeed);
            robot.tableDwellSec = Rule(p, "robot_handoff_s", robot.tableDwellSec);

            // 上げ膳（mode=serve）では、サイトはロボットに載せる時間を 0 秒にしている
            // （common.load_s 15 秒は下げ膳だけで使う値）。そこにそろえる
            people.staffDwellAtPassSec = 0f;
            Line("受け渡し口で載せる時間", "0 秒（上げ膳なので load_s は使わない）");

            float[] fc = p.SiteRuleArray("fragile_cook", null);
            if (fc != null && fc.Length >= 3)
            {
                service.fragileCookMin = new Vector3(fc[0], fc[1], fc[2]);
                Line("common.fragile_cook", Tri(service.fragileCookMin) + " 分");
            }
            else _problems.Add("common.fragile_cook を読めません");

            // ---- stores.{storeKey} ----
            service.peakMinutes = StoreValue(p, storeKey, "peak_min", service.peakMinutes);
            service.callRate = StoreValue(p, storeKey, "call_rate", service.callRate);
            service.fragilePerGroup = StoreValue(p, storeKey, "fragile", service.fragilePerGroup);
            service.busTrips = Mathf.RoundToInt(
                StoreValue(p, storeKey, "bus_trips", service.busTrips));

            service.eatMin = Triangle(p, storeKey, "eat_min", service.eatMin);
            service.firstDish1CookMin = TriangleAt(p, storeKey, "first_dishes", 0, service.firstDish1CookMin);
            service.firstDish2CookMin = TriangleAt(p, storeKey, "first_dishes", 1, service.firstDish2CookMin);

            // 卓の数は店の形の JSON から建てている。数が合わなければ、どちらかが古い
            int tables = Mathf.RoundToInt(StoreValue(p, storeKey, "tables", 0f));
            Line("stores.tables", tables + " 卓（店の形の JSON と合うかは建てたあとに確かめる）");

            _saved = true;
            return Ok;
        }

        /// <summary>入れる前の値に戻す（回し終わり・Play を抜けたとき）</summary>
        public void Restore()
        {
            if (!_saved) return;
            _saved = false;

            if (_service != null)
            {
                _service.arrivalsPerHour = _sArrivals;
                _service.trays = _sTrays;
                _service.busTrips = _sBusTrips;
                _service.fragileByRobot = _sFragileByRobot;
                _service.peakMinutes = _sPeak;
                _service.guideSec = _sGuide;
                _service.paySec = _sPay;
                _service.callSec = _sCall;
                _service.staffPlaceSec = _sPlace;
                _service.robotFirstWaitSec = _sRobotFirstWait;
                _service.clearSec = _sClear;
                _service.wipeSec = _sWipe;
                _service.rescueSec = _sRescue;
                _service.maxExtraMin = _sMaxExtra;
                _service.coldMin = _sCold;
                _service.callRate = _sCallRate;
                _service.fragilePerGroup = _sFragilePerGroup;
                _service.fragileCookMin = _sFragileCook;
                _service.eatMin = _sEat;
                _service.firstDish1CookMin = _sDish1;
                _service.firstDish2CookMin = _sDish2;
            }
            if (_people != null)
            {
                _people.staffCount = _pStaffCount;
                _people.staffSpeed = _pStaffSpeed;
                _people.staffDwellAtPassSec = _pDwellAtPass;
            }
            if (_robot != null) _robot.tableDwellSec = _rTableDwell;
        }

        void Save(ServiceSpec service, PeopleSpec people, RobotSpec robot)
        {
            if (_saved) return;      // 条件を続けて回すときは、いちばん最初の値を覚えておく
            _service = service;
            _people = people;
            _robot = robot;

            _sArrivals = service.arrivalsPerHour;
            _sTrays = service.trays;
            _sBusTrips = service.busTrips;
            _sFragileByRobot = service.fragileByRobot;
            _sPeak = service.peakMinutes;
            _sGuide = service.guideSec;
            _sPay = service.paySec;
            _sCall = service.callSec;
            _sPlace = service.staffPlaceSec;
            _sRobotFirstWait = service.robotFirstWaitSec;
            _sClear = service.clearSec;
            _sWipe = service.wipeSec;
            _sRescue = service.rescueSec;
            _sMaxExtra = service.maxExtraMin;
            _sCold = service.coldMin;
            _sCallRate = service.callRate;
            _sFragilePerGroup = service.fragilePerGroup;
            _sFragileCook = service.fragileCookMin;
            _sEat = service.eatMin;
            _sDish1 = service.firstDish1CookMin;
            _sDish2 = service.firstDish2CookMin;

            _pStaffCount = people.staffCount;
            _pStaffSpeed = people.staffSpeed;
            _pDwellAtPass = people.staffDwellAtPassSec;
            _rTableDwell = robot.tableDwellSec;
        }

        // ---------------- 読み出し（読めなければ問題に残す）----------------

        float Rule(SimParams p, string key, float fallback)
        {
            int before = p.Problems.Count;
            float v = p.SiteRule(key, float.NaN);
            if (float.IsNaN(v) || p.Problems.Count > before)
            {
                _problems.Add("common." + key + " を読めません（role が site_rule か確かめてください）");
                return fallback;
            }
            Line("common." + key, v.ToString("0.###"));
            return v;
        }

        float StoreValue(SimParams p, string storeKey, string key, float fallback)
        {
            float v = p.StoreValue(storeKey, key, float.NaN);
            if (float.IsNaN(v))
            {
                _problems.Add("stores." + storeKey + "." + key + " を読めません");
                return fallback;
            }
            Line("stores." + key, v.ToString("0.###"));
            return v;
        }

        Vector3 Triangle(SimParams p, string storeKey, string key, Vector3 fallback)
        {
            Vector3 v = p.StoreTriangle(storeKey, key, new Vector3(float.NaN, 0f, 0f));
            if (float.IsNaN(v.x))
            {
                _problems.Add("stores." + storeKey + "." + key + " を読めません");
                return fallback;
            }
            Line("stores." + key, Tri(v) + " 分");
            return v;
        }

        Vector3 TriangleAt(SimParams p, string storeKey, string key, int index, Vector3 fallback)
        {
            Vector3 v = p.StoreTriangleAt(storeKey, key, index, new Vector3(float.NaN, 0f, 0f));
            if (float.IsNaN(v.x))
            {
                _problems.Add("stores." + storeKey + "." + key + "[" + index + "] を読めません");
                return fallback;
            }
            Line("stores." + key + "[" + index + "]", Tri(v) + " 分");
            return v;
        }

        static string Tri(Vector3 v)
        {
            return v.x.ToString("0.###") + " / " + v.y.ToString("0.###") + " / " + v.z.ToString("0.###");
        }

        void Line(string key, string value) { _lines.Add(key + " = " + value); }

        /// <summary>報告に出す覚え書き</summary>
        public string Report()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            sb.AppendLine("サイトの試算から入れた値（この回だけ。.asset は回し終わりに戻す）");
            for (int i = 0; i < _lines.Count; i++) sb.AppendLine("  " + _lines[i]);
            sb.AppendLine("  Unity が読まない値（unity_measures）: " +
                          string.Join(" / ", SimParams.UnityMeasureKeys));
            sb.AppendLine("  サイトにあるが Unity で形にしていないもの:");
            for (int i = 0; i < NotModelled.Length; i++) sb.AppendLine("    - " + NotModelled[i]);
            if (_problems.Count > 0)
            {
                sb.AppendLine("  読めなかった値: " + _problems.Count + " 件");
                for (int i = 0; i < _problems.Count; i++) sb.AppendLine("    - " + _problems[i]);
            }
            return sb.ToString().TrimEnd();
        }
    }
}
