#if UNITY_EDITOR
namespace NiAkaDev.EzMirror.Editor
{
    using System.Collections.Generic;
    using System.IO;
    using UdonSharp;
    using UnityEditor;
    using UnityEngine;
    using VRC.SDK3.Components;
    using VRC.SDK3.Dynamics.Contact.Components;
    using VRC.Udon;

    internal static class EzMirrorDistributionValidator
    {
        internal static EzMirrorValidationReport ValidateDistributionPackage()
        {
            EzMirrorValidationReport report = new EzMirrorValidationReport();
            ValidatePackageDefinition(report);
            ValidateRequiredPaths(report);
            ValidateDocsAndCatalog(report);
            ValidatePrefab(report);
            ValidateProgramAsset(report, EzMirrorPackageDefinition.ProgramAssetPath, typeof(EzMirror));
            ValidateProgramAsset(report, EzMirrorPackageDefinition.InputProxyProgramAssetPath, typeof(EzMirrorInputProxy));
            ValidateProgramAsset(report, EzMirrorPackageDefinition.InteractInputProxyProgramAssetPath, typeof(EzMirrorInteractInputProxy));
            ValidateDefaultProfileAssets(report);
            ValidateGeneratedMaterials(report);
            ValidateRuntimeDependencies(report);
            ValidateLocalizationKeys(report);
            return report;
        }

        internal static EzMirrorValidationReport ValidateCurrentScene()
        {
            return ValidateMirrors(Object.FindObjectsOfType<EzMirror>(true));
        }

        internal static EzMirrorValidationReport ValidateMirrors(EzMirror[] mirrors)
        {
            EzMirrorValidationReport report = new EzMirrorValidationReport();
            if (mirrors == null || mirrors.Length == 0)
            {
                report.AddWarning("EZM002", "Scene内にEzMirrorが見つかりません。");
                return report;
            }

            Dictionary<string, EzMirror> persistKeys = new Dictionary<string, EzMirror>();
            for (int i = 0; i < mirrors.Length; i++)
            {
                EzMirror mirror = mirrors[i];
                if (mirror == null)
                    continue;
                ValidateMirrorInstance(report, mirror);
                if (!mirror.usePersist)
                    continue;
                if (string.IsNullOrEmpty(mirror.persistKey))
                {
                    report.AddError("EZM006", GetMirrorLabel(mirror) + ": 保存キーが空です。");
                    continue;
                }
                if (persistKeys.ContainsKey(mirror.persistKey))
                    report.AddWarning("EZM006", "保存キーが重複しています: " + mirror.persistKey);
                else
                    persistKeys.Add(mirror.persistKey, mirror);
            }
            return report;
        }

        private static void ValidatePackageDefinition(EzMirrorValidationReport report)
        {
            if (!EzMirrorPackageDefinition.IsExcludedPath(EzMirrorPackageDefinition.EditorTestsPath + "/AnyTest.cs"))
                report.AddError("EZM010", "Editor Tests が配布除外定義に含まれていません。");

            string[] verificationOnlyPaths = EzMirrorPackageDefinition.GetVerificationOnlyPaths();
            for (int i = 0; i < verificationOnlyPaths.Length; i++)
            {
                if (!EzMirrorPackageDefinition.IsExcludedPath(verificationOnlyPaths[i]))
                    report.AddError("EZM010", "検証専用ファイルが配布除外定義に含まれていません: " + verificationOnlyPaths[i]);
            }

            string[] exporterExcludes = EzMirrorPackageDefinition.GetUnityPackageExcludedPaths();
            for (int i = 0; i < exporterExcludes.Length; i++)
            {
                if (exporterExcludes[i].IndexOf('*') >= 0)
                    report.AddError("EZM010", "PackageExporter非互換の除外パターンがあります: " + exporterExcludes[i]);
                if (!AssetDatabase.IsValidFolder(exporterExcludes[i]) && AssetDatabase.LoadMainAssetAtPath(exporterExcludes[i]) == null)
                    report.AddError("EZM010", "PackageExporter用の除外パスが見つかりません: " + exporterExcludes[i]);
            }

            if (EzMirrorPackageDefinition.IsExcludedPath(EzMirrorPackageDefinition.EditorPreviewMaterialPath))
                report.AddError("EZM010", "Editor視認性用Materialが配布除外定義に含まれています。");
        }

        private static void ValidateRequiredPaths(EzMirrorValidationReport report)
        {
            string[] requiredPaths = EzMirrorPackageDefinition.GetRequiredAssetPaths();
            for (int i = 0; i < requiredPaths.Length; i++)
            {
                string path = requiredPaths[i];
                if (File.Exists(path) || Directory.Exists(path) || AssetDatabase.LoadAssetAtPath<Object>(path) != null)
                    report.AddDetail("必須アセット確認: " + path);
                else
                    report.AddError("EZM001", "必須アセットが見つかりません: " + path);
            }
        }

        private static void ValidateDocsAndCatalog(EzMirrorValidationReport report)
        {
            bool hasRepositoryCatalog = File.Exists(EzMirrorPackageDefinition.CatalogPath);
            bool hasRepositoryBoothDocs = Directory.Exists(EzMirrorPackageDefinition.BoothDocsPath);
            bool hasReleaseChecklist = File.Exists(EzMirrorPackageDefinition.ReleaseChecklistPath);
            if (!hasRepositoryCatalog && !hasRepositoryBoothDocs && !hasReleaseChecklist)
            {
                report.AddWarning(
                    "EZM008",
                    "package-catalog.yml / BoothDocs / release checklist が見つからないため、リポジトリ専用ドキュメント検証をスキップしました。"
                );
                return;
            }

            string[] paths = EzMirrorPackageDefinition.GetVerificationOnlyPaths();
            for (int i = 0; i < paths.Length; i++)
            {
                if (!File.Exists(paths[i]))
                    report.AddError("EZM008", "検証対象ドキュメントが見つかりません: " + paths[i]);
            }

            if (!hasRepositoryCatalog)
                return;

            string catalog = File.ReadAllText(EzMirrorPackageDefinition.CatalogPath);
            string ezMirrorBlock = ExtractEzMirrorCatalogBlock(catalog);
            if (string.IsNullOrEmpty(ezMirrorBlock))
            {
                report.AddError("EZM008", "package-catalog.yml に ez-mirror が見つかりません。");
                return;
            }
            if (!ezMirrorBlock.Contains("model: free"))
                report.AddError("EZM008", "EzMirrorの価格モデルがfreeとして確定していません。");
            if (!ezMirrorBlock.Contains(EzMirrorPackageDefinition.LicensePath))
                report.AddError("EZM008", "EzMirrorのライセンスパスがcatalogに記録されていません。");
            if (!ezMirrorBlock.Contains("GameObject/NiAka/EzMirror/Create EzMirror"))
                report.AddError("EZM008", "EzMirrorの作成メニューがcatalogに記録されていません。");
        }

        private static void ValidatePrefab(EzMirrorValidationReport report)
        {
            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EzMirrorPackageDefinition.PrefabPath);
            if (prefab == null)
            {
                report.AddError("EZM002", "EzMirror prefabを読み込めません。");
                return;
            }

            int missingScripts = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(prefab);
            if (missingScripts > 0)
                report.AddError("EZM002", "EzMirror prefabにMissing Scriptがあります: " + missingScripts);

            EzMirror[] mirrors = prefab.GetComponentsInChildren<EzMirror>(true);
            if (mirrors.Length != 1)
                report.AddError("EZM002", "EzMirror prefabの本体コンポーネント数が不正です: " + mirrors.Length);
            else
                ValidateMirrorInstance(report, mirrors[0]);

            Renderer[] renderers = prefab.GetComponentsInChildren<Renderer>(true);
            for (int i = 0; i < renderers.Length; i++)
            {
                Material material = renderers[i].sharedMaterial;
                if (material == null)
                    continue;
                string materialPath = AssetDatabase.GetAssetPath(material);
                if (materialPath == EzMirrorAssetResolver.GetEditorPreviewMaterialPathNoCreate())
                    report.AddDetail("Editor視認性用Material参照: " + GetHierarchyPath(renderers[i].transform));
            }
        }

        private static void ValidateMirrorInstance(EzMirrorValidationReport report, EzMirror mirror)
        {
            if (mirror.profileMirror == null)
                report.AddError("EZM002", GetMirrorLabel(mirror) + ": VRCMirrorReflectionが未設定です。");
            if (mirror.profileRenderer == null)
                report.AddError("EZM002", GetMirrorLabel(mirror) + ": Rendererが未設定です。");
            if (mirror.triggerCollider == null)
                report.AddWarning("EZM002", GetMirrorLabel(mirror) + ": 表示範囲BoxColliderが未設定です。");
            if (mirror.uiToggle == null && mirror.enableUguiInput)
                report.AddWarning("EZM002", GetMirrorLabel(mirror) + ": uGUI入力がONですがToggleが未設定です。");
            if (mirror.enableContactInput && mirror.contactReceiver == null)
                report.AddWarning("EZM002", GetMirrorLabel(mirror) + ": Contact入力がONですがReceiverが未設定です。");
            if (mirror.profileMaterials == null || mirror.profileMaterials.Length < 2)
                report.AddError("EZM004", GetMirrorLabel(mirror) + ": Profile配列が不足しています。");
            else
                ValidateMirrorGeneratedMaterials(report, mirror);
            if (HasLegacyIconObjects(mirror))
                report.AddWarning("EZM011", GetMirrorLabel(mirror) + ": 旧Profile icon object参照が残っています。必要な場合だけLegacy互換として維持してください。");
        }

        private static void ValidateMirrorGeneratedMaterials(EzMirrorValidationReport report, EzMirror mirror)
        {
            if (mirror.profileMaterials == null)
                return;
            for (int i = 1; i < mirror.profileMaterials.Length; i++)
            {
                Material material = mirror.profileMaterials[i];
                if (material == null)
                {
                    report.AddWarning("EZM005", GetMirrorLabel(mirror) + ": Profile " + i + " の生成Materialが未設定です。");
                    continue;
                }
                string path = AssetDatabase.GetAssetPath(material);
                if (!EzMirrorAssetResolver.IsInGeneratedMaterialFolderNoCreate(path))
                    report.AddWarning("EZM005", GetMirrorLabel(mirror) + ": Profile " + i + " がGeneratedフォルダ外のMaterialを参照しています。");
            }
        }

        private static void ValidateProgramAsset(EzMirrorValidationReport report, string assetPath, System.Type expectedType)
        {
            UdonSharpProgramAsset programAsset = AssetDatabase.LoadAssetAtPath<UdonSharpProgramAsset>(assetPath);
            if (programAsset == null)
            {
                report.AddError("EZM003", "UdonSharp Program Assetを読み込めません: " + assetPath);
                return;
            }
            if (programAsset.GetClass() != expectedType)
                report.AddError("EZM003", "Program Assetの生成対象が不正です: " + assetPath);
        }

        private static void ValidateDefaultProfileAssets(EzMirrorValidationReport report)
        {
            for (int i = 1; i <= 3; i++)
            {
                EzMirrorProfileAsset profile = AssetDatabase.LoadAssetAtPath<EzMirrorProfileAsset>(
                    EzMirrorAssetResolver.GetDefaultProfileAssetPathNoCreate(i)
                );
                if (profile == null)
                    report.AddError("EZM012", "既定Profile Assetを読み込めません: " + i);
            }
        }

        private static void ValidateGeneratedMaterials(EzMirrorValidationReport report)
        {
            string folder = EzMirrorAssetResolver.GetGeneratedMaterialFolderPathNoCreate();
            if (!AssetDatabase.IsValidFolder(folder))
                return;

            string[] guids = AssetDatabase.FindAssets("t:Material", new[] { folder });
            for (int i = 0; i < guids.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(guids[i]);
                Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
                if (material == null)
                    continue;
                if (path == EzMirrorAssetResolver.GetEditorPreviewMaterialPathNoCreate())
                    continue;
                string[] labels = AssetDatabase.GetLabels(material);
                if (!HasLabel(labels, EzMirrorEditor.GeneratedMaterialLabel))
                    report.AddWarning("EZM005", "生成Materialラベルがありません: " + path);
            }
        }

        private static void ValidateRuntimeDependencies(EzMirrorValidationReport report)
        {
            string editorPreviewPath = EzMirrorAssetResolver.GetEditorPreviewMaterialPathNoCreate();
            string[] guids = AssetDatabase.FindAssets(string.Empty, new[] { EzMirrorPackageDefinition.RuntimePath });
            for (int i = 0; i < guids.Length; i++)
            {
                string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]).Replace('\\', '/');
                if (assetPath == editorPreviewPath)
                    continue;
                string[] dependencies = AssetDatabase.GetDependencies(assetPath, true);
                for (int j = 0; j < dependencies.Length; j++)
                {
                    string dependency = dependencies[j].Replace('\\', '/');
                    if (dependency == assetPath)
                        continue;
                    if (dependency.StartsWith("Assets/", System.StringComparison.Ordinal)
                        && dependency.Contains("/Editor/"))
                    {
                        report.AddError("EZM007", "Runtime配下からEditor専用Assetへの参照があります: " + dependency);
                    }
                    if (dependency == editorPreviewPath)
                        report.AddDetail("Editor視認性用Material依存: " + assetPath);
                }
            }
        }

        private static void ValidateLocalizationKeys(EzMirrorValidationReport report)
        {
            string[] keys = EzMirrorLocalization.RequiredLocalizationKeys;
            for (int i = 0; i < keys.Length; i++)
            {
                if (!EzMirrorLocalization.HasTranslationForEveryLanguage(keys[i]))
                    report.AddWarning("EZM009", "Localization key missing: " + keys[i]);
            }
        }

        private static string ExtractEzMirrorCatalogBlock(string catalog)
        {
            const string marker = "  - id: ez-mirror";
            int start = catalog.IndexOf(marker, System.StringComparison.Ordinal);
            if (start < 0)
                return string.Empty;
            int next = catalog.IndexOf("\n  - id: ", start + marker.Length, System.StringComparison.Ordinal);
            return next < 0 ? catalog.Substring(start) : catalog.Substring(start, next - start);
        }

        private static bool HasLegacyIconObjects(EzMirror mirror)
        {
            if (mirror == null || mirror.profileIconObjects == null)
                return false;
            for (int i = 0; i < mirror.profileIconObjects.Length; i++)
            {
                if (mirror.profileIconObjects[i] != null)
                    return true;
            }
            return false;
        }

        private static bool HasLabel(string[] labels, string label)
        {
            for (int i = 0; i < labels.Length; i++)
            {
                if (labels[i] == label)
                    return true;
            }
            return false;
        }

        private static string GetMirrorLabel(EzMirror mirror)
        {
            return mirror == null ? "(null)" : GetHierarchyPath(mirror.transform);
        }

        private static string GetHierarchyPath(Transform transform)
        {
            if (transform == null)
                return "(null)";
            string path = transform.name;
            Transform current = transform.parent;
            while (current != null)
            {
                path = current.name + "/" + path;
                current = current.parent;
            }
            return path;
        }
    }
}
#endif
