using System.Collections.Generic;
using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// 1卓ぶんの椅子の出し入れ。座っている人は人として置かないので、
    /// この script が「その卓の椅子」だけを持つ。
    ///
    /// 引くきっかけは人の動きに合わせる（B6）。GroupManager が次のときに呼ぶ。
    ///   座るとき／ドリンクバーへ立つとき／トイレへ立つとき／会計で立つとき／退店するとき／戻って座り直すとき
    /// 食事中に何もなく引く分は、別の長い間隔（spec.chairPullIdleMeanIntervalSec）で少しだけ起こす。
    ///
    /// この script は椅子の出し入れだけを扱う。いつ引くかは GroupManager が決め、
    /// 椅子1脚の状態は Chair が持つ。数字は PeopleSpec の .asset に置く。
    /// </summary>
    public class SeatedGuest : MonoBehaviour
    {
        /// <summary>引いたきっかけ（記録に出す）</summary>
        public const string ReasonSit = "座る";
        public const string ReasonDrinkBar = "ドリンクバー";
        public const string ReasonToilet = "トイレ";
        public const string ReasonPay = "会計";
        public const string ReasonLeave = "退店";
        public const string ReasonSitBack = "戻って座る";
        public const string ReasonIdle = "食事中";

        [Header("設定")]
        public PeopleSpec spec;

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

        readonly List<Chair> _aisleChairs = new List<Chair>();

        /// <summary>_aisleChairs と同じ並び。0 より大きければ、その秒数だけ引いたまま</summary>
        readonly List<float> _holdLeft = new List<float>();

        System.Random _rng;
        ServiceOps _ops;
        int _tableIndex = -1;

        // ---- 座っている客の形（見た目だけ。当たり・PersonBody・NavMeshAgent を持たない）----
        GameObject _bodyPrefab;
        readonly List<Vector3> _seatPoints = new List<Vector3>();
        readonly List<GameObject> _bodies = new List<GameObject>();
        int _shown;
        bool _warnedNoBody;      // 出せないことを1回だけ知らせる

        /// <summary>いま出している座った形の数（読むだけ）</summary>
        public int SeatedShown { get { return _shown; } }

        /// <summary>この卓の席の数（座った形を出せる上限）</summary>
        public int SeatPointCount { get { return _seatPoints.Count; } }

        /// <summary>いま1脚でも引いているか</summary>
        public bool IsPulling { get { return _pulledNow > 0; } }

        /// <summary>この卓で引いた回数</summary>
        public int Pulls { get { return _pulls; } }

        /// <summary>引ける椅子の数</summary>
        public int AisleChairCount { get { return _aisleChairs.Count; } }

        /// <summary>この卓の引ける椅子と、乱数の種を渡す</summary>
        public void Configure(PeopleSpec peopleSpec, IList<Chair> aisleChairs, int seed,
                              ServiceOps ops, int tableIndex)
        {
            Configure(peopleSpec, aisleChairs, seed, ops, tableIndex, null, null);
        }

        /// <summary>席の位置と、座った形の prefab も渡す</summary>
        public void Configure(PeopleSpec peopleSpec, IList<Chair> aisleChairs, int seed,
                              ServiceOps ops, int tableIndex,
                              GameObject seatedBodyPrefab, IList<Vector3> seatPoints)
        {
            _ops = ops;
            _tableIndex = tableIndex;
            spec = peopleSpec;

            _bodyPrefab = seatedBodyPrefab;
            _seatPoints.Clear();
            if (seatPoints != null)
                for (int i = 0; i < seatPoints.Count; i++) _seatPoints.Add(seatPoints[i]);
            ClearBodies();

            _aisleChairs.Clear();
            _holdLeft.Clear();
            if (aisleChairs != null)
            {
                for (int i = 0; i < aisleChairs.Count; i++)
                {
                    if (aisleChairs[i] == null) continue;
                    _aisleChairs.Add(aisleChairs[i]);
                    _holdLeft.Add(0f);
                }
            }

            _rng = new System.Random(seed);
            _pulledNow = 0;
            _pulls = 0;
            _nextIdlePullIn = NextIdleInterval();
        }

        /// <summary>
        /// 人が動いたので、その人の席の椅子を引く。
        /// seatOrder はその卓の何人目か（同じ人がいつも同じ椅子を引くようにする）。
        /// </summary>
        public void PullFor(string reason, int seatOrder)
        {
            if (spec == null || _aisleChairs.Count == 0) return;

            int i = Mathf.Abs(seatOrder) % _aisleChairs.Count;
            Pull(i, reason);
        }

        void Pull(int i, string reason)
        {
            if (i < 0 || i >= _aisleChairs.Count) return;

            Chair c = _aisleChairs[i];
            if (c == null) return;

            // すでに引いてあるなら、引いたままの時間だけ延ばす（二重に数えない）
            if (_holdLeft[i] > 0f)
            {
                _holdLeft[i] = Mathf.Max(_holdLeft[i], spec.chairPullHoldSec);
                return;
            }

            c.Pull(spec.chairPullDistance);
            _holdLeft[i] = spec.chairPullHoldSec;
            _pulledNow++;
            _pulls++;
            if (_ops != null) _ops.AddChairPull(reason, TableIdOf());
        }

        int TableIdOf()
        {
            if (_ops == null || _tableIndex < 0 || _tableIndex >= _ops.Tables.Count) return 0;
            return _ops.Tables[_tableIndex].tableId;
        }

        float NextIdleInterval()
        {
            if (spec == null || spec.chairPullIdleMeanIntervalSec <= 0f) return float.MaxValue;
            double u = _rng.NextDouble();
            if (u <= 1e-9) u = 1e-9;
            return (float)(-spec.chairPullIdleMeanIntervalSec * System.Math.Log(u));
        }

        void Update()
        {
            if (spec == null || _rng == null) return;
            float dt = Time.deltaTime;

            // 引いたままの時間が切れた椅子を戻す
            for (int i = 0; i < _holdLeft.Count; i++)
            {
                if (_holdLeft[i] <= 0f) continue;
                _holdLeft[i] -= dt;
                if (_holdLeft[i] > 0f) continue;

                _holdLeft[i] = 0f;
                if (_aisleChairs[i] != null) _aisleChairs[i].Release();
                _pulledNow = Mathf.Max(0, _pulledNow - 1);
            }

            // 食事中に何もなく引く分（少しだけ）
            if (_nextIdlePullIn == float.MaxValue) return;
            _nextIdlePullIn -= dt;
            if (_nextIdlePullIn > 0f) return;
            _nextIdlePullIn = NextIdleInterval();

            if (!Occupied()) return;
            if (_aisleChairs.Count == 0) return;
            Pull(_rng.Next(_aisleChairs.Count), ReasonIdle);
        }

        /// <summary>この卓に組が座っているか</summary>
        bool Occupied()
        {
            if (_ops == null || _tableIndex < 0) return true;   // 分からないときは引く
            if (_tableIndex >= _ops.Tables.Count) return true;
            return _ops.Tables[_tableIndex].groupId >= 0;
        }

        // ---------------- 座っている客の形（見た目だけ）----------------

        /// <summary>
        /// 座っている客の形を count 人ぶん出す（0 なら全部消す）。
        /// 形は席の位置に置くだけ。当たり判定も PersonBody も NavMeshAgent も持たないので、
        /// NavMesh・前のセンサー・人の数の記録・物理にはいっさい効かない。
        /// </summary>
        public void ShowSeated(int count)
        {
            // 出せないときは黙って戻らない。
            // 10/21 の分が2日間だれにも気づかれなかったのは、ここが黙って戻っていたため
            if (_bodyPrefab == null || _seatPoints.Count == 0)
            {
                if (count > 0 && !_warnedNoBody)
                {
                    _warnedNoBody = true;
                    Debug.LogWarning("[ServiceSim] 座っている客の形を出せません（卓 " + _tableIndex + "）。" +
                                     (_bodyPrefab == null
                                        ? "形の prefab が入っていません（PeopleSpawner.seatedBodyPrefab）。"
                                          + "ServiceSim/座っている客の形を用意する を押してください"
                                        : "この卓に座る位置（seat_points）がありません"));
                }
                _shown = 0;
                return;
            }

            int want = Mathf.Clamp(count, 0, _seatPoints.Count);
            if (want == _shown) return;

            EnsureBodies();
            for (int i = 0; i < _bodies.Count; i++)
            {
                if (_bodies[i] == null) continue;
                bool on = i < want;
                if (_bodies[i].activeSelf != on) _bodies[i].SetActive(on);
            }
            _shown = want;
        }

        void EnsureBodies()
        {
            if (_bodies.Count == _seatPoints.Count) return;
            ClearBodies();

            for (int i = 0; i < _seatPoints.Count; i++)
            {
                GameObject go = Instantiate(_bodyPrefab, transform);
                go.name = "SeatedBody" + (i + 1).ToString("00");
                go.transform.position = _seatPoints[i];
                go.SetActive(false);
                _bodies.Add(go);
            }
        }

        void ClearBodies()
        {
            for (int i = 0; i < _bodies.Count; i++)
            {
                if (_bodies[i] == null) continue;
                if (Application.isPlaying) Destroy(_bodies[i]);
                else DestroyImmediate(_bodies[i]);
            }
            _bodies.Clear();
            _shown = 0;
        }

        void OnDisable()
        {
            ShowSeated(0);
            for (int i = 0; i < _holdLeft.Count; i++)
            {
                _holdLeft[i] = 0f;
                if (_aisleChairs[i] != null) _aisleChairs[i].Release();
            }
            _pulledNow = 0;
        }
    }
}
