using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace CatsUdonTools.Editor
{
    /// <summary>
    /// 入稿用の一時階層が参照するマテリアルを入稿フォルダへ複製し、Renderer の参照を複製側へ差し替える。
    /// エディタ時のみ動作し、プレイヤービルドには含まれない。入稿者の元マテリアルは変更しない。
    /// 既知の制限: アニメーションでマテリアルを差し替えている場合、クリップが参照する元マテリアルは差し替えない
    /// （依存として元のマテリアル・テクスチャが入稿物に含まれる）
    /// </summary>
    internal static class SubmissionMaterialDuplicator
    {
        private const string MaterialExtension = ".mat";

        /// <summary>
        /// exporterRoot 配下の全 Renderer のマテリアルを materialsFolder へ複製して差し替え、複製したマテリアルを返す
        /// </summary>
        public static IReadOnlyCollection<Material> ReplaceWithCopies(GameObject exporterRoot, string materialsFolder)
        {
            // 同じ元マテリアルを複数 Renderer が共有していても、複製は 1 回だけにするため
            var duplicatedByOriginal = new Dictionary<Material, Material>();

            foreach (var renderer in exporterRoot.GetComponentsInChildren<Renderer>(true))
            {
                ReplaceRendererMaterials(renderer, materialsFolder, duplicatedByOriginal);
            }

            return duplicatedByOriginal.Values;
        }

        private static void ReplaceRendererMaterials(Renderer renderer, string materialsFolder, Dictionary<Material, Material> duplicatedByOriginal)
        {
            var originalMaterials = renderer.sharedMaterials;
            var replacedMaterials = new Material[originalMaterials.Length];

            for (var slotIndex = 0; slotIndex < originalMaterials.Length; slotIndex++)
            {
                replacedMaterials[slotIndex] = GetOrCreateCopy(originalMaterials[slotIndex], materialsFolder, duplicatedByOriginal);
            }

            renderer.sharedMaterials = replacedMaterials;
        }

        private static Material GetOrCreateCopy(Material original, string materialsFolder, Dictionary<Material, Material> duplicatedByOriginal)
        {
            if (original == null)
            {
                return null;
            }

            if (duplicatedByOriginal.TryGetValue(original, out var existingCopy))
            {
                return existingCopy;
            }

            var copy = CreateCopy(original, materialsFolder);
            duplicatedByOriginal.Add(original, copy);
            return copy;
        }

        private static Material CreateCopy(Material original, string materialsFolder)
        {
            var copyPath = AssetDatabase.GenerateUniqueAssetPath($"{materialsFolder}/{ToSafeFileName(original.name)}{MaterialExtension}");
            var originalPath = AssetDatabase.GetAssetPath(original);

            if (IsStandaloneMaterialAsset(originalPath))
            {
                AssetDatabase.CopyAsset(originalPath, copyPath);
                return AssetDatabase.LoadAssetAtPath<Material>(copyPath);
            }

            // FBX 内蔵マテリアルなどは .mat として直接コピーできないため、内容を複製して新規アセットにする
            var copy = new Material(original);
            AssetDatabase.CreateAsset(copy, copyPath);
            return copy;
        }

        private static bool IsStandaloneMaterialAsset(string assetPath)
        {
            return !string.IsNullOrEmpty(assetPath)
                && string.Equals(Path.GetExtension(assetPath), MaterialExtension, System.StringComparison.OrdinalIgnoreCase);
        }

        private static string ToSafeFileName(string materialName)
        {
            foreach (var invalidCharacter in Path.GetInvalidFileNameChars())
            {
                materialName = materialName.Replace(invalidCharacter, '_');
            }

            return materialName;
        }
    }
}
