#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;
using Object = UnityEngine.Object;

namespace AvatarRefinery.Editor.Core.Rules
{
    /// <summary>
    /// 表情アニメーション(FX)と、まばたき(Eye Look の Eyelids)/口パク(Viseme)の競合を検出する。
    /// 典型症状: 半目・白目になる、表情中に口が動かない。
    /// FX の各ステートに VRC Animator Tracking Control(Eyes/Mouth = Animation)が付いていれば「意図的に制御している」とみなして除外する。
    /// ビルド後(NDMF適用後)の Descriptor / FX を優先して判定する。
    /// </summary>
    public sealed class VrcExpressionConflictRule : IRule
    {
        public string Id => "vrc.expression_blink_conflict";
        public bool CanRun(RuleContext ctx) => ctx.HasVrcSdk && ctx.AvatarRoot.GetComponent<VRCAvatarDescriptor>() != null;

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var dOriginal = ctx.AvatarRoot.GetComponent<VRCAvatarDescriptor>();
            var dBaked = ctx.BakedRoot != null ? ctx.BakedRoot.GetComponent<VRCAvatarDescriptor>() : null;
            var d = dBaked != null ? dBaked : dOriginal;
            var root = dBaked != null ? ctx.BakedRoot.transform : ctx.Root;
            string bakedNote = dBaked != null ? L10n.T("(ビルド後の状態で判定)", " (checked after NDMF build)") : "";

            var fx = VrcDescriptorRule.GetFxController(d);
            if (fx == null) yield break;

            // --- 監視対象の BlendShape 名(パス付き)
            var eyelidTargets = GetEyelidBlendShapes(d, root);   // (path, name)
            var visemeTargets = GetVisemeBlendShapes(d, root);
            if (eyelidTargets.Count == 0 && visemeTargets.Count == 0) yield break;

            var eyeHits = new List<string>(); var mouthHits = new List<string>();
            foreach (var layer in fx.layers)
            {
                foreach (var (state, path) in EnumerateStates(layer.stateMachine, layer.name))
                {
                    if (state.motion == null) continue;
                    var clips = new List<AnimationClip>();
                    CollectClips(state.motion, clips, 0);
                    if (clips.Count == 0) continue;

                    bool eyesAnimated = HasTrackingControl(state, layer.stateMachine, "trackingEyes");
                    bool mouthAnimated = HasTrackingControl(state, layer.stateMachine, "trackingMouth");

                    foreach (var clip in clips.Distinct())
                    {
                        foreach (var b in AnimationUtility.GetCurveBindings(clip))
                        {
                            if (b.type != typeof(SkinnedMeshRenderer) || !b.propertyName.StartsWith("blendShape.")) continue;
                            var shape = b.propertyName.Substring("blendShape.".Length);
                            if (!eyesAnimated && eyelidTargets.Any(t => t.name == shape && (t.path == b.path || string.IsNullOrEmpty(t.path))))
                                eyeHits.Add($"{path}({clip.name}: {shape})");
                            if (!mouthAnimated && visemeTargets.Any(t => t.name == shape && (t.path == b.path || string.IsNullOrEmpty(t.path))))
                                mouthHits.Add($"{path}({clip.name}: {shape})");
                        }
                    }
                }
            }

            var target = (Object)VrcDescriptorRule.GetFxController(dOriginal) ?? dOriginal;
            if (eyeHits.Count > 0)
            {
                var uniq = eyeHits.Distinct().ToList();
                yield return new Issue("vrc.expression_blink_conflict", Severity.Broken)
                {
                    Detail = (L10n.IsEnglish ? $"FX states driving Blink BlendShapes: {uniq.Count}{bakedNote}:\n" : $"まばたき用 BlendShape を動かしている FX ステート {uniq.Count} 件{bakedNote}:\n") + string.Join("\n", uniq.Take(8).Select(s => "- " + s)) + (uniq.Count > 8 ? "\n- …" : ""),
                }.WithMetric("count", uniq.Count).WithTarget(target);
            }
            if (mouthHits.Count > 0)
            {
                var uniq = mouthHits.Distinct().ToList();
                yield return new Issue("vrc.expression_viseme_conflict", Severity.Broken)
                {
                    Detail = (L10n.IsEnglish ? $"FX states driving Viseme BlendShapes: {uniq.Count}{bakedNote}:\n" : $"口パク(Viseme)用 BlendShape を動かしている FX ステート {uniq.Count} 件{bakedNote}:\n") + string.Join("\n", uniq.Take(8).Select(s => "- " + s)) + (uniq.Count > 8 ? "\n- …" : ""),
                }.WithMetric("count", uniq.Count).WithTarget(target);
            }
        }

        // ---------------------------------------------------------------- helpers

        private static List<(string path, string name)> GetEyelidBlendShapes(VRCAvatarDescriptor d, Transform root)
        {
            var list = new List<(string, string)>();
            var t = typeof(VRCAvatarDescriptor);
            if (!(t.GetField("enableEyeLook")?.GetValue(d) is bool enabled) || !enabled) return list;
            var s = t.GetField("customEyeLookSettings")?.GetValue(d);
            if (s == null) return list;
            var st = s.GetType();
            if (st.GetField("eyelidType")?.GetValue(s)?.ToString() != "Blendshapes") return list;
            var smr = st.GetField("eyelidsSkinnedMesh")?.GetValue(s) as SkinnedMeshRenderer;
            var idx = st.GetField("eyelidsBlendshapes")?.GetValue(s) as int[];
            if (smr == null || smr.sharedMesh == null || idx == null) return list;
            var path = Issue.HierarchyPath(smr.transform, root);
            path = StripRoot(path, root);
            foreach (var i in idx)
            {
                if (i < 0 || i >= smr.sharedMesh.blendShapeCount) continue;
                list.Add((path, smr.sharedMesh.GetBlendShapeName(i)));
            }
            return list;
        }

        private static List<(string path, string name)> GetVisemeBlendShapes(VRCAvatarDescriptor d, Transform root)
        {
            var list = new List<(string, string)>();
            if (d.lipSync != VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape) return list;
            var smr = d.VisemeSkinnedMesh;
            if (smr == null || d.VisemeBlendShapes == null) return list;
            var path = StripRoot(Issue.HierarchyPath(smr.transform, root), root);
            foreach (var n in d.VisemeBlendShapes) if (!string.IsNullOrEmpty(n)) list.Add((path, n));
            return list;
        }

        /// <summary>Issue.HierarchyPath は root 名を先頭に含むため、AnimationClip の相対パス(root 名を含まない)に合わせる</summary>
        private static string StripRoot(string path, Transform root)
        {
            if (root == null) return path;
            var prefix = root.name + "/";
            if (path == root.name) return "";
            return path.StartsWith(prefix) ? path.Substring(prefix.Length) : path;
        }

        public static IEnumerable<(AnimatorState state, string path)> EnumerateStates(AnimatorStateMachine sm, string prefix)
        {
            if (sm == null) yield break;
            foreach (var cs in sm.states) if (cs.state != null) yield return (cs.state, prefix + "/" + cs.state.name);
            foreach (var child in sm.stateMachines)
                foreach (var x in EnumerateStates(child.stateMachine, prefix + "/" + child.stateMachine.name)) yield return x;
        }

        public static void CollectClips(Motion m, List<AnimationClip> into, int depth)
        {
            if (m == null || depth > 16) return;
            if (m is AnimationClip c) { into.Add(c); return; }
            if (m is BlendTree bt) foreach (var ch in bt.children) CollectClips(ch.motion, into, depth + 1);
        }

        /// <summary>ステート(またはそのレイヤーのルート)に VRC Animator Tracking Control があり、指定フィールドが Animation か</summary>
        private static bool HasTrackingControl(AnimatorState state, AnimatorStateMachine layerRoot, string fieldName)
        {
            foreach (var b in (state.behaviours ?? new StateMachineBehaviour[0]).Concat(layerRoot != null ? layerRoot.behaviours ?? new StateMachineBehaviour[0] : new StateMachineBehaviour[0]))
            {
                if (b == null || b.GetType().Name != "VRCAnimatorTrackingControl") continue;
                var f = b.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
                if (f != null && f.GetValue(b)?.ToString() == "Animation") return true;
            }
            return false;
        }
    }

    /// <summary>
    /// Expression Menu の参照切れ: サブメニュー未設定、Parameters に存在しないパラメータ、上限超過。
    /// ビルド後の Descriptor を優先(Modular Avatar が生成するメニュー・パラメータを含めて判定)。
    /// </summary>
    public sealed class VrcExpressionMenuRule : IRule
    {
        public string Id => "vrc.expression_menu_broken";
        public bool CanRun(RuleContext ctx) => ctx.HasVrcSdk && ctx.AvatarRoot.GetComponent<VRCAvatarDescriptor>() != null;

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var dOriginal = ctx.AvatarRoot.GetComponent<VRCAvatarDescriptor>();
            var dBaked = ctx.BakedRoot != null ? ctx.BakedRoot.GetComponent<VRCAvatarDescriptor>() : null;
            var d = dBaked != null ? dBaked : dOriginal;
            string bakedNote = dBaked != null ? L10n.T("(ビルド後の状態で判定)", "(checked after NDMF build)") : "";
            if (!d.customExpressions || d.expressionsMenu == null) yield break;

            var prm = d.expressionParameters;
            var defined = new HashSet<string>();
            if (prm != null && prm.parameters != null) foreach (var p in prm.parameters) if (p != null && !string.IsNullOrEmpty(p.name)) defined.Add(p.name);

            var problems = new List<string>(); var infos = new List<string>();
            var visited = new HashSet<VRCExpressionsMenu>();
            Walk(d.expressionsMenu, d.expressionsMenu.name, defined, visited, problems, infos, 0);

            var target = (Object)dOriginal.expressionsMenu ?? dOriginal;
            if (problems.Count > 0)
            {
                yield return new Issue("vrc.expression_menu_broken", Severity.Broken)
                {
                    Detail = string.Join("\n", problems.Take(10).Select(p => "- " + p)) + (problems.Count > 10 ? "\n- …" : "") + (bakedNote != "" ? "\n" + bakedNote : ""),
                }.WithMetric("count", problems.Count).WithTarget(target);
            }
            if (infos.Count > 0)
            {
                yield return new Issue("vrc.expression_menu_broken", Severity.Info)
                {
                    TitleOverride = L10n.T("Expression Menu に何もしない項目があります", "Expression Menu contains controls that do nothing"),
                    Detail = string.Join("\n", infos.Take(10).Select(p => "- " + p)) + (infos.Count > 10 ? "\n- …" : ""),
                }.WithMetric("count", infos.Count).WithTarget(target);
            }
        }

        private static void Walk(VRCExpressionsMenu menu, string path, HashSet<string> defined, HashSet<VRCExpressionsMenu> visited, List<string> problems, List<string> infos, int depth)
        {
            if (menu == null || depth > 24 || !visited.Add(menu)) return;
            var controls = menu.controls ?? new List<VRCExpressionsMenu.Control>();
            if (controls.Count > VRCExpressionsMenu.MAX_CONTROLS)
                problems.Add(L10n.IsEnglish ? $"{path}: {controls.Count} controls (limit {VRCExpressionsMenu.MAX_CONTROLS})" : $"{path}: 項目が {controls.Count} 個(上限 {VRCExpressionsMenu.MAX_CONTROLS})");
            foreach (var c in controls)
            {
                if (c == null) continue;
                var label = $"{path} › {(string.IsNullOrEmpty(c.name) ? L10n.T("(無名)", "(unnamed)") : c.name)}";
                var mainParam = c.parameter != null ? c.parameter.name : null;
                switch (c.type)
                {
                    case VRCExpressionsMenu.Control.ControlType.SubMenu:
                        if (c.subMenu == null) problems.Add(label + L10n.T(": サブメニューが未設定", ": submenu is not assigned"));
                        else Walk(c.subMenu, label, defined, visited, problems, infos, depth + 1);
                        break;
                    case VRCExpressionsMenu.Control.ControlType.Button:
                    case VRCExpressionsMenu.Control.ControlType.Toggle:
                        if (string.IsNullOrEmpty(mainParam)) infos.Add(label + L10n.T(": パラメータ未設定(押しても何も起きません)", ": no parameter is assigned (the control does nothing)"));
                        else if (!defined.Contains(mainParam)) problems.Add(L10n.IsEnglish ? $"{label}: parameter “{mainParam}” is missing from Expression Parameters" : $"{label}: パラメータ「{mainParam}」が Expression Parameters にありません");
                        break;
                    default: // Puppets
                        if (!string.IsNullOrEmpty(mainParam) && !defined.Contains(mainParam)) problems.Add(L10n.IsEnglish ? $"{label}: parameter “{mainParam}” is missing from Expression Parameters" : $"{label}: パラメータ「{mainParam}」が Expression Parameters にありません");
                        if (c.subParameters != null)
                            foreach (var sp in c.subParameters)
                                if (sp != null && !string.IsNullOrEmpty(sp.name) && !defined.Contains(sp.name)) problems.Add(L10n.IsEnglish ? $"{label}: axis parameter “{sp.name}” is missing from Expression Parameters" : $"{label}: 軸パラメータ「{sp.name}」が Expression Parameters にありません");
                        break;
                }
            }
        }
    }
}
#endif
