using UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;
using VRM;
using MagicaCloth2;
using MagicaClothType = MagicaCloth2.MagicaCloth;
using UnityEngine.Animations;

public class AvatarComponetCopier : EditorWindow
{
    GameObject sourcePrefab;
    GameObject destinationPrefab;

    [MenuItem("Tools/Avatar Component Mover (SpringBone+MagicaCloth2)")]
    static void ShowWindow()
    {
        GetWindow<AvatarComponetCopier>("Avatar Component Mover");
    }

    void OnGUI()
    {
        GUILayout.Label("Move VRMSpringBone/MagicaCloth2 & Collider Components", EditorStyles.boldLabel);

        sourcePrefab = (GameObject)EditorGUILayout.ObjectField("Source Object", sourcePrefab, typeof(GameObject), true);
        destinationPrefab = (GameObject)EditorGUILayout.ObjectField("Target Object", destinationPrefab, typeof(GameObject), true);

        if (GUILayout.Button("Spring본콜라이더 복사"))
        {
            if (sourcePrefab == null || destinationPrefab == null)
            {
                EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
                return;
            }
            CopySpringBoneAndMagicaColliders();
        }

        if (GUILayout.Button("Spring본/Cloth 옮기기"))
        {
            if (sourcePrefab == null || destinationPrefab == null)
            {
                EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
                return;
            }
            MoveSpringBonesAndMagicaCloth();
        }

        if (GUILayout.Button("Constraint 값 복사"))
        {
            if (sourcePrefab == null || destinationPrefab == null)
            {
                EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
                return;
            }
            CopyConstraints();
        }

        if (GUILayout.Button("BlendShapeCopy"))
        {
            if (sourcePrefab == null || destinationPrefab == null)
            {
                EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
                return;
            }
            CopyBlendShapes();
        }

        if (GUILayout.Button("ActiveStateCopy"))
        {
            if (sourcePrefab == null || destinationPrefab == null)
            {
                EditorUtility.DisplayDialog("Error", "Source and Target Objects must be set.", "OK");
                return;
            }
            CopyActiveStates();
        }
    }

    void CopySpringBoneAndMagicaColliders()
    {
        // VRMSpringBoneColliderGroup
        var srcColliders = sourcePrefab.GetComponentsInChildren<VRMSpringBoneColliderGroup>(true)
            .OrderBy(c => GetTransformPath(c.transform, sourcePrefab.transform)).ToArray();
        foreach (var srcCollider in srcColliders)
        {
            string path = GetTransformPath(srcCollider.transform, sourcePrefab.transform);
            var tgtTransform = destinationPrefab.transform.Find(path);
            if (tgtTransform == null) continue;
            if (tgtTransform.GetComponent<VRMSpringBoneColliderGroup>() != null) continue;
            var tgtCollider = tgtTransform.gameObject.AddComponent<VRMSpringBoneColliderGroup>();
            CopyColliderGroupParameters(srcCollider, tgtCollider);
        }
        // MagicaCloth2 Colliders
        CopyMagicaCollider<MagicaSphereCollider>();
        CopyMagicaCollider<MagicaCapsuleCollider>();
        CopyMagicaCollider<MagicaPlaneCollider>();
        Debug.Log("VRMSpringBoneColliderGroup & MagicaCloth2 Collider 복사가 완료되었습니다.");
    }

    void CopyMagicaCollider<T>() where T : Component
    {
        var srcColliders = sourcePrefab.GetComponentsInChildren<T>(true)
            .OrderBy(c => GetTransformPath(((Component)c).transform, sourcePrefab.transform)).ToArray();
        foreach (var srcCollider in srcColliders)
        {
            var srcTr = ((Component)srcCollider).transform;
            string path = GetTransformPath(srcTr, sourcePrefab.transform);
            var tgtTransform = destinationPrefab.transform.Find(path);
            if (tgtTransform == null) continue;
            if (tgtTransform.GetComponent<T>() != null) continue;
            var tgtCollider = tgtTransform.gameObject.AddComponent<T>();
            CopyMagicaColliderComponents(srcCollider, tgtCollider);
        }
    }

    void CopyMagicaColliderComponents(Component src, Component tgt)
    {
        var fields = src.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        foreach (var field in fields)
        {
            var value = field.GetValue(src);
            if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
            {
                Object refObj = value as Object;
                if (refObj == null) { field.SetValue(tgt, null); continue; }
                if (refObj is Transform tr)
                {
                    string refPath = GetTransformPath(tr, sourcePrefab.transform);
                    var tgtTr = destinationPrefab.transform.Find(refPath);
                    if (tgtTr != null)
                        field.SetValue(tgt, tgtTr);
                }
                else if (refObj is GameObject go)
                {
                    string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
                    var tgtGo = destinationPrefab.transform.Find(refPath);
                    if (tgtGo != null)
                        field.SetValue(tgt, tgtGo.gameObject);
                }
                else if (refObj is Component comp)
                {
                    string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
                    var tgtGo = destinationPrefab.transform.Find(refPath);
                    if (tgtGo != null)
                    {
                        var tgtComps = tgtGo.GetComponents(comp.GetType());
                        int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
                        if (compIdx >= 0 && compIdx < tgtComps.Length)
                            field.SetValue(tgt, tgtComps[compIdx]);
                    }
                }
                else
                {
                    // 기타 UnityEngine.Object 타입은 그대로 복사
                    field.SetValue(tgt, refObj);
                }
            }
            else
            {
                // 값 타입, enum, string 등은 그대로 복사
                field.SetValue(tgt, value);
            }
        }
    }

    void MoveSpringBonesAndMagicaCloth()
    {
        // VRMSpringBone
        var srcSpringBones = sourcePrefab.GetComponentsInChildren<VRMSpringBone>(true)
            .OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
        foreach (var srcSpringBone in srcSpringBones)
        {
            string path = GetTransformPath(srcSpringBone.transform, sourcePrefab.transform);
            var tgtTransform = destinationPrefab.transform.Find(path);
            if (tgtTransform == null) continue;
            var tgtSpringBone = tgtTransform.gameObject.AddComponent<VRMSpringBone>();
            CopyVRMSpringBoneComponents(srcSpringBone, tgtSpringBone);
            DestroyImmediate(srcSpringBone);
        }
        // MagicaCloth
        var srcMagicaCloths = sourcePrefab.GetComponentsInChildren<MagicaClothType>(true)
            .OrderBy(s => GetTransformPath(s.transform, sourcePrefab.transform)).ToArray();
        foreach (var srcCloth in srcMagicaCloths)
        {
            string path = GetTransformPath(srcCloth.transform, sourcePrefab.transform);
            var tgtTransform = destinationPrefab.transform.Find(path);
            if (tgtTransform == null) continue;
            var tgtCloth = tgtTransform.gameObject.AddComponent<MagicaClothType>();
            CopyMagicaClothComponents(srcCloth, tgtCloth);
            DestroyImmediate(srcCloth);
        }
        Debug.Log("VRMSpringBone & MagicaCloth 옮기기가 완료되었습니다.");
    }

    void CopyColliderGroupParameters(VRMSpringBoneColliderGroup source, VRMSpringBoneColliderGroup target)
    {
        target.Colliders = source.Colliders.Select(c => new VRMSpringBoneColliderGroup.SphereCollider
        {
            Offset = c.Offset,
            Radius = c.Radius
        }).ToArray();
    }

    void CopyVRMSpringBoneComponents(VRMSpringBone original, VRMSpringBone copy)
    {
        copy.m_comment = original.m_comment;
        copy.m_stiffnessForce = original.m_stiffnessForce;
        copy.m_gravityPower = original.m_gravityPower;
        copy.m_gravityDir = original.m_gravityDir;
        copy.m_dragForce = original.m_dragForce;
        copy.m_center = FindCorrespondingTransform(original.m_center, destinationPrefab.transform);
        copy.m_hitRadius = original.m_hitRadius;
        copy.m_updateType = original.m_updateType;

        copy.RootBones = original.RootBones
            .Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
            .Where(t => t != null)
            .ToList();

        copy.ColliderGroups = original.ColliderGroups
            .Select(colliderGroup => FindCorrespondingColliderGroup(colliderGroup, destinationPrefab.transform))
            .Where(cg => cg != null)
            .ToArray();
    }

    Transform FindCorrespondingTransform(Transform original, Transform searchRoot)
    {
        if (original == null) return null;
        var path = GetTransformPath(original, sourcePrefab.transform);
        return searchRoot.Find(path);
    }

    string GetTransformPath(Transform current, Transform root)
    {
        if (current == root) return "";
        var path = current.name;
        while (current.parent != null && current.parent != root)
        {
            current = current.parent;
            path = current.name + "/" + path;
        }
        return path;
    }

    VRMSpringBoneColliderGroup FindCorrespondingColliderGroup(VRMSpringBoneColliderGroup original, Transform searchRoot)
    {
        if (original == null) return null;
        var path = GetTransformPath(original.transform, sourcePrefab.transform);
        var correspondingTransform = searchRoot.Find(path);
        return correspondingTransform != null ? correspondingTransform.GetComponent<VRMSpringBoneColliderGroup>() : null;
    }

    void CopyMagicaClothComponents(MagicaClothType src, MagicaClothType tgt)
    {
        var fields = typeof(MagicaClothType).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        foreach (var field in fields)
        {
            var value = field.GetValue(src);
            // ClothType 우선 복사 및 분기 처리
            if (field.Name == "serializeData" && value is MagicaCloth2.ClothSerializeData sdata)
            {
                var tdata = new MagicaCloth2.ClothSerializeData();
                // ClothType을 먼저 복사
                tdata.clothType = sdata.clothType;
                // ClothType에 따라 분기
                if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.MeshCloth)
                {
                    // sourceRenderers 복사
                    tdata.sourceRenderers = sdata.sourceRenderers
                        .Select(r => {
                            if (r == null) return null;
                            string path = GetTransformPath(r.transform, sourcePrefab.transform);
                            var tgtTr = destinationPrefab.transform.Find(path);
                            return tgtTr ? tgtTr.GetComponent<Renderer>() : null;
                        })
                        .Where(r => r != null)
                        .ToList();
                }
                else if (sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneCloth || sdata.clothType == MagicaCloth2.ClothProcess.ClothType.BoneSpring)
                {
                    // rootBones 복사
                    tdata.rootBones = sdata.rootBones
                        .Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
                        .Where(t => t != null)
                        .ToList();
                }
                // colliderCollisionConstraint 복사
                if (sdata.colliderCollisionConstraint != null)
                {
                    var srcCol = sdata.colliderCollisionConstraint;
                    var tgtCol = new MagicaCloth2.ColliderCollisionConstraint.SerializeData();
                    tgtCol.mode = srcCol.mode;
                    tgtCol.friction = srcCol.friction;
                    tgtCol.limitDistance = srcCol.limitDistance;
                    // colliderList 변환
                    tgtCol.colliderList = srcCol.colliderList
                        .Select(c => {
                            if (c == null) return null;
                            string path = GetTransformPath(c.transform, sourcePrefab.transform);
                            var tgtTr = destinationPrefab.transform.Find(path);
                            return tgtTr ? tgtTr.GetComponent<MagicaCloth2.ColliderComponent>() : null;
                        })
                        .Where(c => c != null)
                        .ToList();
                    // collisionBones 변환
                    tgtCol.collisionBones = srcCol.collisionBones
                        .Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
                        .Where(t => t != null)
                        .ToList();
                    tdata.colliderCollisionConstraint = tgtCol;
                }
                // 공통 필드 복사
                tdata.paintMaps = new List<Texture2D>(sdata.paintMaps);
                tdata.paintMode = sdata.paintMode;
                tdata.connectionMode = sdata.connectionMode;
                tdata.rotationalInterpolation = sdata.rotationalInterpolation;
                tdata.rootRotation = sdata.rootRotation;
                tdata.updateMode = sdata.updateMode;
                tdata.animationPoseRatio = sdata.animationPoseRatio;
                tdata.reductionSetting = sdata.reductionSetting;
                tdata.customSkinningSetting = sdata.customSkinningSetting;
                tdata.normalAlignmentSetting = sdata.normalAlignmentSetting;
                tdata.cullingSettings = sdata.cullingSettings;
                tdata.normalAxis = sdata.normalAxis;
                tdata.gravity = sdata.gravity;
                tdata.gravityDirection = sdata.gravityDirection;
                tdata.gravityFalloff = sdata.gravityFalloff;
                tdata.stablizationTimeAfterReset = sdata.stablizationTimeAfterReset;
                tdata.blendWeight = sdata.blendWeight;
                tdata.damping = sdata.damping;
                tdata.radius = sdata.radius;
                // 기타 값들도 필요시 추가 복사
                field.SetValue(tgt, tdata);
                continue;
            }
            if (field.Name == "serializeData2" && value is MagicaCloth2.ClothSerializeData2 sdata2)
            {
                var tdata2 = new MagicaCloth2.ClothSerializeData2();
                // boneAttributeDict 변환
                tdata2.boneAttributeDict = sdata2.boneAttributeDict.ToDictionary(
                    kvp => FindCorrespondingTransform(kvp.Key, destinationPrefab.transform),
                    kvp => kvp.Value
                );
                // selectionData 등 나머지 값은 그대로 복사
                tdata2.selectionData = sdata2.selectionData;
                tdata2.preBuildData = sdata2.preBuildData;
                field.SetValue(tgt, tdata2);
                continue;
            }
            // customSkinningSetting.skinningBones 변환
            if (field.Name == "customSkinningSetting" && value != null)
            {
                var srcSkin = value;
                var skinField = value.GetType().GetField("skinningBones");
                if (skinField != null)
                {
                    var srcList = skinField.GetValue(srcSkin) as List<Transform>;
                    if (srcList != null)
                    {
                        var tgtList = srcList
                            .Select(bone => FindCorrespondingTransform(bone, destinationPrefab.transform))
                            .Where(t => t != null)
                            .ToList();
                        skinField.SetValue(srcSkin, tgtList);
                    }
                }
                field.SetValue(tgt, srcSkin);
                continue;
            }
            // 이하 기존 로직
            if (typeof(UnityEngine.Object).IsAssignableFrom(field.FieldType))
            {
                Object refObj = value as Object;
                if (refObj == null) { field.SetValue(tgt, null); continue; }
                if (refObj is Transform tr)
                {
                    string refPath = GetTransformPath(tr, sourcePrefab.transform);
                    var tgtTr = destinationPrefab.transform.Find(refPath);
                    if (tgtTr != null)
                        field.SetValue(tgt, tgtTr);
                }
                else if (refObj is GameObject go)
                {
                    string refPath = GetTransformPath(go.transform, sourcePrefab.transform);
                    var tgtGo = destinationPrefab.transform.Find(refPath);
                    if (tgtGo != null)
                        field.SetValue(tgt, tgtGo.gameObject);
                }
                else if (refObj is Component comp)
                {
                    string refPath = GetTransformPath(comp.gameObject.transform, sourcePrefab.transform);
                    var tgtGo = destinationPrefab.transform.Find(refPath);
                    if (tgtGo != null)
                    {
                        var tgtComps = tgtGo.GetComponents(comp.GetType());
                        int compIdx = comp.gameObject.GetComponents(comp.GetType()).ToList().IndexOf(comp);
                        if (compIdx >= 0 && compIdx < tgtComps.Length)
                            field.SetValue(tgt, tgtComps[compIdx]);
                    }
                }
                else
                {
                    // 기타 UnityEngine.Object 타입은 그대로 복사
                    field.SetValue(tgt, refObj);
                }
            }
            else
            {
                // 값 타입, enum, string 등은 그대로 복사
                field.SetValue(tgt, value);
            }
        }
    }

    void CopyConstraints()
    {
        var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
        foreach (var srcTr in srcTransforms)
        {
            string path = GetTransformPath(srcTr, sourcePrefab.transform);
            var tgtTr = destinationPrefab.transform.Find(path);
            if (tgtTr == null) continue;

            // PositionConstraint
            var srcPos = srcTr.GetComponent<PositionConstraint>();
            if (srcPos != null)
                CopyConstraintComponent<PositionConstraint>(srcPos, tgtTr);

            // RotationConstraint
            var srcRot = srcTr.GetComponent<RotationConstraint>();
            if (srcRot != null)
                CopyConstraintComponent<RotationConstraint>(srcRot, tgtTr);

            // ParentConstraint
            var srcParent = srcTr.GetComponent<ParentConstraint>();
            if (srcParent != null)
                CopyConstraintComponent<ParentConstraint>(srcParent, tgtTr);
        }
        Debug.Log("Constraint 값 복사가 완료되었습니다.");
    }

    void CopyConstraintComponent<T>(T srcConstraint, Transform tgtTr) where T : Behaviour
    {
        // 이미 있으면 삭제 후 새로 추가
        var tgtConstraint = tgtTr.GetComponent<T>();
        if (tgtConstraint != null)
            DestroyImmediate(tgtConstraint);
        tgtConstraint = tgtTr.gameObject.AddComponent<T>();

        if (srcConstraint is PositionConstraint srcPos && tgtConstraint is PositionConstraint tgtPos)
        {
            tgtPos.weight = srcPos.weight;
            tgtPos.constraintActive = srcPos.constraintActive;
            tgtPos.locked = srcPos.locked;
            for (int i = 0; i < srcPos.sourceCount; i++)
            {
                var src = srcPos.GetSource(i);
                var srcTr = src.sourceTransform;
                if (srcTr == null) continue;
                string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
                var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
                if (tgtSourceTr == null) continue;
                var newSource = src;
                newSource.sourceTransform = tgtSourceTr;
                tgtPos.AddSource(newSource);
            }
            tgtPos.translationAtRest = srcPos.translationAtRest;
            tgtPos.translationOffset = srcPos.translationOffset;
            tgtPos.translationAxis = srcPos.translationAxis;
        }
        else if (srcConstraint is RotationConstraint srcRot && tgtConstraint is RotationConstraint tgtRot)
        {
            tgtRot.weight = srcRot.weight;
            tgtRot.constraintActive = srcRot.constraintActive;
            tgtRot.locked = srcRot.locked;
            for (int i = 0; i < srcRot.sourceCount; i++)
            {
                var src = srcRot.GetSource(i);
                var srcTr = src.sourceTransform;
                if (srcTr == null) continue;
                string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
                var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
                if (tgtSourceTr == null) continue;
                var newSource = src;
                newSource.sourceTransform = tgtSourceTr;
                tgtRot.AddSource(newSource);
            }
            tgtRot.rotationAtRest = srcRot.rotationAtRest;
            tgtRot.rotationOffset = srcRot.rotationOffset;
            tgtRot.rotationAxis = srcRot.rotationAxis;
        }
        else if (srcConstraint is ParentConstraint srcParent && tgtConstraint is ParentConstraint tgtParent)
        {
            tgtParent.weight = srcParent.weight;
            tgtParent.constraintActive = srcParent.constraintActive;
            tgtParent.locked = srcParent.locked;
            for (int i = 0; i < srcParent.sourceCount; i++)
            {
                var src = srcParent.GetSource(i);
                var srcTr = src.sourceTransform;
                if (srcTr == null) continue;
                string srcPath = GetTransformPath(srcTr, sourcePrefab.transform);
                var tgtSourceTr = destinationPrefab.transform.Find(srcPath);
                if (tgtSourceTr == null) continue;
                var newSource = src;
                newSource.sourceTransform = tgtSourceTr;
                tgtParent.AddSource(newSource);
                // Offset도 복사
                tgtParent.SetTranslationOffset(i, srcParent.GetTranslationOffset(i));
                tgtParent.SetRotationOffset(i, srcParent.GetRotationOffset(i));
            }
            tgtParent.translationAtRest = srcParent.translationAtRest;
            tgtParent.rotationAtRest = srcParent.rotationAtRest;
            tgtParent.translationAxis = srcParent.translationAxis;
            tgtParent.rotationAxis = srcParent.rotationAxis;
        }
    }

    void CopyBlendShapes()
    {
        var srcRenderers = sourcePrefab.GetComponentsInChildren<SkinnedMeshRenderer>(true);
        foreach (var srcRenderer in srcRenderers)
        {
            string path = GetTransformPath(srcRenderer.transform, sourcePrefab.transform);
            var tgtTransform = destinationPrefab.transform.Find(path);
            if (tgtTransform == null) continue;
            var tgtRenderer = tgtTransform.GetComponent<SkinnedMeshRenderer>();
            if (tgtRenderer == null) continue;
            int blendShapeCount = srcRenderer.sharedMesh != null ? srcRenderer.sharedMesh.blendShapeCount : 0;
            for (int i = 0; i < blendShapeCount; i++)
            {
                string shapeName = srcRenderer.sharedMesh.GetBlendShapeName(i);
                int tgtIndex = tgtRenderer.sharedMesh != null ? tgtRenderer.sharedMesh.GetBlendShapeIndex(shapeName) : -1;
                if (tgtIndex >= 0)
                {
                    float value = srcRenderer.GetBlendShapeWeight(i);
                    tgtRenderer.SetBlendShapeWeight(tgtIndex, value);
                }
            }
        }
        Debug.Log("BlendShape 값 복사가 완료되었습니다.");
    }

    void CopyActiveStates()
    {
        var srcTransforms = sourcePrefab.GetComponentsInChildren<Transform>(true);
        foreach (var srcTr in srcTransforms)
        {
            string path = GetTransformPath(srcTr, sourcePrefab.transform);
            var tgtTr = destinationPrefab.transform.Find(path);
            if (tgtTr == null) continue;
            tgtTr.gameObject.SetActive(srcTr.gameObject.activeSelf);
        }
        Debug.Log("ActiveState 복사가 완료되었습니다.");
    }
} 