using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

namespace ServiceSim
{
    /// <summary>
    /// スタッフ1人。確率で仕事を選ぶのではなく、そのときに起きている仕事を
    /// 仕事板（ServiceOps）から優先度の順に取って片付ける。
    ///   優先0：立ち往生したロボットを動かす（v5 3章・サイトの決まりで「ほかの仕事より先」）
    ///   優先1：案内・料理を運ぶ・呼び出し・会計　／　優先2：片付け
    /// 手が空いているときはパントリーの前で待つ。
    ///
    /// この script は人だけを扱う。どの仕事が要るかは ServiceOps が決める。
    /// 数字は ServiceSpec と PeopleSpec の .asset に置く。
    /// </summary>
    [RequireComponent(typeof(NavMeshAgent))]
    public class StaffAgent : MonoBehaviour
    {
        /// <summary>行き先1つぶん</summary>
        struct Step
        {
            public Vector3 point;
            public float dwellSec;
            public bool handDish;   // ここで料理が届く
        }

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

        [Tooltip("仕事板")]
        public ServiceOps ops;

        [Header("いまの様子（読むだけ）")]
        [SerializeField] string _jobText = "待機";
        [SerializeField] int _step;
        [SerializeField] float _stepS;     // いまの行き先へ歩き始めてからの秒数

        NavMeshAgent _agent;
        PersonBody _body;             // 歩いた距離を数えるのに使う
        PersonRobotYield _yield;      // ロボットをよけている最中は「着いた」と見ない
        PeopleTargets _targets;
        StaffJob _job;
        readonly List<Step> _steps = new List<Step>();
        float _timer;
        bool _dwelling;
        bool _idleParked;
        Vector3 _pending;
        bool _needsDestination;


        /// <summary>いまの仕事（頭の上の字幕が読む）</summary>
        public StaffJob CurrentJob { get { return _job; } }
        public string JobText { get { return _jobText; } }
        public int Step_ { get { return _step; } }

        /// <summary>いま向かっている点（頭の上の字幕が読む）</summary>
        public Vector3 TargetPoint
        {
            get { return _step >= 0 && _step < _steps.Count ? _steps[_step].point : transform.position; }
        }

        void Awake()
        {
            _agent = GetComponent<NavMeshAgent>();
            _yield = GetComponent<PersonRobotYield>();
        }

        /// <summary>ロボットをよけている最中か（行き先がよけ場所にすり替わっている）</summary>
        bool Yielding
        {
            get
            {
                if (_yield == null) _yield = GetComponent<PersonRobotYield>();
                return _yield != null && _yield.Intervening;
            }
        }

        /// <summary>何人目のスタッフか（待つ点を1人に1つ割り当てるのに使う）</summary>
        public int staffIndex;

        /// <summary>手の空いたときに待つ点。無ければパントリーの前</summary>
        Vector3 StandbyPoint
        {
            get
            {
                if (_targets == null) return transform.position;
                int n = _targets.staffStandby.Count;
                if (n == 0) return _targets.pantry;
                return _targets.staffStandby[((staffIndex % n) + n) % n];
            }
        }

        public void Configure(PeopleSpec peopleSpec, ServiceSpec serviceSpec, ServiceOps serviceOps,
                              PeopleTargets targets, int seed, int index)
        {
            staffIndex = index;
            spec = peopleSpec;
            service = serviceSpec;
            ops = serviceOps;
            _targets = targets;

            if (_agent == null) _agent = GetComponent<NavMeshAgent>();
            ApplySpec();

            _job = null;
            _steps.Clear();
            _step = 0;
            _stepS = 0f;
            _dwelling = false;
            _idleParked = false;
            SetIgnoredRobot(null);
            _jobText = "待機";
        }

        void ApplySpec()
        {
            if (_agent == null || spec == null) return;
            _agent.radius = spec.personRadius;
            _agent.height = spec.personHeight;
            _agent.speed = spec.staffSpeed;
            _agent.acceleration = spec.acceleration;
            _agent.angularSpeed = spec.angularSpeedDegPerSec;
            _agent.stoppingDistance = spec.stoppingDistance;
            _agent.obstacleAvoidanceType = spec.avoidanceQuality;
            _agent.avoidancePriority = spec.avoidancePriority;
            _agent.autoRepath = true;
            // 人は人用の NavMesh（半径 0.2 m）を歩く。ロボットは 0.25 m のまま
            PeopleNav.ApplyAgentType(_agent);
            // スタッフはレジの内側（店員側）に入れる
            _agent.areaMask = NavMesh.AllAreas;
        }

        void Update()
        {
            if (_agent == null || spec == null || service == null || ops == null || _targets == null) return;

            // 行き先に着けないまま時間が過ぎたら、仕事を板に戻す（B1）。
            // NavMesh に乗れないときも時間が進むように、下で戻る前に数える
            if (_job != null && !_dwelling)
            {
                _stepS += Time.deltaTime;
                if (GiveUpIfLate()) return;
            }

            // 時間の使い方を残す（案内・料理・呼び出し・会計・片付け・パントリーで待つ）
            ops.AddStaffTime(_job != null ? JobKindName(_job.kind) : "待機", Time.deltaTime);

            // 歩いた距離を残す（walk_km_per_staff）。「移した」ぶんの跳びは ServiceOps 側で落とす
            if (_body == null) _body = GetComponent<PersonBody>();
            if (_body != null) ops.AddStaffWalk(_body.LastStepM);

            if (!PeopleNav.EnsureReady(transform, _agent)) return;
            if (_needsDestination && !PeopleNav.Go(_agent, _pending)) return;
            _needsDestination = false;

            if (_job == null) { Idle(); return; }

            if (_dwelling)
            {
                _timer += Time.deltaTime;
                if (_timer < _steps[_step].dwellSec) return;
                _dwelling = false;
                Deliver(_steps[_step]);
                _step++;
                if (_step >= _steps.Count) { Done(); return; }
                Go(_steps[_step].point);
                return;
            }

            // ロボットをよけている最中は、行き先がよけ場所にすり替わっているので
            // 「着いた」と見てはいけない
            if (Yielding) return;

            // 立ち往生を動かしに行くときは、相手が動いたら行き先を追いかけ、
            // 着いたかどうかもその点との距離で見る（v5 3章）
            if (IsRescueStep()) { if (!UpdateRescueStep()) return; }
            else
            {
                if (_agent.pathPending) return;
                if (_agent.remainingDistance > _agent.stoppingDistance + 0.05f) return;
            }

            if (_steps[_step].dwellSec > 0f) { _dwelling = true; _timer = 0f; return; }
            Deliver(_steps[_step]);
            _step++;
            if (_step >= _steps.Count) { Done(); return; }
            Go(_steps[_step].point);
        }

        /// <summary>手が空いているとき。次の仕事を取り、無ければパントリーの前で待つ</summary>
        void Idle()
        {
            StaffJob job = ops.TakeNextJob();
            if (job == null)
            {
                if (!_idleParked)
                {
                    _idleParked = true;
                    _jobText = "待機";
                    Go(StandbyPoint);
                }
                return;
            }
            _idleParked = false;
            Begin(job);
        }

        /// <summary>仕事の名前（時間の使い方の記録に使う）</summary>
        public static string JobKindName(StaffJobKind kind)
        {
            switch (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 "そのほか";
        }

        void Begin(StaffJob job)
        {
            _job = job;
            _steps.Clear();
            _step = 0;
            _dwelling = false;
            _timer = 0f;

            switch (job.kind)
            {
                case StaffJobKind.Guide:
                    _jobText = "案内";
                    Add(_targets.waiting, service.guideSec, false);
                    Add(TablePoint(job), 0f, false);
                    break;

                case StaffJobKind.Serve:
                    _jobText = "料理を運ぶ";
                    Add(_targets.foodPass, 0f, false);
                    Add(TablePoint(job), service.staffPlaceSec, true);
                    Add(_targets.foodPass, 0f, false);
                    break;

                case StaffJobKind.Call:
                    _jobText = "呼び出し";
                    Add(TablePoint(job), service.callSec, false);
                    break;

                case StaffJobKind.Pay:
                    // スタッフはレジの内側（客と反対側。太い通路からだけ入れる）に立つ
                    _jobText = "会計";
                    Add(_targets.registerBack, service.paySec, false);
                    break;

                case StaffJobKind.Clear:
                    // 皿をまとめ、返却口へ運び、戻って拭いて、もう一度運ぶ（bus_trips ぶん）
                    _jobText = "片付け";
                    Add(TablePoint(job), service.clearSec, false);
                    int trips = Mathf.Max(1, service.busTrips);
                    for (int i = 0; i < trips; i++)
                    {
                        Add(_targets.dishReturn, 0f, false);
                        if (i < trips - 1) Add(TablePoint(job), 0f, false);
                    }
                    Add(TablePoint(job), service.wipeSec, false);
                    break;

                case StaffJobKind.Rescue:
                    // 動けなくなったロボットの手前 0.8 m まで歩いて行き、rescue_s のあいだ動かす。
                    // そのあいだ、この台についてはよけない（よけると近づけない）
                    _jobText = "立ち往生を動かす";
                    SetIgnoredRobot(job.robot);
                    Add(RescueStandPoint(job.robot), service.rescueSec, false);
                    break;
            }

            if (_steps.Count == 0) { Done(); return; }
            Go(_steps[0].point);
        }

        // ---------------- 立ち往生を動かしに行く（v5 3章）----------------

        /// <summary>
        /// 動けなくなったロボットの **手前** に立つ点（Rescue の行き先）。
        ///
        /// ロボットの位置そのものを行き先にすると、着けません。
        /// 人がロボットをよける処理が、ロボットの中心から
        ///   人の半径 0.2 ＋ ロボットの半径 0.25 ＋ robotKeepClearM 0.3 ＝ 0.75 m
        /// まで押しのけるのに、着いたとみなすのは 0.35 m（stoppingDistance ＋ 0.05）だからです。
        /// そこで、ロボットから **自分がいる側へ 0.8 m** 離れた点を行き先にします。
        /// この台についてはよけないので（ignoreRobot）、押しのけずにそこまで行けます。
        /// </summary>
        Vector3 RescueStandPoint(ServingRobotController robot)
        {
            if (robot == null) return transform.position;

            Vector3 r = robot.transform.position; r.y = 0f;
            Vector3 me = transform.position; me.y = 0f;
            Vector3 d = me - r;
            if (d.sqrMagnitude < 0.0001f) d = transform.forward;   // 重なっているときは向いている方
            d.y = 0f;
            if (d.sqrMagnitude < 0.0001f) d = Vector3.forward;
            return r + d.normalized * RescueStandM;
        }

        /// <summary>ロボットの中心からこれだけ手前に立って動かす（m）</summary>
        const float RescueStandM = 0.8f;

        /// <summary>
        /// 「この台はよけない」を入れ替える（v5 3章）。
        /// 立ち往生の仕事を始めたらその台、やめたり終わったら null（ほかの台はいつもよける）。
        /// </summary>
        void SetIgnoredRobot(ServingRobotController robot)
        {
            if (_yield == null) _yield = GetComponent<PersonRobotYield>();
            if (_yield != null) _yield.ignoreRobot = robot;
        }

        /// <summary>行き先を引き直す目安（m）。これ以上ずれたら引き直す</summary>
        const float RescueRetargetM = 0.25f;

        /// <summary>いまの行き先が「立ち往生を動かす」か</summary>
        bool IsRescueStep()
        {
            return _job != null && _job.kind == StaffJobKind.Rescue && _job.robot != null
                   && _step >= 0 && _step < _steps.Count;
        }

        /// <summary>
        /// 立ち往生の行き先を、相手の位置に合わせて追いかける。
        /// 戻り値 true なら、その点に着いた（このあと rescue_s のあいだ動かす）。
        /// </summary>
        bool UpdateRescueStep()
        {
            Vector3 want = RescueStandPoint(_job.robot);

            // 相手が動いたら行き先を引き直す（引き直しすぎると経路が落ち着かないので目安を置く）
            Step s = _steps[_step];
            if ((Flat(s.point) - Flat(want)).magnitude > RescueRetargetM)
            {
                s.point = want;
                _steps[_step] = s;
                _pending = want;
                _needsDestination = !PeopleNav.Go(_agent, want);
            }

            // 着いたかどうかは、その点との距離で見る（経路の残りは引き直しの直後に当てにならない）
            float gap = (Flat(transform.position) - Flat(_steps[_step].point)).magnitude;
            return gap <= _agent.stoppingDistance + 0.05f;
        }

        static Vector3 Flat(Vector3 v) { v.y = 0f; return v; }

        Vector3 TablePoint(StaffJob job)
        {
            if (ops == null || job.tableIndex < 0 || job.tableIndex >= ops.Tables.Count)
                return _targets.foodPass;
            return ops.Tables[job.tableIndex].deliveryPoint;
        }

        void Add(Vector3 point, float dwellSec, bool handDish)
        {
            Step s = new Step();
            s.point = point;
            s.dwellSec = dwellSec;
            s.handDish = handDish;
            _steps.Add(s);
        }

        bool _wasInRegisterBack;

        /// <summary>
        /// 会計でないスタッフが店員側（レジの内側）に入っていないかを見る。
        /// 店員側は1人ぶんなので、会計の仕事を持っていないスタッフは入らない。
        /// </summary>
        void WatchRegisterBack()
        {
            if (ops == null || ops.store == null) { _wasInRegisterBack = false; return; }

            PlanRect back = ops.store.RegisterBackRect;
            bool paying = _job != null && _job.kind == StaffJobKind.Pay;
            bool inside = !back.IsEmpty && back.Contains(transform.position);

            if (inside && !paying)
            {
                ops.AddRegisterBackIntruder(Time.deltaTime, !_wasInRegisterBack);
                _wasInRegisterBack = true;
                return;
            }
            _wasInRegisterBack = inside && !paying;
        }

        /// <summary>
        /// 会計を済ませたとき、店員側（レジの内側）に立っていたかを残す。
        /// 0 にならなければ、スタッフが客側に立ってしまっている。
        /// </summary>
        void RecordPayPlace()
        {
            if (ops == null || ops.store == null || _targets == null) return;

            PlanRect back = ops.store.RegisterBackRect;
            bool atBack = !back.IsEmpty && back.Expand(0.1f).Contains(transform.position);

            // 立つ点と、実際に立った位置の距離
            Vector3 a = transform.position; a.y = 0f;
            Vector3 b = _targets.registerBack; b.y = 0f;
            ops.AddRegisterPay(atBack, Vector3.Distance(a, b));
        }

        void Deliver(Step s)
        {
            // 会計を済ませた瞬間に、店員側に立っていたかを残す
            if (_job != null && _job.kind == StaffJobKind.Pay) RecordPayPlace();

            // rescue_s のあいだ動かし終わった。ロボットは止まっていた所から走りを続ける。
            // robot が null なのは、待っているあいだにその往復が打ち切られたとき（何もしない）
            if (_job != null && _job.kind == StaffJobKind.Rescue)
            {
                if (_job.robot != null) _job.robot.Rescued();
                return;
            }

            if (!s.handDish || _job == null || _job.dish == null) return;
            ops.DishArrived(_job.dish, false);
        }

        void Done()
        {
            StaffJob job = _job;
            _job = null;
            _steps.Clear();
            _step = 0;
            _stepS = 0f;
            _jobText = "待機";
            SetIgnoredRobot(null);              // よけない台を元に戻す（v5 3章）
            if (job != null) { job.tries = 0; ops.FinishJob(job); }
        }

        void Go(Vector3 point)
        {
            _pending = point;
            _needsDestination = true;
            _stepS = 0f;
            if (PeopleNav.EnsureReady(transform, _agent) && PeopleNav.Go(_agent, point))
                _needsDestination = false;
        }

        // ---------------- あきらめて、やり直す（B1）----------------

        /// <summary>
        /// 行き先に着けないまま spec.staffGiveUpSec が過ぎたとき。
        ///   ・まだ staffGiveUpMaxTries 回未満なら、仕事を板に戻して別の仕事を取る
        ///   ・その回数に達したら、その場所へ移して仕事を続ける（店が通れないしるし）
        /// </summary>
        bool GiveUpIfLate()
        {
            if (spec.staffGiveUpSec <= 0f || _stepS < spec.staffGiveUpSec) return false;

            StaffJob job = _job;
            Vector3 to = _step >= 0 && _step < _steps.Count ? _steps[_step].point : transform.position;

            // あきらめた回数は **仕事ごと** に数える（v5 3章）。
            // 人ごとに数えていたときは、仕事が人から人へ回ると誰も上限に届かず、
            // 「3回で移す」逃がし弁が働かないまま回り続けた
            int tries = ++job.tries;

            int max = Mathf.Max(1, spec.staffGiveUpMaxTries);
            if (tries >= max)
            {
                // 何度やっても着けない。その場所へ移して仕事を済ませる
                ops.AddGiveUp("スタッフ", _jobText, job != null ? job.tableId : 0,
                              to, transform.position, _stepS, "移した");
                job.tries = 0;
                PlaceAt(to);
                _stepS = 0f;
                return false;   // 移したので、このコマの続きで着いた扱いにする
            }

            // 仕事を板に戻して、別の仕事を取る
            ops.AddGiveUp("スタッフ", _jobText, job != null ? job.tableId : 0,
                          to, transform.position, _stepS, "あきらめた");
            _job = null;
            _steps.Clear();
            _step = 0;
            _dwelling = false;
            _stepS = 0f;
            _jobText = "待機";
            SetIgnoredRobot(null);              // よけない台を元に戻す（v5 3章）
            ops.AbandonJob(job);
            return true;
        }

        /// <summary>体をその場所に移す（どうしても着けないとき）</summary>
        void PlaceAt(Vector3 p)
        {
            transform.position = p;
            if (Application.isPlaying && _agent != null && _agent.enabled && _agent.isOnNavMesh)
                _agent.Warp(p);
        }
    }
}
