#if UNITY_EDITOR
namespace NiAkaDev.EzMirror.Editor
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using UnityEditor;
    using UnityEditor.SceneManagement;
    using UnityEngine;
    using UnityEngine.SceneManagement;

    internal static class EzMirrorGeneratedMaterialCleanup
    {
        internal static string[] PreviewUnusedGeneratedMaterialPaths()
        {
            List<string> candidates = CollectGeneratedMaterialCandidatePaths();
            if (candidates.Count == 0)
                return new string[0];

            HashSet<string> candidateSet = new HashSet<string>(candidates, StringComparer.Ordinal);
            HashSet<string> referencedPaths = new HashSet<string>(StringComparer.Ordinal);
            AddOpenSceneEzMirrorReferences(referencedPaths);
            AddOpenPrefabStageEzMirrorReferences(referencedPaths);
            AddProjectDependencyReferences(candidateSet, referencedPaths);

            List<string> unused = new List<string>();
            for (int i = 0; i < candidates.Count; i++)
            {
                string path = candidates[i];
                if (!referencedPaths.Contains(path))
                    unused.Add(path);
            }

            unused.Sort(StringComparer.Ordinal);
            return unused.ToArray();
        }

        internal static int DeleteUnusedGeneratedMaterials(string[] candidatePaths)
        {
            if (candidatePaths == null || candidatePaths.Length == 0)
                return 0;

            HashSet<string> latestUnused = new HashSet<string>(
                PreviewUnusedGeneratedMaterialPaths(),
                StringComparer.Ordinal
            );
            int deletedCount = 0;
            for (int i = 0; i < candidatePaths.Length; i++)
            {
                string path = candidatePaths[i];
                if (string.IsNullOrEmpty(path) || !latestUnused.Contains(path))
                    continue;
                if (!IsGeneratedMaterialCandidatePath(path))
                    continue;

                if (!AssetDatabase.MoveAssetToTrash(path))
                {
                    Debug.LogWarning(
                        "[EzMirror] Failed to move unused generated material to Trash: " + path
                    );
                    continue;
                }
                deletedCount++;
            }

            if (deletedCount > 0)
            {
                AssetDatabase.SaveAssets();
                AssetDatabase.Refresh();
            }
            return deletedCount;
        }

        private static List<string> CollectGeneratedMaterialCandidatePaths()
        {
            List<string> paths = new List<string>();
            string folder = EzMirrorAssetResolver.GetGeneratedMaterialFolder();
            string[] guids = AssetDatabase.FindAssets("t:Material", new[] { folder });
            for (int i = 0; i < guids.Length; i++)
            {
                string path = AssetDatabase.GUIDToAssetPath(guids[i]);
                if (IsGeneratedMaterialCandidatePath(path))
                    paths.Add(path);
            }
            return paths;
        }

        private static bool IsGeneratedMaterialCandidatePath(string path)
        {
            if (string.IsNullOrEmpty(path))
                return false;
            if (!path.EndsWith(".mat", StringComparison.OrdinalIgnoreCase))
                return false;
            if (!EzMirrorAssetResolver.IsInGeneratedMaterialFolder(path))
                return false;

            Material material = AssetDatabase.LoadAssetAtPath<Material>(path);
            string fileName = Path.GetFileNameWithoutExtension(path);
            return material != null
                && (
                    material.name.StartsWith(EzMirrorEditor.GeneratedMaterialPrefix, StringComparison.Ordinal)
                    || fileName.StartsWith(EzMirrorEditor.GeneratedMaterialPrefix, StringComparison.Ordinal)
                )
                && HasMaterialLabel(material, EzMirrorEditor.GeneratedMaterialLabel);
        }

        private static void AddOpenSceneEzMirrorReferences(HashSet<string> referencedPaths)
        {
            for (int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++)
            {
                Scene scene = SceneManager.GetSceneAt(sceneIndex);
                if (!scene.IsValid() || !scene.isLoaded)
                    continue;

                GameObject[] roots = scene.GetRootGameObjects();
                for (int rootIndex = 0; rootIndex < roots.Length; rootIndex++)
                    AddEzMirrorReferences(roots[rootIndex], referencedPaths);
            }
        }

        private static void AddOpenPrefabStageEzMirrorReferences(HashSet<string> referencedPaths)
        {
            PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
            if (prefabStage == null || prefabStage.prefabContentsRoot == null)
                return;

            AddEzMirrorReferences(prefabStage.prefabContentsRoot, referencedPaths);
        }

        private static void AddEzMirrorReferences(
            GameObject root,
            HashSet<string> referencedPaths
        )
        {
            if (root == null)
                return;

            AddRendererMaterialReferences(root, referencedPaths);
            AddSerializedMaterialReferences(root, referencedPaths);

            EzMirror[] mirrors = root.GetComponentsInChildren<EzMirror>(true);
            for (int mirrorIndex = 0; mirrorIndex < mirrors.Length; mirrorIndex++)
            {
                EzMirror mirror = mirrors[mirrorIndex];
                if (mirror == null || mirror.profileMaterials == null)
                    continue;

                for (int profileIndex = 1; profileIndex < mirror.profileMaterials.Length; profileIndex++)
                    AddMaterialPath(mirror.profileMaterials[profileIndex], referencedPaths);
            }
        }

        private static void AddRendererMaterialReferences(
            GameObject root,
            HashSet<string> referencedPaths
        )
        {
            Renderer[] renderers = root.GetComponentsInChildren<Renderer>(true);
            for (int rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++)
            {
                Material[] materials = renderers[rendererIndex].sharedMaterials;
                for (int materialIndex = 0; materialIndex < materials.Length; materialIndex++)
                    AddMaterialPath(materials[materialIndex], referencedPaths);
            }
        }

        private static void AddSerializedMaterialReferences(
            GameObject root,
            HashSet<string> referencedPaths
        )
        {
            Component[] components = root.GetComponentsInChildren<Component>(true);
            for (int componentIndex = 0; componentIndex < components.Length; componentIndex++)
            {
                Component component = components[componentIndex];
                if (component == null)
                    continue;

                SerializedObject serializedObject;
                try
                {
                    serializedObject = new SerializedObject(component);
                }
                catch (Exception)
                {
                    continue;
                }

                SerializedProperty property = serializedObject.GetIterator();
                while (property.Next(true))
                {
                    if (property.propertyType != SerializedPropertyType.ObjectReference)
                        continue;
                    AddMaterialPath(property.objectReferenceValue as Material, referencedPaths);
                }
            }
        }

        private static void AddProjectDependencyReferences(
            HashSet<string> candidatePaths,
            HashSet<string> referencedPaths
        )
        {
            string[] guids = AssetDatabase.FindAssets(string.Empty, new[] { "Assets" });
            for (int i = 0; i < guids.Length; i++)
            {
                string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
                if (string.IsNullOrEmpty(assetPath) || candidatePaths.Contains(assetPath))
                    continue;

                string[] dependencies = AssetDatabase.GetDependencies(assetPath, true);
                for (int dependencyIndex = 0; dependencyIndex < dependencies.Length; dependencyIndex++)
                {
                    string dependency = dependencies[dependencyIndex];
                    if (candidatePaths.Contains(dependency))
                        referencedPaths.Add(dependency);
                }
            }
        }

        private static void AddMaterialPath(Material material, HashSet<string> paths)
        {
            if (material == null)
                return;

            string path = AssetDatabase.GetAssetPath(material);
            if (!string.IsNullOrEmpty(path))
                paths.Add(path);
        }

        private static bool HasMaterialLabel(Material material, string label)
        {
            if (material == null || string.IsNullOrEmpty(label))
                return false;

            string[] labels = AssetDatabase.GetLabels(material);
            for (int i = 0; i < labels.Length; i++)
            {
                if (labels[i] == label)
                    return true;
            }
            return false;
        }
    }
}
#endif
