using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace ServiceSim.EditorTools
{
    /// <summary>
    /// 真上の画面の隅に出す「色の意味」の凡例（見た目だけ）。
    ///
    /// 字幕（ServiceSimLabels）は Scene ビューにだけ描くのでカメラに写らない。
    /// 凡例はカメラに写す必要があるので、床の上に置く板と文字（TextMesh）で作る。
    /// 当たりは付けないので NavMesh には焼かれない。
    ///
    /// Unity の NavMesh の表示（通行者の中心が行ける場所）の切り替えもここに置く。
    /// 見る人には「ロボットはこの細い帯しか通れない」と読めてしまうので、初期値は消す。
    /// </summary>
    public static class ServiceSimLegend
    {
        const string RootName = "FloorPaint_Legend";
        const string MenuNav = "ServiceSim/Show NavMesh（中心の範囲・確認用）";
        const string NavKey = "ServiceSim.ShowNavMesh";

        /// <summary>
        /// 真上のカメラの左上に凡例を作る。カメラの画角から位置を決めるので、
        /// 店の大きさが変わってもはみ出さない。
        /// </summary>
        public static void Build(Transform parent, Camera topCam, float aspect,
                                 Material robotMat, Material peopleMat)
        {
            Clear(parent);
            if (parent == null || topCam == null || !topCam.orthographic) return;

            float halfZ = topCam.orthographicSize;
            float halfX = halfZ * Mathf.Max(0.01f, aspect);
            Vector3 c = topCam.transform.position;

            // 画面の左上から少し内側
            float x = c.x - halfX + 0.35f;
            float z = c.z + halfZ - 0.55f;
            float y = 0.05f;      // 床の塗りより上

            GameObject root = new GameObject(RootName);
            root.transform.SetParent(parent, false);

            Row(root.transform, new Vector3(x, y, z), robotMat, "ロボットがふだん走る床");
            Row(root.transform, new Vector3(x, y, z - 0.75f), peopleMat, "細い通路（狭い道で入る）");
        }

        static void Row(Transform parent, Vector3 at, Material mat, string text)
        {
            // 色の板
            GameObject chip = GameObject.CreatePrimitive(PrimitiveType.Quad);
            chip.name = "FloorPaint_LegendChip";
            Object.DestroyImmediate(chip.GetComponent<Collider>());
            chip.transform.SetParent(parent, false);
            chip.transform.position = at;
            chip.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
            chip.transform.localScale = new Vector3(0.55f, 0.4f, 1f);
            MeshRenderer mr = chip.GetComponent<MeshRenderer>();
            if (mat != null) mr.sharedMaterial = mat;
            mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
            mr.receiveShadows = false;

            // 文字
            GameObject label = new GameObject("FloorPaint_LegendText");
            label.transform.SetParent(parent, false);
            label.transform.position = at + new Vector3(0.45f, 0f, 0f);
            label.transform.rotation = Quaternion.Euler(90f, 0f, 0f);

            TextMesh tm = label.AddComponent<TextMesh>();
            tm.text = text;
            tm.anchor = TextAnchor.MiddleLeft;
            tm.alignment = TextAlignment.Left;
            tm.characterSize = 0.08f;
            tm.fontSize = 56;
            tm.color = new Color(0.08f, 0.09f, 0.12f);

            // フォントとマテリアルは必ず入れる（入れないと何も描かれない）。
            // このプロジェクトは Built-in（URP の .asset は script 参照が切れていて
            // currentRenderPipeline は null）なので、フォントが持つマテリアル
            // （GUI/Text Shader）がそのまま使える。念のため壊れていたら探し直す
            Font font = JapaneseFont();
            if (font == null) font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
            if (font == null) font = Resources.GetBuiltinResource<Font>("Arial.ttf");
            if (font != null) tm.font = font;

            MeshRenderer lr = label.GetComponent<MeshRenderer>();
            if (lr == null) return;

            Material textMat = font != null ? font.material : null;
            if (textMat == null || textMat.shader == null ||
                textMat.shader.name == ServiceSimRender.ErrorShaderName)
            {
                Shader sh = Shader.Find("GUI/Text Shader");
                if (sh != null && font != null)
                {
                    textMat = new Material(sh);
                    textMat.mainTexture = font.material != null ? font.material.mainTexture : null;
                }
            }
            if (textMat != null) lr.sharedMaterial = textMat;
        }

        static Font _font;
        static bool _fontTried;

        /// <summary>
        /// 日本語が出るフォントを探す。組み込みの Arial には日本語が無いので、
        /// OS のフォントから作る。見つからなければ組み込みのまま（文字が□になる）。
        /// </summary>
        static Font JapaneseFont()
        {
            if (_fontTried) return _font;
            _fontTried = true;

            string[] names =
            {
                "Hiragino Sans", "HiraginoSans-W3", "Hiragino Kaku Gothic ProN",
                "Yu Gothic", "YuGothic", "Noto Sans CJK JP", "Arial Unicode MS"
            };
            try { _font = Font.CreateDynamicFontFromOSFont(names, 56); }
            catch { _font = null; }

            if (_font == null)
                Debug.LogWarning("[ServiceSim] 凡例に使える日本語フォントが見つかりませんでした。" +
                                 "文字が□になります（ServiceSimLegend.JapaneseFont の名前を増やしてください）");
            return _font;
        }

        public static void Clear(Transform parent)
        {
            if (parent == null) return;
            for (int i = parent.childCount - 1; i >= 0; i--)
            {
                Transform t = parent.GetChild(i);
                if (t.name == RootName) Object.DestroyImmediate(t.gameObject);
            }
        }

        // ---------------- Unity の NavMesh の表示（確認用）----------------

        /// <summary>Unity の NavMesh の表示（通行者の中心が行ける場所）を出すか。初期値は消す</summary>
        public static bool ShowNavMesh
        {
            get { return EditorPrefs.GetBool(NavKey, false); }
            set { EditorPrefs.SetBool(NavKey, value); Apply(); }
        }

        [MenuItem(MenuNav, false, 106)]
        static void Toggle()
        {
            ShowNavMesh = !ShowNavMesh;
            Debug.Log("[ServiceSim] Unity の NavMesh の表示（中心の範囲）: " +
                      (ShowNavMesh ? "出す" : "消す") +
                      "\n  これは「通行者の中心が行ける場所」です。" +
                      "人に見せる絵の床の塗り分け（ロボットの体が入れる床）とは別物です");
        }

        [MenuItem(MenuNav, true)]
        static bool ToggleValidate()
        {
            Menu.SetChecked(MenuNav, ShowNavMesh);
            return true;
        }

        [InitializeOnLoadMethod]
        static void OnLoad()
        {
            Apply();
            EditorApplication.playModeStateChanged -= OnPlayMode;
            EditorApplication.playModeStateChanged += OnPlayMode;
        }

        /// <summary>
        /// 再生中は、Unity の NavMesh の表示（中心の範囲）を必ず消す。
        /// 床の塗り分け（本体が行ける範囲）と重なって、卓のまわりに灰色の帯と
        /// 三角形の線が出て見えてしまうため。Play を抜けたら設定どおりに戻す。
        /// </summary>
        static void OnPlayMode(PlayModeStateChange state)
        {
            if (state == PlayModeStateChange.EnteredPlayMode) Set(false);
            else if (state == PlayModeStateChange.EnteredEditMode) Apply();
        }

        /// <summary>
        /// Unity の NavMesh の表示を切り替える。
        /// API の名前が版で変わるので、反射で探して、無ければ何もしない。
        /// </summary>
        static void Apply() { Set(ShowNavMesh); }

        static void Set(bool on)
        {
            try
            {
                System.Type t = System.Type.GetType(
                    "UnityEditor.AI.NavMeshVisualizationSettings, UnityEditor");
                if (t == null) return;
                PropertyInfo p = t.GetProperty("showNavigation",
                    BindingFlags.Public | BindingFlags.Static);
                if (p == null) return;
                p.SetValue(null, on ? 1 : 0, null);
                SceneView.RepaintAll();
            }
            catch
            {
                // 版によって無いことがある。表示の切り替えだけなので、黙って見送る
            }
        }
    }
}
