#if AR_VRCSDK3_AVATARS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Avatars.ScriptableObjects;
using VRC.SDKBase;
using L10n = AvatarRefinery.Editor.Core.L10n;

namespace AvatarRefinery.Editor.Core.Rules
{
    /// <summary>VRC Avatar Descriptor の有無・視点位置・口パク・Eye Look・Expressions・Write Defaults</summary>
    public sealed class VrcDescriptorRule : IRule
    {
        public string Id => "vrc.descriptor_missing";
        public bool CanRun(RuleContext ctx) => ctx.HasVrcSdk;

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var d = ctx.AvatarRoot.GetComponent<VRCAvatarDescriptor>();
            if (d == null)
            {
                yield return new Issue("vrc.descriptor_missing", Severity.Blocker)
                {
                    Detail = L10n.T("ルートに VRC Avatar Descriptor がありません", "The avatar root has no VRC Avatar Descriptor"),
                }.WithTarget(ctx.AvatarRoot, ctx.Root);
                yield break;
            }

            // --- 視点位置
            var anim = ctx.AvatarRoot.GetComponent<Animator>();
            var head = anim != null && anim.isHuman ? anim.GetBoneTransform(HumanBodyBones.Head) : null;
            var vp = d.ViewPosition;
            if (head != null)
            {
                var headLocal = ctx.Root.InverseTransformPoint(head.position);
                float dy = Mathf.Abs(vp.y - headLocal.y);
                float dxz = Vector2.Distance(new Vector2(vp.x, vp.z), new Vector2(headLocal.x, headLocal.z));
                float scale = Mathf.Max(0.05f, headLocal.y); // 身長比で許容
                if (vp.y <= 0.01f || dy > scale * 0.3f || dxz > scale * 0.3f)
                {
                    yield return new Issue("vrc.view_position", Severity.Broken)
                    {
                        Detail = L10n.IsEnglish
                            ? $"View Position = ({vp.x:0.00}, {vp.y:0.00}, {vp.z:0.00}) / Head bone = ({headLocal.x:0.00}, {headLocal.y:0.00}, {headLocal.z:0.00}) (vertical difference {dy * 100:0} cm)"
                            : $"View Position = ({vp.x:0.00}, {vp.y:0.00}, {vp.z:0.00}) / 頭ボーンの位置 = ({headLocal.x:0.00}, {headLocal.y:0.00}, {headLocal.z:0.00})(高さの差 {dy * 100:0} cm)",
                    }.WithMetric("viewY", vp.y).WithMetric("headY", headLocal.y).WithTarget(d, ctx.Root);
                }
            }
            else if (vp == Vector3.zero)
            {
                yield return new Issue("vrc.view_position", Severity.Broken) { Detail = L10n.T("View Position が原点(0,0,0)のままです", "View Position is still at the origin (0, 0, 0)") }.WithTarget(d, ctx.Root);
            }

            // --- 口パク(Viseme)
            if (d.lipSync == VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape)
            {
                var smr = d.VisemeSkinnedMesh;
                if (smr == null || smr.sharedMesh == null)
                {
                    yield return new Issue("vrc.viseme_broken", Severity.Broken)
                    {
                        Detail = L10n.T("LipSync = Viseme Blend Shape ですが Face Mesh が未設定です", "LipSync is set to Viseme Blend Shape, but Face Mesh is not assigned"),
                    }.WithTarget(d, ctx.Root);
                }
                else
                {
                    var names = new HashSet<string>();
                    for (int i = 0; i < smr.sharedMesh.blendShapeCount; i++) names.Add(smr.sharedMesh.GetBlendShapeName(i));
                    var missing = (d.VisemeBlendShapes ?? new string[0]).Where(n => !string.IsNullOrEmpty(n) && !names.Contains(n)).ToList();
                    int set = (d.VisemeBlendShapes ?? new string[0]).Count(n => !string.IsNullOrEmpty(n));
                    if (missing.Count > 0)
                    {
                        yield return new Issue("vrc.viseme_broken", Severity.Broken)
                        {
                            Detail = (L10n.IsEnglish ? $"Face Mesh “{smr.name}” is missing {missing.Count} configured BlendShapes: " : $"Face Mesh「{smr.name}」に存在しない BlendShape が {missing.Count} 個: ") + string.Join(", ", missing.Take(6)),
                        }.WithMetric("missing", missing.Count).WithTarget(smr, ctx.Root);
                    }
                    else if (set == 0)
                    {
                        yield return new Issue("vrc.viseme_broken", Severity.Broken)
                        {
                            Detail = L10n.T("Face Mesh は設定済みですが、Viseme の BlendShape が 1 つも割り当てられていません(Auto Detect! を試してください)", "Face Mesh is assigned, but no Viseme BlendShapes are configured. Try Auto Detect!"),
                        }.WithTarget(d, ctx.Root);
                    }
                }
            }
            else if (d.lipSync == VRC_AvatarDescriptor.LipSyncStyle.Default)
            {
                // 未設定。VRM直後など。口パクの説明として Info で知らせる
                yield return new Issue("vrc.viseme_broken", Severity.Info)
                {
                    TitleOverride = L10n.T("口パク(リップシンク)が未設定です", "LipSync is not configured"),
                    Detail = L10n.T("LipSync が未設定(Default)です。Viseme Blend Shape を選び Face Mesh を設定すると口パクが動きます", "LipSync is set to Default. Choose Viseme Blend Shape and assign Face Mesh to enable lip sync."),
                }.WithTarget(d, ctx.Root);
            }

            // --- Eye Look(フィールド名はSDKバージョン差の可能性があるためリフレクション)
            foreach (var i in CheckEyeLook(d, ctx)) yield return i;

            // --- Expressions / Write Defaults は「ビルド後の状態」を優先して判定する
            //     (Modular Avatar 等はビルド時にパラメータ・メニュー・レイヤーを追加するため、シーン上だけ見ると過小/過大評価になる)
            var dBaked = ctx.BakedRoot != null ? ctx.BakedRoot.GetComponent<VRCAvatarDescriptor>() : null;
            var dEval = dBaked != null ? dBaked : d;
            string bakedNote = dBaked != null ? L10n.T("(ビルド後の状態で判定)", " (checked after NDMF build)") : "";

            if (dEval.customExpressions)
            {
                var menu = dEval.expressionsMenu; var prm = dEval.expressionParameters;
                if ((menu == null) != (prm == null) || (menu == null && prm == null))
                {
                    yield return new Issue("vrc.expression_refs_missing", Severity.Broken)
                    {
                        Detail = $"Expressions: Menu = {(menu ? menu.name : L10n.T("未設定", "Not set"))} / Parameters = {(prm ? prm.name : L10n.T("未設定", "Not set"))}{bakedNote}",
                    }.WithTarget(d, ctx.Root);
                }
                if (prm != null)
                {
                    int cost = prm.CalcTotalCost();
                    int max = VRCExpressionParameters.MAX_PARAMETER_COST;
                    var target = (UnityEngine.Object)d.expressionParameters ?? d;
                    if (cost > max)
                    {
                        yield return new Issue("vrc.expression_params_over", Severity.Blocker)
                        {
                            Detail = L10n.IsEnglish ? $"Used: {cost} bits / Limit: {max} bits ({cost - max} bits over){bakedNote}" : $"使用 {cost} bit / 上限 {max} bit({cost - max} bit 超過){bakedNote}",
                        }.WithMetric("cost", cost).WithMetric("max", max).WithTarget(target);
                    }
                    else if (cost > max * 0.9f)
                    {
                        yield return new Issue("vrc.expression_params_over", Severity.Info)
                        {
                            Detail = L10n.IsEnglish ? $"Used: {cost} bits / Limit: {max} bits ({max - cost} bits remain; another prefab may exceed the limit){bakedNote}" : $"使用 {cost} bit / 上限 {max} bit(残り {max - cost} bit。ギミック追加で超えやすい状態){bakedNote}",
                        }.WithMetric("cost", cost).WithMetric("max", max).WithTarget(target);
                    }
                }
            }

            // --- Write Defaults 混在(FX レイヤー)
            foreach (var i in CheckWriteDefaults(dEval, d, ctx, bakedNote)) yield return i;
        }

        private static IEnumerable<Issue> CheckEyeLook(VRCAvatarDescriptor d, RuleContext ctx)
        {
            var t = typeof(VRCAvatarDescriptor);
            var enableF = t.GetField("enableEyeLook", BindingFlags.Public | BindingFlags.Instance);
            var settingsF = t.GetField("customEyeLookSettings", BindingFlags.Public | BindingFlags.Instance);
            if (enableF == null || settingsF == null) yield break;
            if (!(enableF.GetValue(d) is bool enabled) || !enabled) yield break;
            var s = settingsF.GetValue(d);
            if (s == null) yield break;
            var st = s.GetType();
            var left = st.GetField("leftEye")?.GetValue(s) as Transform;
            var right = st.GetField("rightEye")?.GetValue(s) as Transform;
            var problems = new List<string>();
            if (left == null || right == null) problems.Add(L10n.T("目のボーン(LeftEye/RightEye)が未設定", "Eye bones (LeftEye / RightEye) are not assigned"));
            var eyelidType = st.GetField("eyelidType")?.GetValue(s)?.ToString();
            if (eyelidType == "Blendshapes")
            {
                var eyelidMesh = st.GetField("eyelidsSkinnedMesh")?.GetValue(s) as SkinnedMeshRenderer;
                if (eyelidMesh == null || eyelidMesh.sharedMesh == null) problems.Add(L10n.T("まぶた用メッシュ(Eyelids Skinned Mesh)が未設定", "Eyelids Skinned Mesh is not assigned"));
            }
            if (problems.Count == 0) yield break;
            yield return new Issue("vrc.eyelook_broken", Severity.Broken)
            {
                Detail = string.Join(" / ", problems),
            }.WithTarget(d, ctx.Root);
        }

        /// <summary>FX の Animator Controller(playable layer)を取得。null なら未設定/既定。</summary>
        public static AnimatorController GetFxController(VRCAvatarDescriptor d)
        {
            if (d == null || d.baseAnimationLayers == null) return null;
            foreach (var layer in d.baseAnimationLayers)
            {
                if (layer.type != VRCAvatarDescriptor.AnimLayerType.FX || layer.isDefault) continue;
                return layer.animatorController as AnimatorController;
            }
            return null;
        }

        private static IEnumerable<Issue> CheckWriteDefaults(VRCAvatarDescriptor dEval, VRCAvatarDescriptor dOriginal, RuleContext ctx, string bakedNote)
        {
            var ac = GetFxController(dEval);
            if (ac == null) yield break;
            int on = 0, off = 0; var offStates = new List<string>(); var onStates = new List<string>();
            foreach (var l in ac.layers) Walk(l.stateMachine, l.name, ref on, ref off, onStates, offStates);
            if (on > 0 && off > 0)
            {
                int minority = Math.Min(on, off);
                var minorityList = on < off ? onStates : offStates;
                // 対象は元の(シーン上の)FX。ビルド後の一時アセットは選択できないため
                var target = (UnityEngine.Object)GetFxController(dOriginal) ?? dOriginal;
                yield return new Issue("vrc.fx_write_defaults_mixed", Severity.Broken)
                {
                    Detail = (L10n.IsEnglish
                        ? $"FX “{ac.name}”: Write Defaults ON = {on} / OFF = {off} states (minority {minority}: "
                        : $"FX「{ac.name}」: Write Defaults ON = {on} / OFF = {off} ステート(少数派 {minority} 個: ") + string.Join(", ", minorityList.Take(5)) + (minorityList.Count > 5 ? " …" : "") + $"){bakedNote}",
                }.WithMetric("on", on).WithMetric("off", off).WithTarget(target);
            }
        }

        private static void Walk(AnimatorStateMachine sm, string prefix, ref int on, ref int off, List<string> onStates, List<string> offStates)
        {
            if (sm == null) return;
            foreach (var cs in sm.states)
            {
                if (cs.state == null) continue;
                if (cs.state.writeDefaultValues) { on++; onStates.Add(prefix + "/" + cs.state.name); }
                else { off++; offStates.Add(prefix + "/" + cs.state.name); }
            }
            foreach (var child in sm.stateMachines) Walk(child.stateMachine, prefix + "/" + child.stateMachine.name, ref on, ref off, onStates, offStates);
        }
    }

    /// <summary>SDK バージョンの最低要件(VRChat が古いSDKからのアップロードをブロックする)</summary>
    public sealed class SdkVersionRule : IRule
    {
        public string Id => "env.sdk_version";
        public bool CanRun(RuleContext ctx) => ctx.HasVrcSdk;
        public static string DetectSdkVersion()
        {
            try
            {
                var info = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VRCAvatarDescriptor).Assembly);
                return info?.version;
            }
            catch { return null; }
        }
        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var v = DetectSdkVersion();
            if (string.IsNullOrEmpty(v)) yield break;
            if (Version.TryParse(v.Split('-')[0], out var ver) && ver < new Version(3, 9, 0))
            {
                yield return new Issue("env.sdk_version", Severity.Blocker)
                {
                    TitleOverride = L10n.T("VRChat SDK が古すぎます(3.9.0 未満はアップロードがブロックされます)", "VRChat SDK is too old (versions before 3.9.0 are blocked from uploading)"),
                    Detail = L10n.IsEnglish ? $"Current: {v}. Update the SDK in VCC or ALCOM." : $"現在: {v}。VCC / ALCOM で SDK を更新してください。",
                }.WithMetric("current", v);
            }
        }
    }
}
#endif
