using System.Linq;
using UnityEngine;

namespace CatsUdonTools.Editor
{
    /// <summary>
    /// 入稿対象の GameObject から、エンコーダーが期待する CatsUdonExporter 階層（Directional Light + 対象の複製）を
    /// シーン上に一時的に組み立てる。エディタ時のみ動作し、プレイヤービルドには含まれない
    /// </summary>
    internal static class CatsUdonExporterBuilder
    {
        /// <summary>入稿 Prefab のルート名</summary>
        internal const string RootName = "CatsUdonExporter";

        /// <summary>入稿に含めない参考表示をまとめる GameObject 名。この名前の GameObject はどこにあっても除外される</summary>
        internal const string GuidesName = "_IgnoreCatsUdonGuides";

        /// <summary>Unity 標準のタグ。このタグが付いた GameObject もどこにあっても除外される</summary>
        internal const string EditorOnlyTag = "EditorOnly";

        private const string DirectionalLightName = "Directional Light";
        private const float LightPitchDegrees = 50f;
        private const float LightIntensity = 1f;

        /// <summary>
        /// CatsUdonExporter ルートを組み立てて返す。
        /// exportTarget が CatsUdonExporter（テンプレートのルート）ならその複製を、それ以外なら新しいルートの子に Light と複製を置く。
        /// 返したルートは呼び出し側が DestroyImmediate する責任を持つ
        /// </summary>
        internal static GameObject Build(GameObject exportTarget)
        {
            var root = exportTarget.name == RootName ? BuildFromTemplateRoot(exportTarget) : BuildWrappingRoot(exportTarget);
            DestroyExcludedObjects(root);
            EnsureDirectionalLight(root);
            return root;
        }

        /// <summary>
        /// 指定した親の下に、エンコーダーが展示ライティングとして読む Directional Light を作って返す。
        /// テンプレート生成とビルドで同じ仕様を共有する
        /// </summary>
        internal static GameObject CreateDirectionalLight(Transform parent)
        {
            var lightObject = new GameObject(DirectionalLightName);
            lightObject.transform.SetParent(parent, false);
            lightObject.transform.localRotation = Quaternion.Euler(LightPitchDegrees, 0f, 0f);

            var light = lightObject.AddComponent<Light>();
            light.type = LightType.Directional;
            light.color = Color.white;
            light.intensity = LightIntensity;
            light.shadows = LightShadows.None;
            return lightObject;
        }

        private static GameObject BuildFromTemplateRoot(GameObject templateRoot)
        {
            // 位置・回転を触らないのは、エンコーダーが root のローカル空間基準で扱うため root 自体の位置は結果に影響しないから
            var root = Object.Instantiate(templateRoot);
            root.name = RootName;
            return root;
        }

        // 除外の後に確認するのは、EditorOnly の Light が消えて展示ライトが無くなる場合も補うため。
        // 展示ライトとみなすのは root 直下の Light だけ。アバター内のギミック用ライト（演出用の黒いライト等）があっても
        // 展示ライトにはならないので、直下に無ければ作る。エンコーダーは GetComponentInChildren で最初の Light を拾うため先頭に置く
        private static void EnsureDirectionalLight(GameObject root)
        {
            foreach (Transform child in root.transform)
            {
                if (child.GetComponent<Light>() != null)
                {
                    child.SetSiblingIndex(0);
                    return;
                }
            }

            CreateDirectionalLight(root.transform).transform.SetSiblingIndex(0);
        }

        private static GameObject BuildWrappingRoot(GameObject exportTarget)
        {
            var root = new GameObject(RootName);
            root.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
            root.transform.localScale = Vector3.one;

            // エンコーダーは root 配下の最初の Light を展示ライティングとして焼き、無いと既定値（強さ 0.6・逆向き）になるため常に付ける。
            // 仕様は「完全白色・影無し・正面から 50° 下向き」。
            // 兄弟順を先頭にするのは、アバター側に Light があっても GetComponentInChildren で展示ライトを優先して拾わせるため
            CreateDirectionalLight(root.transform);
            CreateTargetCopy(root.transform, exportTarget);
            return root;
        }

        // 参考表示やエディタ専用物を入稿物に混ぜないため。列挙中に破棄しないよう先に集める
        private static void DestroyExcludedObjects(GameObject root)
        {
            var excludedObjects = root.GetComponentsInChildren<Transform>(true)
                .Where(child => child != root.transform && IsExcluded(child.gameObject))
                .ToList();

            foreach (var excludedObject in excludedObjects)
            {
                // 入れ子の除外対象は親の破棄で既に消えているため
                if (excludedObject != null)
                {
                    Object.DestroyImmediate(excludedObject.gameObject);
                }
            }
        }

        /// <summary>Export から除外する GameObject か。名前が _IgnoreCatsUdonGuides か、EditorOnly タグ付き</summary>
        internal static bool IsExcluded(GameObject target)
        {
            return target.name == GuidesName || target.CompareTag(EditorOnlyTag);
        }

        private static void CreateTargetCopy(Transform parent, GameObject exportTarget)
        {
            var targetCopy = Object.Instantiate(exportTarget);
            targetCopy.name = exportTarget.name;

            // ワールド姿勢を保つのは、原点基準でポーズ・位置を決めた配置をそのまま入稿するため
            targetCopy.transform.SetParent(parent, true);
        }
    }
}
