using System;
using UnityEngine;

namespace Woop.Unity
{
    [DisallowMultipleComponent]
    [RequireComponent(typeof(WoopCutoutRig2D))]
    public sealed class WoopRigMotion2D : MonoBehaviour
    {
        [SerializeField] private WoopCutoutRig2D rig;
        [SerializeField] private WoopCharacterController2D controller;

        [Header("Rig Semantics")]
        [SerializeField] private WoopRigRoles rigRoles;

        [Header("Natural Idle")]
        [SerializeField, Range(0f, 16f)] private float idleArmSwayDegrees = 6f;
        [SerializeField, Range(0f, 10f)] private float idleLegSwayDegrees = 2.2f;
        [SerializeField, Min(0.1f)] private float idleFrequency = 1.35f;

        [Header("Procedural Walk")]
        [SerializeField, Min(0.1f)] private float walkFrequency = 1.8f;
        [SerializeField, Range(0f, 45f)] private float legSwingDegrees = 13f;
        [SerializeField, Range(0f, 35f)] private float upperArmSwingDegrees = 9f;
        [SerializeField, Range(0f, 25f)] private float lowerArmSwingDegrees = 5f;
        [SerializeField, Range(0f, 20f)] private float handSwingDegrees = 3.5f;
        [SerializeField, Range(0f, 12f)] private float hipSwayDegrees = 3f;
        [SerializeField, Min(0.1f)] private float animationSmoothing = 12f;

        [Header("Jump Pose")]
        [SerializeField, Range(0f, 60f)] private float legSpreadDegrees = 28f;

        [Header("Legacy Woop Fallback")]
        [SerializeField] private string legacyLeftLegPartId = "part_002";
        [SerializeField] private string legacyRightLegPartId = "part_001";

        private Quaternion[] restRotations = Array.Empty<Quaternion>();
        private float walkPhase;

        public WoopRigRoles RigRoles => rigRoles;

        public void Configure(WoopRigRoles importedRigRoles)
        {
            rigRoles = importedRigRoles;
        }

        private void Awake()
        {
            if (rig == null)
                rig = GetComponent<WoopCutoutRig2D>();
            if (controller == null)
                controller = GetComponent<WoopCharacterController2D>();

            CacheRestPose();
        }

        private void OnEnable()
        {
            if (rig != null && restRotations.Length != rig.Bones.Length)
                CacheRestPose();
        }

        private void LateUpdate()
        {
            if (rig == null || restRotations.Length == 0)
                return;

            float movement = controller == null ? 0f : Mathf.Clamp01(controller.MoveInput.magnitude);
            if (movement > 0.001f)
                walkPhase = Mathf.Repeat(walkPhase + Time.deltaTime * walkFrequency * Mathf.PI * 2f, Mathf.PI * 2f);

            if (rigRoles.HasMappedLimbs)
                UpdateSemanticRig(movement);
            else
                UpdateLegacyRig(movement);
        }

        private void UpdateSemanticRig(float movement)
        {
            float stride = Mathf.Sin(walkPhase) * movement;
            float idleBlend = 1f - movement;
            float idlePhase = Time.time * idleFrequency * Mathf.PI * 2f;
            float leftIdle = Mathf.Sin(idlePhase) * idleBlend;
            float rightIdle = Mathf.Sin(idlePhase + Mathf.PI) * idleBlend;
            float leftLag = Mathf.Sin(idlePhase - 0.38f) * idleBlend;
            float rightLag = Mathf.Sin(idlePhase + Mathf.PI - 0.38f) * idleBlend;
            float jump = controller == null ? 0f : controller.JumpAmount;
            float blend = 1f - Mathf.Exp(-animationSmoothing * Time.deltaTime);

            WoopCutoutRig2D.BoneBinding[] bones = rig.Bones;
            for (int index = 0; index < bones.Length && index < restRotations.Length; index++)
            {
                Transform bone = bones[index].bone;
                if (bone == null)
                    continue;

                float angle = SemanticAngle(
                    bones[index].partId,
                    stride,
                    leftIdle,
                    rightIdle,
                    leftLag,
                    rightLag,
                    jump);
                Quaternion target = restRotations[index] * Quaternion.Euler(0f, 0f, angle);
                bone.localRotation = Quaternion.Slerp(bone.localRotation, target, blend);
            }
        }

        private float SemanticAngle(
            string partId,
            float stride,
            float leftIdle,
            float rightIdle,
            float leftLag,
            float rightLag,
            float jump)
        {
            if (partId == rigRoles.leftLeg)
                return stride * legSwingDegrees + leftIdle * idleLegSwayDegrees - jump * legSpreadDegrees;
            if (partId == rigRoles.rightLeg)
                return -stride * legSwingDegrees + rightIdle * idleLegSwayDegrees + jump * legSpreadDegrees;

            if (partId == rigRoles.leftUpperArm)
                return -stride * upperArmSwingDegrees + leftIdle * idleArmSwayDegrees;
            if (partId == rigRoles.rightUpperArm)
                return stride * upperArmSwingDegrees + rightIdle * idleArmSwayDegrees;
            if (partId == rigRoles.leftLowerArm)
                return -stride * lowerArmSwingDegrees + leftLag * idleArmSwayDegrees * 0.72f;
            if (partId == rigRoles.rightLowerArm)
                return stride * lowerArmSwingDegrees + rightLag * idleArmSwayDegrees * 0.72f;
            if (partId == rigRoles.leftHand)
                return -stride * handSwingDegrees + leftLag * idleArmSwayDegrees * 0.42f;
            if (partId == rigRoles.rightHand)
                return stride * handSwingDegrees + rightLag * idleArmSwayDegrees * 0.42f;

            if (partId == rigRoles.lowerTorso)
                return stride * hipSwayDegrees + leftIdle * hipSwayDegrees * 0.3f;
            if (partId == rigRoles.upperTorso)
                return -stride * hipSwayDegrees * 0.25f - leftIdle * hipSwayDegrees * 0.12f;
            if (partId == rigRoles.head)
                return -leftIdle * idleArmSwayDegrees * 0.12f;

            return 0f;
        }

        private void UpdateLegacyRig(float movement)
        {
            float amplitude = Mathf.Lerp(4f, 6f, movement);
            float phase = Time.time * 2.4f * Mathf.Lerp(1f, 2.2f, movement);
            float jump = controller == null ? 0f : controller.JumpAmount;
            WoopCutoutRig2D.BoneBinding[] bones = rig.Bones;

            for (int index = 0; index < bones.Length && index < restRotations.Length; index++)
            {
                Transform bone = bones[index].bone;
                if (bone == null)
                    continue;

                float depthWeight = bone == rig.RootBone ? 0.28f : Mathf.Clamp01(0.55f + GetDepth(bone) * 0.18f);
                float direction = (index & 1) == 0 ? 1f : -1f;
                float angle = Mathf.Sin(phase + index * 0.73f) * amplitude * depthWeight * direction;

                if (bones[index].partId == legacyLeftLegPartId)
                    angle -= legSpreadDegrees * jump;
                else if (bones[index].partId == legacyRightLegPartId)
                    angle += legSpreadDegrees * jump;

                bone.localRotation = restRotations[index] * Quaternion.Euler(0f, 0f, angle);
            }
        }

        private void CacheRestPose()
        {
            if (rig == null)
                return;

            WoopCutoutRig2D.BoneBinding[] bones = rig.Bones;
            restRotations = new Quaternion[bones.Length];
            for (int index = 0; index < bones.Length; index++)
                restRotations[index] = bones[index].bone == null ? Quaternion.identity : bones[index].bone.localRotation;
        }

        private static int GetDepth(Transform bone)
        {
            int depth = 0;
            for (Transform current = bone.parent; current != null && depth < 8; current = current.parent)
                depth++;
            return depth;
        }
    }
}
