using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using RootMotion.FinalIK;

namespace KindRetargeting
{
    [DefaultExecutionOrder(3)]
    public class LimbWeightController : MonoBehaviour
    {
        [Header("거리 기반 가중치 설정")]
        [SerializeField, Range(0.3f, 1f)] public float maxDistance = 0.5f;  // 가중치가 0이 되는 최대 거리
        [SerializeField, Range(0.05f, 0.3f)] public float minDistance = 0.1f;  // 가중치가 1이 되는 최소 거리

        [Header("마스터 가중치 변화 속도")]
        [SerializeField, Range(0.1f, 20f)] public float weightSmoothSpeed = 10f;  // 가중치 변화 속도

        [Header("허리 가중치 설정")]
        [SerializeField, Range(0.1f, 1f)] public float hipsMinDistance = 0.2f;  // 최소 거리 (가중치 1)
        [SerializeField, Range(0.1f, 1f)] public float hipsMaxDistance = 0.6f;      // 최대 거리 (가중치 0)

        [Header("바닥 기준 히프 보정")]
        [SerializeField, Range(0.3f, 1f)] public float groundHipsMinHeight = 0f;  // 최소 높이 (가중치 0)
        [SerializeField, Range(0.5f, 1.5f)] public float groundHipsMaxHeight = 0.5f;  // 최대 높이 (가중치 1)

        [Header("발 높이 기반 가중치 설정")]
        [SerializeField, Range(0.1f, 0.5f)] public float footHeightMinThreshold = 0.2f;  // 최소 높이 (가중치 1)
        [SerializeField, Range(0.5f, 1.0f)] public float footHeightMaxThreshold = 0.5f;  // 최대 높이 (가중치 0)

        private FullBodyInverseKinematics_RND fbik;

        private CustomRetargetingScript crs;
        private Dictionary<string, Dictionary<int, float>> weightLayers = new Dictionary<string, Dictionary<int, float>>();

        List<float> leftArmEndWeights = new List<float>();
        List<float> rightArmEndWeights = new List<float>();
        List<float> leftLegEndWeights = new List<float>();
        List<float> rightLegEndWeights = new List<float>();

                List<float> leftLegBendWeights = new List<float>();
        List<float> rightLegBendWeights = new List<float>();

        private float MasterleftArmEndWeights = 0f;
        private float MasterrightArmEndWeights = 0f;
        private float MasterleftLegEndWeights = 0f;
        private float MasterrightLegEndWeights = 0f;
        private float MasterleftLegBendWeights = 0f;
        private float MasterrightLegBendWeights = 0f;

        public List<Transform> props = new List<Transform>();

        public Transform characterRoot;

        // 힙스 가중치 리스트 추가
        List<float> hipsWeights = new List<float>();
        private float MasterHipsWeight = 1f;

        void Update()
        {
            //손의 거리를 기반으로한 가중치 적용
            HandDistances();

            //프랍과의 거리에 따른 가중치 적용
            PropDistances();

            // 마스터 변수에 가중치 적용
            FinalizeWeights();

            // 앉아있을 때 다리와의 거리에 따른 가중치 적용
            SitLegDistances();

            // 의자 타입 프랍과의 거리에 따른 가중치 적용
            SitChairDistances();

            // 바닥 기준 히프 보정 적용
            UpdateGroundBasedHipsWeight();

            // 발 높이에 따른 가중치 적용
            UpdateFootHeightBasedWeight();

            // FBIK에 최종 가중치 적용
            ApplyWeightsToFBIK();
        }

        void Start()
        {
            fbik = GetComponent<FullBodyInverseKinematics_RND>();

            crs = GetComponent<CustomRetargetingScript>();

            InitWeightLayers();

            //프랍 오브젝트 찾기
            props = FindObjectsByType<PropTypeController>(FindObjectsSortMode.None).Select(controller => controller.transform).ToList();

            // 프랍 오브젝트 찾기   
            Gethnad();

            //HandDistances();에서 사용을 위한 리스트 추가
            //손 거리에 따른 웨이트 업데이트 인덱스 0번
            leftArmEndWeights.Add(0);
            rightArmEndWeights.Add(0);

            // 프랍과의 거리에 따른 웨이트 업데이트 인덱스 1번
            leftArmEndWeights.Add(0);
            rightArmEndWeights.Add(0);

            // 앉아있을 때 다리와의 거리에 따른 가중치 적용 인덱스 0번
            leftLegEndWeights.Add(0);
            rightLegEndWeights.Add(0);

            // 다리 골 가중치 초기화
            leftLegBendWeights.Add(1f);  // 기본 가중치
            rightLegBendWeights.Add(1f); // 기본 가중치

            // CharacterController가 있는 루트 오브젝트 찾기


            if (characterRoot == null)
            {
                characterRoot = transform;
            }

            // 힙스 가중치 초기화 인덱스 0번
            hipsWeights.Add(1f);  // 의자 거리 기반 가중치

            // 지면 높이 기반 가중치 초기화 인덱스 1번
            hipsWeights.Add(1f);  // 지면 높이 기반 가중치

            // 발 높이 기반 가중치 초기화 인덱스 1번        
            leftLegEndWeights.Add(1f);
            rightLegEndWeights.Add(1f);
        }

        private void Gethnad()
        {
            //손탐지 로직 넣기
        }

        private void InitWeightLayers()
        {
            fbik.solver.leftArm.positionWeight = 0f;
            fbik.solver.leftArm.rotationWeight = 0f;
            fbik.solver.leftArm.bendGoalWeight = 0f;
            fbik.solver.rightArm.positionWeight = 0f;
            fbik.solver.rightArm.rotationWeight = 0f;
            fbik.solver.rightArm.bendGoalWeight = 0f;
        }

        /// <summary>
        /// 타겟과의 거리에 따라 가중치를 업데이트합니다.
        /// </summary>
        private void HandDistances()
        {
            if (fbik == null || fbik == null) return;

            // 왼쪽 팔 가중치 업데이트
            if (fbik.solver.leftArm.target != null && fbik.solver.rightArm.target != null)
            {
                Transform leftHandTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.LeftHand);
                Transform rightHandTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.RightHand);
                if (leftHandTransform != null && rightHandTransform != null)
                {
                    float Distance = Vector3.Distance(leftHandTransform.position, rightHandTransform.position);
                    float Weight = 1f - Mathf.Clamp01((Distance - minDistance) / (maxDistance - minDistance));
                    leftArmEndWeights[0] = Weight;
                    rightArmEndWeights[0] = Weight;
                }
            }
        }

        void SitLegDistances()
        {
            if (fbik == null || fbik == null) return;

            Transform hips = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips);
            if (hips == null) return;

            // 캐릭터 루트 기준으로 히프의 로컬 높이 계산
            float hipHeight = characterRoot.InverseTransformPoint(hips.position).y;
            float normalStandingHeight = 0.9f;
            float sitThreshold = 0.6f;

            // 기본적으로 다리 가중치를 1로 설정
            while (leftLegEndWeights.Count <= 0) leftLegEndWeights.Add(1f);
            while (rightLegEndWeights.Count <= 0) rightLegEndWeights.Add(1f);
            leftLegEndWeights[0] = 1f;
            rightLegEndWeights[0] = 1f;

            // 히프가 충분히 낮을 때만 가중치 감소 처리
            if (hipHeight < sitThreshold * normalStandingHeight)
            {
                // 왼쪽 다리 처리
                ProcessSitLegWeight(
                    hips,
                    fbik.solver.leftLeg.target,
                    leftLegEndWeights,
                    0  // 레이어 인덱스
                );

                // 오른쪽 다리 처리
                ProcessSitLegWeight(
                    hips,
                    fbik.solver.rightLeg.target,
                    rightLegEndWeights,
                    0  // 레이어 인덱스
                );
            }
        }

        private void ProcessSitLegWeight(Transform hips, Transform footTarget, List<float> weightList, int weightIndex)
        {
            if (footTarget == null) return;

            // 히프와 발 타겟 사이의 수평 거리 계산 (Y축 제외)
            Vector3 hipPos = hips.position;
            Vector3 footPos = footTarget.position;
            float horizontalDistance = Vector3.Distance(hipPos, footPos);

            // 다리 길이를 기준으로 최대/최소 허용 거리 설정
            float maxLegDistance = 0.8f; // 다리 길이의 80%를 최대 거리로 설정
            float minLegDistance = 0.5f; // 다리 길이의 30%를 최소 거리로 설정

            // 거리가 멀수록 가중치 감소
            float weight = 1f - Mathf.Clamp01((horizontalDistance - minLegDistance) / (maxLegDistance - minLegDistance));

            // 계산된 가중치 설정
            weightList[weightIndex] = weight;
        }

        void PropDistances()
        {
            if (fbik == null || fbik == null) return;

            // 프랍과의 거리에 따른 웨이트 업데이트
            Transform leftHandTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.LeftHand);
            Transform rightHandTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.RightHand);

            if (leftHandTransform != null && rightHandTransform != null && props != null)
            {
                // 왼손과 프랍 사이의 최소 거리 계산
                float minLeftDistance = float.MaxValue;
                foreach (Transform prop in props)
                {
                    float distance = Vector3.Distance(leftHandTransform.position, prop.position);
                    minLeftDistance = Mathf.Min(minLeftDistance, distance);
                }

                // 오른손과 프랍 사이의 최소 거리 계산 
                float minRightDistance = float.MaxValue;
                foreach (Transform prop in props)
                {
                    float distance = Vector3.Distance(rightHandTransform.position, prop.position);
                    minRightDistance = Mathf.Min(minRightDistance, distance);
                }

                // 거리에 따른 가중치 계산
                float leftWeight = 1f - Mathf.Clamp01((minLeftDistance - minDistance) / (maxDistance - minDistance));
                float rightWeight = 1f - Mathf.Clamp01((minRightDistance - minDistance) / (maxDistance - minDistance));

                leftArmEndWeights[1] = leftWeight;
                rightArmEndWeights[1] = rightWeight;
            }
        }

        void SitChairDistances()
        {
            if (crs == null) return;

            Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips);
            if (hipsTransform != null && props != null)
            {
                float minDistance = float.MaxValue;
                foreach (Transform prop in props)
                {
                    PropTypeController ptc = prop.GetComponent<PropTypeController>();
                    if (ptc != null && ptc.propType == EnumsList.PropType.Chair)
                    {
                        float distance = Vector3.Distance(hipsTransform.position, prop.childCount > 0 ? prop.GetChild(0).position : prop.position);
                        minDistance = Mathf.Min(minDistance, distance);
                    }
                }

                float t = Mathf.Clamp01((minDistance - hipsMinDistance) / (hipsMaxDistance - hipsMinDistance));
                hipsWeights[0] = t;  // 직접 HipsWeightOffset 수정 대신 배열에 저장
            }
        }

        /// <summary>
        /// 각 리스트의 최대 가중치 값을 마스터 변수에 부드럽게 설정합니다.
        /// </summary>
        public void FinalizeWeights()
        {
            float deltaTime = Time.deltaTime;

            // 팔 가중치 계산 (최대값 사용)
            MasterleftArmEndWeights = Mathf.Lerp(
                MasterleftArmEndWeights,
                GetMaxValue(leftArmEndWeights),
                weightSmoothSpeed * deltaTime
            );
            MasterrightArmEndWeights = Mathf.Lerp(
                MasterrightArmEndWeights,
                GetMaxValue(rightArmEndWeights),
                weightSmoothSpeed * deltaTime
            );

            // 다리 가중치 계산 (최소값 사용)
            MasterleftLegEndWeights = Mathf.Lerp(
                MasterleftLegEndWeights,
                GetMinValue(leftLegEndWeights),
                weightSmoothSpeed * deltaTime
            );
            MasterrightLegEndWeights = Mathf.Lerp(
                MasterrightLegEndWeights,
                GetMinValue(rightLegEndWeights),
                weightSmoothSpeed * deltaTime
            );

            // 다리 골 가중치 계산
            MasterleftLegBendWeights = Mathf.Lerp(
                MasterleftLegBendWeights,
                GetMinValue(leftLegBendWeights),
                weightSmoothSpeed * deltaTime
            );
            MasterrightLegBendWeights = Mathf.Lerp(
                MasterrightLegBendWeights,
                GetMinValue(rightLegBendWeights),
                weightSmoothSpeed * deltaTime
            );

            // 힙스 마스터 가중치 업데이트
            MasterHipsWeight = Mathf.Lerp(
                MasterHipsWeight,
                GetMinValue(hipsWeights),
                weightSmoothSpeed * deltaTime
            );

            // CustomRetargetingScript에 최종 가중치 전달
            if (crs != null)
            {
                crs.HipsWeightOffset = MasterHipsWeight;
            }
        }

        /// <summary>
        /// 리스트에서 최대값을 찾습니다.
        /// </summary>
        private float GetMaxValue(List<float> list)
        {
            if (list.Count == 0) return 0f;

            float max = list[0];
            for (int i = 1; i < list.Count; i++)
            {
                if (list[i] > max)
                {
                    max = list[i];
                }
            }
            return max;
        }


        /// <summary>
        /// 리스트에서 최소값을 찾습니다.
        /// </summary>
        private float GetMinValue(List<float> list)
        {
            if (list.Count == 0) return 0f;

            float min = list[0];
            for (int i = 1; i < list.Count; i++)
            {
                if (list[i] < min)
                {
                    min = list[i];
                }
            }
            return min;
        }

        /// <summary>
        /// 마스터 가중치 값을 FBIK에 적용합니다.
        /// </summary>
        private void ApplyWeightsToFBIK()
        {
            if (fbik == null) return;

            // 왼쪽 팔 가중치 적용
            if (fbik.solver.leftArm != null)
            {
                if (fbik.solver.leftArm.target != null)
                {
                    fbik.solver.leftArm.positionWeight = MasterleftArmEndWeights;
                    fbik.solver.leftArm.rotationWeight = MasterleftArmEndWeights;
                }

                if (fbik.solver.leftArm.bendGoal != null)
                    fbik.solver.leftArm.bendGoalWeight = MasterleftArmEndWeights;
            }

            // 오른쪽 팔 가중치 적용
            if (fbik.solver.rightArm != null)
            {
                if (fbik.solver.rightArm.target != null)
                {
                    fbik.solver.rightArm.positionWeight = MasterrightArmEndWeights;
                    fbik.solver.rightArm.rotationWeight = MasterrightArmEndWeights;
                }

                if (fbik.solver.rightArm.bendGoal != null)
                    fbik.solver.rightArm.bendGoalWeight = MasterrightArmEndWeights;
            }

            // 왼쪽 다리 가중치 적용
            if (fbik.solver.leftLeg != null)
            {
                if (fbik.solver.leftLeg.target != null)
                {
                    fbik.solver.leftLeg.positionWeight = MasterleftLegEndWeights;
                    fbik.solver.leftLeg.rotationWeight = MasterleftLegEndWeights;
                }

                if (fbik.solver.leftLeg.bendGoal != null)
                    fbik.solver.leftLeg.bendGoalWeight = MasterleftLegBendWeights;  // 골 가중치 적용
            }

            // 오른쪽 다리 가중치 적용
            if (fbik.solver.rightLeg != null)
            {
                if (fbik.solver.rightLeg.target != null)
                {
                    fbik.solver.rightLeg.positionWeight = MasterrightLegEndWeights;
                    fbik.solver.rightLeg.rotationWeight = MasterrightLegEndWeights;
                }

                if (fbik.solver.rightLeg.bendGoal != null)
                    fbik.solver.rightLeg.bendGoalWeight = MasterrightLegBendWeights;  // 골 가중치 적용
            }
        }

        /// <summary>
        /// 바닥으로부터의 높이에 따라 히프 보정값을 조정합니다.
        /// </summary>
        private void UpdateGroundBasedHipsWeight()
        {
            if (crs == null || fbik == null || fbik == null) return;

            Transform hipsTransform = crs.sourceAnimator.GetBoneTransform(HumanBodyBones.Hips);
            if (hipsTransform != null)
            {
                float groundHeight = characterRoot.position.y;
                float hipsHeight = hipsTransform.position.y - groundHeight;

                float weight = Mathf.Clamp01((hipsHeight - groundHipsMinHeight) / (groundHipsMaxHeight - groundHipsMinHeight));
                hipsWeights[1] = weight;  // 직접 HipsWeightOffset 수정 대신 배열에 저장
            }
        }

        /// <summary>
        /// 발의 높이에 따라 IK 가중치를 조절합니다.
        /// </summary>
        private void UpdateFootHeightBasedWeight()
        {
            if (fbik == null || fbik == null || characterRoot == null) return;

            // 왼발 처리
            ProcessFootHeightWeight(
                fbik.solver.leftLeg.target,
                leftLegEndWeights,
                1  // 새로 추가된 레이어의 인덱스
            );

            // 오른발 처리
            ProcessFootHeightWeight(
                fbik.solver.rightLeg.target,
                rightLegEndWeights,
                1  // 새로 추가된 레이어의 인덱스
            );
        }

        private void ProcessFootHeightWeight(Transform footTarget, List<float> weightList, int weightIndex)
        {
            if (footTarget == null) return;

            // 캐릭터 루트 기준으로 발의 로컬 높이 계산
            float footHeight = characterRoot.InverseTransformPoint(footTarget.position).y;

            // 높이에 따른 가중치 계산 (높이가 높을수록 가중치 감소)
            float weight = 1f - Mathf.Clamp01((footHeight - footHeightMinThreshold) /
                                             (footHeightMaxThreshold - footHeightMinThreshold));

            // 계산된 가중치 설정
            weightList[weightIndex] = weight;
        }
    }
}
