using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

#if AR_VRCSDK3_AVATARS
using VRC.SDKBase.Validation.Performance;
using VRC.SDKBase.Validation.Performance.Stats;
#endif

namespace AvatarRefinery.Editor.Core
{
    /// <summary>SDKのパフォーマンス統計を、SDK型に依存しない形で保持するスナップショット。</summary>
    public sealed class PerfSnapshot
    {
        public bool Ok;
        public string Error;
        public string PcOverall = "None";
        public string AndroidOverall = "None";
        /// <summary>カテゴリ名 → (値, PC評価, Android評価)</summary>
        public List<PerfCategoryResult> Categories = new List<PerfCategoryResult>();
        /// <summary>SDKの生フィールド(デバッグ・将来のフィールド名変更検知用)</summary>
        public Dictionary<string, string> Raw = new Dictionary<string, string>();

        public PerfCategoryResult Get(string category) => Categories.Find(c => c.Category == category);

        public double GetNumber(string category, double fallback = double.NaN)
        {
            var c = Get(category);
            if (c == null) return fallback;
            return double.TryParse(c.Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var d) ? d : fallback;
        }
    }

    /// <summary>
    /// VRChat SDK の AvatarPerformanceStats へのアダプタ。
    /// - SDK不在時はOk=falseのスナップショットを返す(コンパイルは通る)。
    /// - SDKの閾値セット未初期化問題(起動直後/バッチ)を自前で解消する。
    /// - ランク閾値(GetStatLevelForRating)もここから取り出せる。
    /// </summary>
    public static class VrcPerformanceAdapter
    {
        public static bool Available
        {
            get
            {
#if AR_VRCSDK3_AVATARS
                return true;
#else
                return false;
#endif
            }
        }

        /// <summary>PC/Android両方の統計を1回の呼び出しで取得。</summary>
        public static PerfSnapshot Calculate(GameObject root)
        {
            var snap = new PerfSnapshot();
#if AR_VRCSDK3_AVATARS
            try
            {
                EnsureInitialized();
                var pc = new AvatarPerformanceStats(false);
                AvatarPerformance.CalculatePerformanceStats(root.name, root, pc, false);
                var mobile = new AvatarPerformanceStats(true);
                AvatarPerformance.CalculatePerformanceStats(root.name, root, mobile, true);

                snap.PcOverall = pc.GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall).ToString();
                snap.AndroidOverall = mobile.GetPerformanceRatingForCategory(AvatarPerformanceCategory.Overall).ToString();

                foreach (AvatarPerformanceCategory cat in Enum.GetValues(typeof(AvatarPerformanceCategory)))
                {
                    var name = cat.ToString();
                    if (cat == AvatarPerformanceCategory.Overall || name == "None" || name == "AvatarPerformanceCategoryCount") continue;
                    var r = new PerfCategoryResult { Category = name, Value = ValueForCategory(pc, cat) };
                    try { r.PcRating = pc.GetPerformanceRatingForCategory(cat).ToString(); } catch { r.PcRating = "None"; }
                    try { r.AndroidRating = mobile.GetPerformanceRatingForCategory(cat).ToString(); } catch { r.AndroidRating = "None"; }
                    snap.Categories.Add(r);
                }
                foreach (var f in typeof(AvatarPerformanceStats).GetFields(BindingFlags.Public | BindingFlags.Instance))
                {
                    object v = null; try { v = f.GetValue(pc); } catch { }
                    snap.Raw[f.Name] = v == null ? "null" : v.ToString();
                }
                snap.Ok = true;
            }
            catch (Exception e)
            {
                snap.Ok = false; snap.Error = e.ToString();
            }
#else
            snap.Ok = false; snap.Error = "VRChat SDK (Avatars) not present";
#endif
            return snap;
        }

        /// <summary>指定ランクの閾値(そのランクでいられる上限)。SDKの閾値アセットを一次情報とする。取得不能ならnull。</summary>
        public static double? GetLimit(string category, string rating, bool mobile)
        {
#if AR_VRCSDK3_AVATARS
            try
            {
                EnsureInitialized();
                if (!Enum.TryParse<PerformanceRating>(rating, out var pr)) return null;
                var level = AvatarPerformanceStats.GetStatLevelForRating(pr, mobile);
                if (level == null) return null;
                var fieldName = FieldNameForCategory(category);
                if (fieldName == null) return null;
                object v = null;
                if (fieldName.StartsWith("physBone."))
                {
                    var pb = level.GetType().GetField("physBone", BindingFlags.Public | BindingFlags.Instance)?.GetValue(level);
                    if (pb == null) return null;
                    var sub = fieldName.Substring("physBone.".Length);
                    var m = pb.GetType().GetField(sub, BindingFlags.Public | BindingFlags.Instance);
                    v = m?.GetValue(pb);
                }
                else
                {
                    var f = level.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
                    v = f?.GetValue(level);
                }
                if (v == null) return null;
                if (v is float fl) return fl;
                if (v is int i) return i;
                if (v is double d) return d;
                if (v is long l) return l;
                if (double.TryParse(v.ToString(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var parsed)) return parsed;
                return null;
            }
            catch { return null; }
#else
            return null;
#endif
        }

#if AR_VRCSDK3_AVATARS
        private static bool _initialized;

        /// <summary>
        /// SDKは起動後の遅延処理(EnvConfig.LoadEditorResources → AvatarPerformanceStats.Initialize)で閾値セットをロードする。
        /// バッチ実行や起動直後は未ロードで CalculatePerformanceRating が NullReference になる(2026-08-15 スパイクで確認)。
        /// </summary>
        public static void EnsureInitialized()
        {
            if (_initialized) return;
            var m = typeof(AvatarPerformanceStats).GetMethod("Initialize",
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, Type.EmptyTypes, null);
            if (m == null) Debug.LogWarning("[AvatarRefinery] AvatarPerformanceStats.Initialize() was not found (the SDK API may have changed). Performance-rank calculation may fail.");
            else m.Invoke(null, null);
            _initialized = true;
        }

        private static string FieldNameForCategory(string cat)
        {
            switch (cat)
            {
                case "PolyCount": return "polyCount";
                case "AABB": return "aabb";
                case "SkinnedMeshCount": return "skinnedMeshCount";
                case "MeshCount": return "meshCount";
                case "MaterialCount": return "materialCount";
                case "AnimatorCount": return "animatorCount";
                case "BoneCount": return "boneCount";
                case "LightCount": return "lightCount";
                case "ParticleSystemCount": return "particleSystemCount";
                case "ParticleTotalCount": return "particleTotalCount";
                case "ParticleMaxMeshPolyCount": return "particleMaxMeshPolyCount";
                case "ParticleTrailsEnabled": return "particleTrailsEnabled";
                case "ParticleCollisionEnabled": return "particleCollisionEnabled";
                case "TrailRendererCount": return "trailRendererCount";
                case "LineRendererCount": return "lineRendererCount";
                case "PhysBoneComponentCount": return "physBone.componentCount";
                case "PhysBoneTransformCount": return "physBone.transformCount";
                case "PhysBoneColliderCount": return "physBone.colliderCount";
                case "PhysBoneCollisionCheckCount": return "physBone.collisionCheckCount";
                case "ContactCount": return "contactCount";
                case "ConstraintsCount": return "constraintsCount";
                case "ConstraintDepth": return "constraintDepth";
                case "RaycastCount": return "raycastCount";
                case "ClothCount": return "clothCount";
                case "ClothMaxVertices": return "clothMaxVertices";
                case "PhysicsColliderCount": return "physicsColliderCount";
                case "PhysicsRigidbodyCount": return "physicsRigidbodyCount";
                case "AudioSourceCount": return "audioSourceCount";
                case "TextureMegabytes": return "textureMegabytes";
                case "DownloadSize": return "downloadSize";
                case "UncompressedSize": return "uncompressedSize";
                default: return null;
            }
        }

        private static string ValueForCategory(AvatarPerformanceStats s, AvatarPerformanceCategory cat)
        {
            var name = FieldNameForCategory(cat.ToString());
            if (name == null) return "";
            try
            {
                object v;
                if (name.StartsWith("physBone."))
                {
                    var pb = typeof(AvatarPerformanceStats).GetField("physBone", BindingFlags.Public | BindingFlags.Instance)?.GetValue(s);
                    if (pb == null) return "";
                    var sub = name.Substring("physBone.".Length);
                    var m = pb.GetType().GetField(sub, BindingFlags.Public | BindingFlags.Instance);
                    v = m?.GetValue(pb);
                }
                else
                {
                    var f = typeof(AvatarPerformanceStats).GetField(name, BindingFlags.Public | BindingFlags.Instance);
                    if (f == null) return "";
                    v = f.GetValue(s);
                }
                if (v == null) return "";
                if (v is float fl) return fl.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
                if (v is Bounds b) return $"{b.size.x:0.00}x{b.size.y:0.00}x{b.size.z:0.00}m";
                return v.ToString();
            }
            catch { return ""; }
        }
#endif
    }
}
