using UnityEngine;
using UnityEngine.AI;

namespace ServiceSim
{
    /// <summary>
    /// 人の NavMeshAgent を、安全に使えるようにする小さな道具。
    ///
    /// prefab では NavMeshAgent を切ってある。置いた直後に有効にすると
    /// 「NavMesh に近くないので agent を作れません」の警告が出たり、
    /// NavMesh に乗る前に SetDestination を呼んで
    /// 「can only be called on an active agent that has been placed on a NavMesh」
    /// のエラーが出るため、ここを通してから使う。
    ///
    /// この script は人だけを扱う。
    /// </summary>
    public static class PeopleNav
    {
        /// <summary>NavMesh の上を探す距離（m）</summary>
        const float SampleRadius = 2.0f;

        /// <summary>
        /// 人が歩く NavMesh の種類（agentTypeID）。NavMeshBaker が焼いたあとに入れる。
        /// 人は半径 0.2 m の面、ロボットは 0.25 m の面を歩く。
        /// 焼けていなければ 0（ロボットと同じ面）のまま。
        /// </summary>
        public static int PeopleAgentTypeId { get; private set; }

        /// <summary>人用の面を探すための絞り込み</summary>
        public static NavMeshQueryFilter Filter
        {
            get
            {
                NavMeshQueryFilter f = new NavMeshQueryFilter();
                f.agentTypeID = PeopleAgentTypeId;
                f.areaMask = NavMesh.AllAreas;
                return f;
            }
        }

        /// <summary>焼いたあとに NavMeshBaker が呼ぶ</summary>
        public static void SetPeopleAgentType(int agentTypeId)
        {
            PeopleAgentTypeId = Mathf.Max(0, agentTypeId);
        }

        /// <summary>
        /// 客が歩ける区画（レジの内側＝店員側を外す）。
        /// スタッフは NavMesh.AllAreas のまま。
        /// </summary>
        public static int GuestAreaMask(PeopleSpec spec)
        {
            if (spec == null || spec.registerBackArea <= 2) return NavMesh.AllAreas;
            return NavMesh.AllAreas & ~(1 << spec.registerBackArea);
        }

        /// <summary>その agent を人用の面で歩かせる</summary>
        public static void ApplyAgentType(NavMeshAgent agent)
        {
            if (agent == null) return;
            if (agent.agentTypeID != PeopleAgentTypeId) agent.agentTypeID = PeopleAgentTypeId;
        }

        /// <summary>
        /// agent を使える状態にする。
        /// 編集モード（Play していないとき）は何もせず false を返す。
        /// </summary>
        public static bool EnsureReady(Transform tr, NavMeshAgent agent)
        {
            if (!Application.isPlaying) return false;
            if (agent == null || tr == null) return false;

            if (!agent.enabled)
            {
                // 有効にする前に NavMesh の上へ移す（警告を出さないため）
                NavMeshHit hit;
                if (NavMesh.SamplePosition(tr.position, out hit, SampleRadius, Filter))
                    tr.position = hit.position;
                else
                    return false;   // 近くに NavMesh が無いので、まだ有効にしない
                ApplyAgentType(agent);
                agent.enabled = true;
            }

            if (!agent.isActiveAndEnabled) return false;

            if (!agent.isOnNavMesh)
            {
                NavMeshHit hit;
                if (NavMesh.SamplePosition(tr.position, out hit, SampleRadius, Filter))
                    agent.Warp(hit.position);
            }
            return agent.isOnNavMesh;
        }

        /// <summary>
        /// 行き先を指示する。指示できたら true。
        /// EnsureReady が true を返したあとだけ呼ぶ。
        /// </summary>
        /// <summary>
        /// 試し2（依頼 v11 の 5 章）：人の道を **同期** で引く。
        ///
        /// SetDestination は非同期なので、道が引けるまで agent.pathPending が true のままで、
        /// 何コマかかるかは Unity の都合で変わる。
        /// その間「着いたかの判定」と「よけるかの判断」が打ち切られるので、
        /// 同じ種でも数コマずれる（確かめ B で出たずれ）。
        ///
        /// true にすると NavMesh.CalculatePath（同期）で引いて SetPath で渡すので、
        /// pathPending が true になる場面が無くなる。
        ///
        /// ★ 調べるためだけの切り替え。ExperimentRunner が回し始めに
        ///   ExperimentPlan.detCheckSyncPeoplePaths から入れ、回し終わりに false に戻す
        /// </summary>
        public static bool SyncPaths;

        /// <summary>同期で引くときに使い回す道の入れ物（毎回 new しない）</summary>
        static readonly NavMeshPath _path = new NavMeshPath();

        /// <summary>
        /// 行き先を指示する。指示できたら true。
        /// EnsureReady が true を返したあとだけ呼ぶ。
        /// </summary>
        public static bool Go(NavMeshAgent agent, Vector3 point)
        {
            if (agent == null || !agent.isActiveAndEnabled || !agent.isOnNavMesh) return false;

            NavMeshHit hit;
            Vector3 to = NavMesh.SamplePosition(point, out hit, 1.5f, Filter) ? hit.position : point;

            if (!SyncPaths) return agent.SetDestination(to);

            // 同期で引く。引けなければ、これまでどおり非同期に任せる
            // （引けない所へ行こうとしている場面の動きを変えないため）
            NavMeshQueryFilter f = new NavMeshQueryFilter();
            f.agentTypeID = agent.agentTypeID;
            f.areaMask = agent.areaMask;
            if (!NavMesh.CalculatePath(agent.nextPosition, to, f, _path) ||
                _path.corners.Length == 0)
                return agent.SetDestination(to);
            return agent.SetPath(_path);
        }
    }
}
