#if AR_VRCSDK3_AVATARS
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VRC.SDK3.Dynamics.PhysBone.Components;

namespace AvatarRefinery.Editor.Core.Rules
{
    /// <summary>
    /// PhysBone の無効な設定: 揺れる対象が無い / コライダー欄の空参照 / Root がアバター外 / 別の PhysBone と重なっている。
    /// シーン上(オリジナル)の状態で判定する(対象を選択できるように)。
    /// </summary>
    public sealed class VrcPhysBoneRule : IRule
    {
        public string Id => "vrc.physbone_invalid";
        public bool CanRun(RuleContext ctx) => ctx.HasVrcSdk;

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var pbs = ctx.GetAll<VRCPhysBone>();
            if (pbs.Length == 0) yield break;

            var empty = new List<VRCPhysBone>();
            var nullCollider = new List<VRCPhysBone>();
            var external = new List<VRCPhysBone>();
            var nullIgnore = new List<VRCPhysBone>();
            var overlapping = new List<(VRCPhysBone a, VRCPhysBone b)>();

            var affected = new Dictionary<VRCPhysBone, HashSet<Transform>>();
            foreach (var pb in pbs)
            {
                var root = pb.rootTransform != null ? pb.rootTransform : pb.transform;
                if (pb.rootTransform != null && !pb.rootTransform.IsChildOf(ctx.Root)) { external.Add(pb); continue; }
                var ignore = new HashSet<Transform>((pb.ignoreTransforms ?? new List<Transform>()).Where(t => t != null));
                if ((pb.ignoreTransforms ?? new List<Transform>()).Any(t => t == null)) nullIgnore.Add(pb);
                if ((pb.colliders ?? new List<VRC.Dynamics.VRCPhysBoneColliderBase>()).Any(c => c == null)) nullCollider.Add(pb);

                var set = new HashSet<Transform>();
                Collect(root, ignore, set);
                affected[pb] = set;
                // 揺れる対象が無い: root に子が無く(または全て ignore)、Endpoint Position も 0
                bool hasChain = set.Count > 1;
                if (!hasChain && pb.endpointPosition == Vector3.zero) empty.Add(pb);
            }

            // 重なり: ある PhysBone の root が、別の PhysBone の影響範囲(root 以外)に含まれる
            var list = affected.Keys.ToList();
            for (int i = 0; i < list.Count; i++)
            {
                var a = list[i]; var aRoot = a.rootTransform != null ? a.rootTransform : a.transform;
                for (int j = 0; j < list.Count; j++)
                {
                    if (i == j) continue;
                    var b = list[j]; var bRoot = b.rootTransform != null ? b.rootTransform : b.transform;
                    if (aRoot != bRoot && affected[b].Contains(aRoot)) overlapping.Add((a, b));
                }
            }

            if (external.Count > 0)
            {
                var issue = new Issue("vrc.physbone_invalid", Severity.Broken)
                {
                    TitleOverride = L10n.T("PhysBone の Root Transform がアバターの外を指しています", "A PhysBone Root Transform points outside the avatar"),
                    Detail = string.Join(", ", external.Take(6).Select(p => p.name)),
                }.WithMetric("external", external.Count);
                foreach (var p in external) issue.WithTarget(p, ctx.Root);
                yield return issue;
            }
            if (overlapping.Count > 0)
            {
                var uniq = overlapping.Select(x => x.a).Distinct().ToList();
                var issue = new Issue("vrc.physbone_invalid", Severity.Info)
                {
                    TitleOverride = L10n.T("重なっている PhysBone があります(同じボーンを 2 つの PhysBone が動かそうとしています)", "Overlapping PhysBones are trying to drive the same chain"),
                    Detail = string.Join("\n", overlapping.Take(8).Select(x => L10n.IsEnglish
                        ? $"- {x.a.name} (Root: {(x.a.rootTransform ? x.a.rootTransform.name : x.a.transform.name)}) is inside {x.b.name}'s affected chain"
                        : $"- {x.a.name}(Root: {(x.a.rootTransform ? x.a.rootTransform.name : x.a.transform.name)}) は {x.b.name} の範囲内")) + (overlapping.Count > 8 ? "\n- …" : ""),
                }.WithMetric("overlapping", uniq.Count);
                foreach (var p in uniq) issue.WithTarget(p, ctx.Root);
                yield return issue;
            }
            if (empty.Count > 0)
            {
                var issue = new Issue("vrc.physbone_invalid", Severity.Info)
                {
                    TitleOverride = L10n.T("揺れる対象が無い PhysBone があります", "A PhysBone has no transforms to animate"),
                    Detail = string.Join(", ", empty.Take(8).Select(p => p.name)) + (empty.Count > 8 ? " …" : "") + L10n.T("(子ボーンが無く Endpoint Position も 0。ランクの PhysBone 数だけ消費しています)", " (no child chain and Endpoint Position is zero; it only consumes PhysBone budget)"),
                }.WithMetric("empty", empty.Count);
                foreach (var p in empty) issue.WithTarget(p, ctx.Root);
                yield return issue;
            }
            if (nullCollider.Count > 0 || nullIgnore.Count > 0)
            {
                var all = nullCollider.Concat(nullIgnore).Distinct().ToList();
                var issue = new Issue("vrc.physbone_invalid", Severity.Info)
                {
                    TitleOverride = L10n.T("PhysBone の Colliders / Ignore Transforms に空(None)の欄があります", "PhysBone Colliders or Ignore Transforms contains empty (None) entries"),
                    Detail = string.Join(", ", all.Take(8).Select(p => p.name)) + (all.Count > 8 ? " …" : ""),
                }.WithMetric("nullRefs", all.Count);
                foreach (var p in all) issue.WithTarget(p, ctx.Root);
                yield return issue;
            }
        }

        private static void Collect(Transform t, HashSet<Transform> ignore, HashSet<Transform> into)
        {
            if (t == null || ignore.Contains(t)) return;
            into.Add(t);
            for (int i = 0; i < t.childCount; i++) Collect(t.GetChild(i), ignore, into);
        }
    }
}
#endif
