using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace AvatarRefinery.Editor.Core.Rules
{
    /// <summary>Animator / Humanoid / 必須ボーン / 指ボーン</summary>
    public sealed class RigRule : IRule
    {
        public string Id => "rig.not_humanoid";
        public bool CanRun(RuleContext ctx) => true;

        private static readonly HumanBodyBones[] Required =
        {
            HumanBodyBones.Hips, HumanBodyBones.Spine, HumanBodyBones.Head,
            HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand,
            HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm, HumanBodyBones.RightHand,
            HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg, HumanBodyBones.LeftFoot,
            HumanBodyBones.RightUpperLeg, HumanBodyBones.RightLowerLeg, HumanBodyBones.RightFoot,
        };
        private static readonly HumanBodyBones[] Fingers =
        {
            HumanBodyBones.LeftThumbProximal, HumanBodyBones.LeftIndexProximal, HumanBodyBones.LeftMiddleProximal, HumanBodyBones.LeftRingProximal, HumanBodyBones.LeftLittleProximal,
            HumanBodyBones.RightThumbProximal, HumanBodyBones.RightIndexProximal, HumanBodyBones.RightMiddleProximal, HumanBodyBones.RightRingProximal, HumanBodyBones.RightLittleProximal,
        };

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var anim = ctx.AvatarRoot.GetComponent<Animator>();
            var blockerSev = ctx.HasVrcSdk ? Severity.Blocker : Severity.Broken;
            if (anim == null || anim.avatar == null)
            {
                yield return new Issue("rig.animator_missing", blockerSev)
                {
                    Detail = anim == null ? L10n.T("ルートに Animator がありません", "The avatar root has no Animator") : L10n.T("Animator の Avatar 欄が空です", "The Animator's Avatar field is empty"),
                }.WithTarget(ctx.AvatarRoot, ctx.Root);
                yield break;
            }
            if (!anim.isHuman)
            {
                yield return new Issue("rig.not_humanoid", blockerSev)
                {
                    Detail = L10n.IsEnglish ? $"Animator.avatar = {anim.avatar.name} (not Humanoid)" : $"Animator.avatar = {anim.avatar.name}(Humanoid ではありません)",
                }.WithTarget(ctx.AvatarRoot, ctx.Root);
                yield break;
            }
            var missing = Required.Where(b => anim.GetBoneTransform(b) == null).ToList();
            if (missing.Count > 0)
            {
                yield return new Issue("rig.bones_missing", blockerSev)
                {
                    Detail = L10n.T("欠落: ", "Missing: ") + string.Join(", ", missing),
                }.WithMetric("missing", string.Join("|", missing)).WithTarget(ctx.AvatarRoot, ctx.Root);
            }
            if (Fingers.All(b => anim.GetBoneTransform(b) == null))
            {
                yield return new Issue("rig.no_fingers", Severity.Info)
                {
                    Detail = L10n.T("Humanoid の指ボーンが 1 本も割り当てられていません", "No Humanoid finger bones are assigned"),
                }.WithTarget(ctx.AvatarRoot, ctx.Root);
            }
        }
    }

    /// <summary>Anchor Override の不統一 / Bounds 異常 / メッシュ参照切れ</summary>
    public sealed class RenderRule : IRule
    {
        public string Id => "render.anchor_override_mixed";
        public bool CanRun(RuleContext ctx) => true;

        public IEnumerable<Issue> Evaluate(RuleContext ctx)
        {
            var renderers = ctx.GetAll<Renderer>().Where(r => r is SkinnedMeshRenderer || r is MeshRenderer).ToList();
            if (renderers.Count == 0) yield break;

            // --- メッシュ参照切れ
            var noMesh = new List<Renderer>();
            foreach (var r in renderers)
            {
                if (r is SkinnedMeshRenderer smr && smr.sharedMesh == null) noMesh.Add(r);
                else if (r is MeshRenderer mr) { var mf = mr.GetComponent<MeshFilter>(); if (mf == null || mf.sharedMesh == null) noMesh.Add(r); }
            }
            if (noMesh.Count > 0)
            {
                var issue = new Issue("render.mesh_missing", Severity.Broken)
                {
                    Detail = (L10n.IsEnglish ? $"Renderers with no mesh: {noMesh.Count}: " : $"メッシュ未設定の Renderer {noMesh.Count} 個: ") + string.Join(", ", noMesh.Take(6).Select(r => r.name)),
                }.WithMetric("count", noMesh.Count);
                foreach (var r in noMesh) issue.WithTarget(r, ctx.Root);
                yield return issue;
            }

            // --- Anchor Override
            // EditorOnly タグのオブジェクトはアップロードで消えるので除外
            var live = renderers.Where(r => !IsEditorOnly(r.transform, ctx.Root)).ToList();
            if (live.Count > 1)
            {
                var groups = live.GroupBy(r => r.probeAnchor).ToList();
                if (groups.Count > 1)
                {
                    var desc = string.Join(" / ", groups.Take(4).Select(g => L10n.IsEnglish
                        ? $"{(g.Key ? g.Key.name : "Not set")}: {g.Count()}"
                        : $"{(g.Key ? g.Key.name : "未設定")}: {g.Count()}個"));
                    var issue = new Issue("render.anchor_override_mixed", Severity.Broken)
                    {
                        Detail = L10n.IsEnglish ? $"Anchor Override uses {groups.Count} different values ({desc})" : $"Anchor Override が {groups.Count} 通りに分かれています({desc})",
                        AutoFixable = true,
                    }.WithMetric("groups", groups.Count);
                    // 少数派側を対象として提示(直す対象が分かるように)
                    var majority = groups.OrderByDescending(g => g.Count()).First().Key;
                    foreach (var r in live.Where(r => r.probeAnchor != majority)) issue.WithTarget(r, ctx.Root);
                    yield return issue;
                }
                else if (groups[0].Key == null)
                {
                    var issue = new Issue("render.anchor_override_mixed", Severity.Info)
                    {
                        TitleOverride = L10n.T("Anchor Override が未設定です(メッシュごとに明るさの基準が変わる可能性)", "Anchor Override is not set (lighting may differ between meshes)"),
                        Detail = L10n.IsEnglish ? $"Anchor Override is unset on all {live.Count} Renderers. Each mesh may sample lighting from its own Bounds center." : $"Anchor Override が全 {live.Count} 個で未設定です。未設定のメッシュは各自の Bounds 中心を基準にライトを計算するため、結果が揃わないことがあります",
                        AutoFixable = true,
                    }.WithMetric("groups", 0);
                    foreach (var r in live) issue.WithTarget(r, ctx.Root);
                    yield return issue;
                }
            }

            // --- Bounds 異常(誤検出を避けるため、極端な値のみ)
            var badBounds = new List<(SkinnedMeshRenderer, string)>();
            foreach (var smr in live.OfType<SkinnedMeshRenderer>())
            {
                if (smr.sharedMesh == null) continue;
                var b = smr.localBounds;
                var e = b.extents; float maxE = Mathf.Max(e.x, e.y, e.z); float minE = Mathf.Min(e.x, e.y, e.z);
                if (maxE > 10f) badBounds.Add((smr, L10n.IsEnglish ? $"too large ({maxE:0.0} m)" : $"大きすぎ({maxE:0.0}m)"));
                else if (maxE < 0.02f) badBounds.Add((smr, L10n.IsEnglish ? $"too small ({maxE * 100:0.0} cm)" : $"小さすぎ({maxE * 100:0.0}cm)"));
                else if (b.center.magnitude > 5f) badBounds.Add((smr, L10n.IsEnglish ? $"center is {b.center.magnitude:0.0} m away" : $"中心が {b.center.magnitude:0.0}m 離れている"));
                if (smr.updateWhenOffscreen) badBounds.Add((smr, L10n.T("Update When Offscreen が ON(毎フレーム再計算=負荷)", "Update When Offscreen is ON (continuous update cost)")));
            }
            if (badBounds.Count > 0)
            {
                var issue = new Issue("render.bounds_abnormal", Severity.Info)
                {
                    Detail = string.Join("\n", badBounds.Take(8).Select(x => $"  - {x.Item1.name}: {x.Item2}")),
                    AutoFixable = true,
                }.WithMetric("count", badBounds.Count);
                foreach (var x in badBounds.Select(x => x.Item1).Distinct()) issue.WithTarget(x, ctx.Root);
                yield return issue;
            }
        }

        private static bool IsEditorOnly(Transform t, Transform root)
        {
            while (t != null && t != root) { if (t.CompareTag("EditorOnly")) return true; t = t.parent; }
            return false;
        }
    }
}
