using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// ロボットの前に人・ロボット・椅子があるかを見る。
    /// 見るだけで、止まる・動き出すの判断はしない（それは ServingRobotController）。
    /// 止まった原因を分けられるように、いちばん近いものの種類も返す。
    /// </summary>
    public class FrontSensor : MonoBehaviour
    {
        public struct Reading
        {
            /// <summary>前に何かあるか</summary>
            public bool blocked;
            /// <summary>いちばん近いものまでの、ロボットの外周からの距離（m）。何もなければ sensorRange</summary>
            public float nearestDistance;

            public bool sawRobot;
            public bool sawPerson;
            public bool sawChair;

            /// <summary>いちばん近いものが人か</summary>
            public bool nearestIsPerson;
            /// <summary>いちばん近いものが人のときの種類</summary>
            public PersonKind nearestPersonKind;
            /// <summary>いちばん近いものが人のときの速さ（m/秒）</summary>
            public float nearestPersonSpeed;
            /// <summary>いちばん近いものが人で、立ち止まっているか</summary>
            public bool nearestPersonStanding;
            /// <summary>いちばん近いものがロボットか</summary>
            public bool nearestIsRobot;
            /// <summary>いちばん近いものが椅子か</summary>
            public bool nearestIsChair;
            /// <summary>いちばん近いものの名前（cause_detail に使う）</summary>
            public string nearestName;
        }

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

        [Tooltip("自分のロボット。自分を見ないようにするために使う")]
        public ServingRobotController owner;

        const int MaxHits = 32;
        readonly Collider[] _hits = new Collider[MaxHits];

        /// <summary>
        /// この回で、前のセンサーの箱に入った当たりのいちばん多かった数（依頼 v11 の 5 章）。
        /// MaxHits（32）に届いていなければ切り捨てが起きていないので、
        /// 「物理の返る順番」はずれの元から外せる。ExperimentRunner が回し始めに 0 に戻す
        /// </summary>
        public static int MaxSeen;

        /// <summary>切り捨てが起きた回数（0 のはず）</summary>
        public static int Overflows;

        Reading _last;
        public Reading Last { get { return _last; } }

        /// <summary>いまの前の様子を見る</summary>
        public Reading Sense()
        {
            Reading r = new Reading();
            if (spec == null)
            {
                _last = r;
                return r;
            }

            r.nearestDistance = spec.sensorRange;
            r.nearestName = "";

            float radius = spec.Radius;
            Vector3 fwd = transform.forward;
            Vector3 eye = transform.position + Vector3.up * (spec.sensorHeight * 0.5f);
            Vector3 center = eye + fwd * (radius + spec.sensorRange * 0.5f);
            Vector3 half = new Vector3(spec.sensorWidth * 0.5f, spec.sensorHeight * 0.5f, spec.sensorRange * 0.5f);

            int n = Physics.OverlapBoxNonAlloc(center, half, _hits, transform.rotation, ~0, QueryTriggerInteraction.Ignore);

            // 依頼 v11 の 5 章：当たりが MaxHits を超えると切り捨てになり、
            // どれが落ちるかは物理の返す順番で決まる（＝順番に頼る所）。
            // 実際に超えているかを数えて summary.json に出す。
            // 超えていなければ「物理の返る順番」はずれの元から外せる
            if (n > MaxSeen) MaxSeen = n;
            if (n > MaxHits) { n = MaxHits; Overflows++; }

            for (int i = 0; i < n; i++)
            {
                Collider col = _hits[i];
                if (col == null) continue;

                PersonBody person = col.GetComponentInParent<PersonBody>();
                ServingRobotController other = null;
                Chair chair = null;

                if (person == null)
                {
                    other = col.GetComponentInParent<ServingRobotController>();
                    if (other != null && owner != null && other == owner) continue;   // 自分は見ない
                    if (other == null) chair = col.GetComponentInParent<Chair>();
                }

                if (person == null && other == null && chair == null) continue;   // 床・壁・卓は見ない

                r.blocked = true;
                if (person != null) r.sawPerson = true;
                if (other != null) r.sawRobot = true;
                if (chair != null) r.sawChair = true;

                // ロボットの外周からの距離
                Vector3 closest = col.ClosestPoint(eye);
                Vector3 flat = closest - transform.position;
                flat.y = 0f;
                float d = Mathf.Max(0f, Vector3.Dot(flat, fwd) - radius);
                if (d >= r.nearestDistance) continue;

                r.nearestDistance = d;
                r.nearestIsPerson = person != null;
                r.nearestIsRobot = other != null;
                r.nearestIsChair = chair != null;
                if (person != null)
                {
                    r.nearestPersonKind = person.kind;
                    r.nearestPersonSpeed = person.Speed;
                    r.nearestPersonStanding = person.IsStanding;
                    r.nearestName = person.name;
                }
                else if (other != null) r.nearestName = other.name;
                else if (chair != null) r.nearestName = chair.transform.parent != null
                    ? chair.transform.parent.name + "/" + chair.name : chair.name;
            }

            _last = r;
            return r;
        }

        void OnDrawGizmosSelected()
        {
            if (spec == null) return;
            Vector3 fwd = transform.forward;
            Vector3 center = transform.position
                             + Vector3.up * (spec.sensorHeight * 0.5f)
                             + fwd * (spec.Radius + spec.sensorRange * 0.5f);
            Gizmos.color = _last.blocked ? new Color(1f, 0.3f, 0.2f, 0.6f) : new Color(0.3f, 1f, 0.4f, 0.4f);
            Matrix4x4 old = Gizmos.matrix;
            Gizmos.matrix = Matrix4x4.TRS(center, transform.rotation, Vector3.one);
            Gizmos.DrawWireCube(Vector3.zero, new Vector3(spec.sensorWidth, spec.sensorHeight, spec.sensorRange));
            Gizmos.matrix = old;
        }
    }
}
