using System.Collections.Generic;
using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// サイトの試算の値（serving-robot-params.json）を読む .asset。
    ///
    /// 2026-10-22 にエイイチと決めた「何について正か」（unity_design_v0.md の 0 章）:
    ///   条件（スタッフ・来客・ロボットの台数）と、店の回し方の決まりと時間は **サイトが正**。
    /// なので、店の回し方の数字は .asset に手で写さず、この JSON から読む。
    ///
    /// role（meta.roles）の扱い:
    ///   site_rule      … サイトが正。Unity はこの値を使う
    ///   unity_measures … **読まない**（Unity で測ってサイトに戻すための仮の値）
    ///   site_only      … 使わない（試算の中だけの設定）
    /// unity_measures の名前（robot_speed / pause_rate / pause_s / stuck_rate）は、
    /// まちがって使わないように、読もうとしたら例外ではなく「読まない」として弾く。
    ///
    /// 版の突き合わせ: meta.layout_generation が、店の形の JSON の meta.generation と
    /// 違ったら回さない（Ready が false になる）。
    /// </summary>
    [CreateAssetMenu(menuName = "ServiceSim/Sim Params", fileName = "SimParams")]
    public class SimParams : ScriptableObject
    {
        /// <summary>Unity が読まない値（unity_measures）の名前</summary>
        public static readonly string[] UnityMeasureKeys =
            { "robot_speed", "pause_rate", "pause_s", "stuck_rate" };

        [Header("読む JSON（サイトから写したもの）")]
        public TextAsset paramsJson;

        [Header("写した記録（報告に出す）")]
        public string fetchedAt = "";
        public string sourceUrl = "";

        // ---- 読んだ中身（Load のたびに作り直す）----
        JsonValue _root;
        readonly List<string> _problems = new List<string>();
        string _layoutGeneration = "";
        string _model = "";

        public string LayoutGeneration { get { return _layoutGeneration; } }
        public string Model { get { return _model; } }
        public bool Ready { get { return _root != null && _problems.Count == 0; } }
        public IList<string> Problems { get { return _problems; } }

        /// <summary>1 つの条件（unity_conditions の 1 件）</summary>
        public class Condition
        {
            public string store;          // fam_p1 / fam_p5
            public int staff;
            public int robots;
            public int arrivalsPerHour;
            public string mode;           // serve（上げ膳だけ）
            public int trays;             // 1 往復で運ぶ卓の数
            public string fragileBy;      // any（倒れやすい物もロボットが運ぶ）
            public readonly Dictionary<string, float> siteResult = new Dictionary<string, float>();

            /// <summary>書き出しのフォルダ名に使う名前</summary>
            public string Id
            {
                get { return store + "_s" + staff + "_r" + robots + "_a" + arrivalsPerHour; }
            }
        }

        readonly List<Condition> _conditions = new List<Condition>();
        public IList<Condition> Conditions { get { return _conditions; } }

        /// <summary>
        /// JSON を読む。storeGeneration に店の形の JSON の meta.generation を渡すと、
        /// meta.layout_generation と突き合わせる（違えば Ready が false）。
        /// </summary>
        public bool Load(string storeGeneration)
        {
            _problems.Clear();
            _conditions.Clear();
            _root = null;
            _layoutGeneration = "";
            _model = "";

            if (paramsJson == null)
            {
                _problems.Add("serving-robot-params.json（paramsJson）が入っていません");
                return false;
            }

            string err;
            JsonValue root = JsonValue.Parse(paramsJson.text, out err);
            if (root == null || !root.IsObject)
            {
                _problems.Add("serving-robot-params.json を読めません: " + err);
                return false;
            }
            _root = root;

            JsonValue meta = root["meta"];
            _layoutGeneration = meta["layout_generation"].S("");
            _model = meta["model"].S("");

            if (string.IsNullOrEmpty(_layoutGeneration))
                _problems.Add("meta.layout_generation がありません");
            else if (!string.IsNullOrEmpty(storeGeneration) && _layoutGeneration != storeGeneration)
                _problems.Add("版の番号が店の形の JSON と違います: 試算 " + _layoutGeneration +
                              " / 店の形 " + storeGeneration + "（写し直してください）");

            // ---- 条件 ----
            JsonValue list = root["unity_conditions"];
            for (int i = 0; i < list.Count; i++)
            {
                JsonValue c = list[i]["condition"];
                Condition cond = new Condition();
                cond.store = c["store"].S("");
                cond.staff = c["staff"].I(0);
                cond.robots = c["robots"].I(0);
                cond.arrivalsPerHour = c["arrivals_per_h"].I(0);
                cond.mode = c["mode"].S("serve");
                cond.trays = c["trays"].I(1);
                cond.fragileBy = c["fragile_by"].S("any");
                JsonValue sr = list[i]["site_result"];
                foreach (string k in sr.Keys) cond.siteResult[k] = sr[k].F(0f);
                if (string.IsNullOrEmpty(cond.store) || cond.staff <= 0)
                    _problems.Add("unity_conditions[" + i + "] の中身が足りません");
                _conditions.Add(cond);
            }
            if (_conditions.Count == 0) _problems.Add("unity_conditions が空です");

            return Ready;
        }

        // ---------------- 値の読み出し ----------------

        /// <summary>
        /// common の値（role=site_rule のものだけ）。
        /// unity_measures / site_only を読もうとしたら、使わずに fallback を返して問題に残す。
        /// </summary>
        public float SiteRule(string key, float fallback)
        {
            JsonValue v = Common(key);
            if (v == null) return fallback;
            return v["value"].F(fallback);
        }

        public float[] SiteRuleArray(string key, float[] fallback)
        {
            JsonValue v = Common(key);
            if (v == null) return fallback;
            JsonValue a = v["value"];
            if (!a.IsArray) return fallback;
            float[] r = new float[a.Count];
            for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f);
            return r;
        }

        /// <summary>common の 1 件。role が site_rule でなければ null（使わない）</summary>
        JsonValue Common(string key)
        {
            if (_root == null) return null;
            JsonValue v = _root["common"][key];
            if (v.IsNull)
            {
                _problems.Add("common." + key + " がありません");
                return null;
            }
            string role = v["role"].S("");
            if (role == "site_rule") return v;
            _problems.Add("common." + key + " は role=" + role + " なので Unity は読みません");
            return null;
        }

        /// <summary>stores.{key} の 1 件（fam_p1 / fam_p5）</summary>
        public JsonValue Store(string storeKey)
        {
            if (_root == null) return null;
            JsonValue s = _root["stores"][storeKey];
            if (s.IsNull)
            {
                _problems.Add("stores." + storeKey + " がありません");
                return null;
            }
            return s;
        }

        public float StoreValue(string storeKey, string key, float fallback)
        {
            JsonValue s = Store(storeKey);
            if (s == null) return fallback;
            JsonValue v = s[key];
            return v.kind == JsonValue.Kind.Number ? v.F(fallback) : fallback;
        }

        /// <summary>stores.{key} の配列（[最短, 最頻, 最長] など）</summary>
        public float[] StoreArray(string storeKey, string key, float[] fallback)
        {
            JsonValue s = Store(storeKey);
            if (s == null) return fallback;
            JsonValue a = s[key];
            if (!a.IsArray) return fallback;
            float[] r = new float[a.Count];
            for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f);
            return r;
        }

        /// <summary>
        /// 配列の配列の、index 番めを [最短, 最頻, 最長] で取る
        /// （first_dishes は [[5,8,12],[8,12,18]] の形）
        /// </summary>
        public Vector3 StoreTriangleAt(string storeKey, string key, int index, Vector3 fallback)
        {
            JsonValue s = Store(storeKey);
            if (s == null) return fallback;
            JsonValue a = s[key];
            if (!a.IsArray || index < 0 || index >= a.Count) return fallback;
            JsonValue t = a[index];
            if (!t.IsArray || t.Count < 3) return fallback;
            return new Vector3(t[0].F(0f), t[1].F(0f), t[2].F(0f));
        }

        /// <summary>[最短, 最頻, 最長] を Vector3 で</summary>
        public Vector3 StoreTriangle(string storeKey, string key, Vector3 fallback)
        {
            float[] a = StoreArray(storeKey, key, null);
            if (a == null || a.Length < 3) return fallback;
            return new Vector3(a[0], a[1], a[2]);
        }

        /// <summary>真ん中の値（arrivals・staff の「普通」）</summary>
        public int StoreMiddle(string storeKey, string key, int fallback)
        {
            float[] a = StoreArray(storeKey, key, null);
            if (a == null || a.Length == 0) return fallback;
            return Mathf.RoundToInt(a[a.Length / 2]);
        }

        /// <summary>何番めの条件（_conditions の並びのまま）。範囲外なら null</summary>
        public Condition ConditionAt(int index)
        {
            return index >= 0 && index < _conditions.Count ? _conditions[index] : null;
        }

        /// <summary>metrics の名前と意味（meta.metrics）</summary>
        public string MetricNote(string key)
        {
            if (_root == null) return "";
            return _root["meta"]["metrics"][key].S("");
        }

        public List<string> MetricKeys()
        {
            List<string> keys = new List<string>();
            if (_root == null) return keys;
            foreach (string k in _root["meta"]["metrics"].Keys) keys.Add(k);
            return keys;
        }

        /// <summary>報告に出す覚え書き</summary>
        public string Report()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            sb.AppendLine("サイトの試算の値（serving-robot-params.json）");
            sb.AppendLine("  ファイル: " + (paramsJson != null ? paramsJson.name : "（未設定）"));
            sb.AppendLine("  写した日時: " + (string.IsNullOrEmpty(fetchedAt) ? "（未記入）" : fetchedAt));
            sb.AppendLine("  写し元: " + (string.IsNullOrEmpty(sourceUrl) ? "（未記入）" : sourceUrl));
            sb.AppendLine("  試算の版 (meta.model): " + _model +
                          " / 店の形の版 (meta.layout_generation): " + _layoutGeneration);
            sb.AppendLine("  条件: " + _conditions.Count + " 件");
            for (int i = 0; i < _conditions.Count; i++)
            {
                Condition c = _conditions[i];
                sb.AppendLine("    " + c.Id + "  スタッフ " + c.staff + " / ロボット " + c.robots +
                              " / " + c.arrivalsPerHour + " 組毎時 / " + c.mode +
                              " / 1往復 " + c.trays + " 卓 / 倒れやすい物 " + c.fragileBy);
            }
            sb.AppendLine("  Unity が読まない値 (unity_measures): " +
                          string.Join(" / ", UnityMeasureKeys));
            if (_problems.Count == 0) sb.AppendLine("  合わないもの: なし");
            else
            {
                sb.AppendLine("  合わないもの: " + _problems.Count + " 件");
                for (int i = 0; i < _problems.Count; i++) sb.AppendLine("    - " + _problems[i]);
            }
            return sb.ToString().TrimEnd();
        }
    }
}
