using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;

namespace NBF
{
    /// <summary>
    /// 用于生成并模拟基于 Verlet 积分绳索的基类。
    /// </summary>
    public class VerletRope : MonoBehaviour
    {
        #region --- 数据结构 ---

        /// <summary>
        /// 表示绳索模拟中的一个质点节点。
        /// </summary>
        [System.Serializable]
        public class RopeNode
        {
            public Vector3 position;
            public Vector3 prevPosition;
            public bool isPinned;

            public RopeNode connectedNode;
            public Vector3 connectionOffset;

            public Transform pinnedTransform;
            public Vector3 pinLocalPos;

            public RopeNode(Vector3 pos)
            {
                position = pos;
                prevPosition = pos;
                isPinned = false;
            }

            /// <summary>
            /// 将该节点固定到一个变换组件，可选本地偏移。
            /// </summary>
            /// <param name="t">目标变换组件</param>
            /// <param name="offset">本地偏移</param>
            public void PinTo(Transform t, Vector3 offset)
            {
                isPinned = true;
                pinnedTransform = t;
                pinLocalPos = offset;
            }

            /// <summary>
            /// 将该节点连接到另一条绳索节点，可选偏移量。
            /// </summary>
            /// <param name="targetNode">目标绳索节点</param>
            /// <param name="offset">本地偏移</param>
            public void ConnectToNode(RopeNode targetNode, Vector3 offset = default)
            {
                connectedNode = targetNode;
                connectionOffset = offset;
                isPinned = false;
                pinnedTransform = null;
            }

            /// <summary>
            /// 取消该节点的固定状态。
            /// </summary>
            public void Unpin()
            {
                isPinned = false;
                pinnedTransform = null;
            }

            /// <summary>
            /// 对该节点施加外力。
            /// </summary>
            /// <param name="force"></param>
            public void ApplyForce(Vector3 force)
            {
                position += force;
            }
        }

        [System.Serializable]
        public struct PinConfiguration
        {
            [Tooltip("要固定的节点索引（0 = 起点，-1 = 末尾）")] public int nodeIndex;
            public Transform targetTransform;
            public Vector3 localPos;
        }

        #endregion

        #region --- 配置 ---

        [Header("通用设置")] public bool initializeOnStart = false;
        public bool enabled = true;
        [Tooltip("绳索起点")] public Vector3 ropeStartPoint;
        [Tooltip("绳索终点（可选，若为空则绳索自由下垂）")] public Vector3 ropeEndPoint;

        [Header("节点间距与尺寸")]
        [Min(0.001f)]
        [Tooltip("相邻节点的基础间距。总长度不能整除时，余量会落在第一段。")]
        public float nodeSpacing = 0.1f;

        [Tooltip("物理检测半径（碰撞、地形、自碰撞、射线检测使用）")] [FormerlySerializedAs("ropeRadius")]
        public float ropePhysicsRadius = 0.1f;

        [Tooltip("渲染表现半径（可小于物理检测半径）")] public float ropeRenderRadius = 0.1f;

        [Header("物理参数")] public Vector3 gravity = new Vector3(0, -9.81f, 0);
        [Range(0f, 1f)] public float airFriction = 0.1f;
        [Range(0f, 1f)] public float stiffness = 1.0f;
        [Tooltip("约束求解迭代次数。值越高越硬，但开销更大")] public int constraintIterations = 8;
        [Tooltip("绳索与刚体碰撞时施加给刚体的力")] public float collisionPushPower = 5f;
        [Range(0f, 1f)] public float contactFriction = 0.5f;


        [Header("耦合设置")] public Rigidbody connectedBodyStart;
        public Rigidbody connectedBodyEnd;
        public float rigidbodyInteractionForce = 10f;

        [Header("碰撞设置")] public bool enableCollisions = true;
        public LayerMask collisionLayer;
        public bool enableSelfCollision = false;

        [Header("初始设置")] public List<PinConfiguration> initialPinPoints;

        #endregion

        #region --- 公开属性 ---

        public List<RopeNode> Nodes { get; private set; } = new List<RopeNode>();
        public float PhysicsRadius => Mathf.Max(0.001f, ropePhysicsRadius);
        public float RenderRadius => Mathf.Max(0.0001f, ropeRenderRadius);

        #endregion

        #region --- 私有变量 ---

        private float _segmentLength;
        private float _firstSegmentLength;
        private Collider[] _collisionBuffer = new Collider[10];
        private List<Collider> _ignoreColliders = new List<Collider>();
        private Vector3 _connectedBodyEndLocalPos;

        private IRopeRenderer _ropeRenderer;

        #endregion

        #region --- 初始化 ---

        private void OnEnable()
        {
            if (enabled)
                VerletPhysics.RegisterRopeGenerator(this);
        }

        private void OnDisable()
        {
            VerletPhysics.UnregisterRopeGenerator(this);
        }

        private void Awake()
        {
            ResolveRenderer();
        }

        private void Start()
        {
            if (initializeOnStart && enabled)
                InitializeRope();
        }

        private void ResolveRenderer()
        {
            if (_ropeRenderer != null) return;
            var behaviours = GetComponents<MonoBehaviour>();
            foreach (var behaviour in behaviours)
            {
                if (behaviour is not IRopeRenderer ropeRenderer) continue;
                _ropeRenderer = ropeRenderer;
                break;
            }

            if (_ropeRenderer == null)
                Debug.LogWarning("未找到 IRopeRenderer 实现，绳索将不会渲染。请在预制体上绑定渲染组件。", this);
        }

        private int ConfigureConstraintLayout(float totalLength)
        {
            totalLength = Mathf.Max(0.001f, totalLength);
            _segmentLength = Mathf.Max(0.001f, nodeSpacing);

            var segmentCount = CalculateSegmentCount(totalLength, _segmentLength);
            _firstSegmentLength = CalculateFirstSegmentLength(totalLength, segmentCount, _segmentLength);

            return segmentCount;
        }

        private static int CalculateSegmentCount(float totalLength, float spacing)
        {
            totalLength = Mathf.Max(0.001f, totalLength);
            spacing = Mathf.Max(0.001f, spacing);

            var fullSegmentCount = Mathf.FloorToInt(totalLength / spacing);
            var remainder = totalLength - fullSegmentCount * spacing;

            return remainder > 0.0001f
                ? Mathf.Max(1, fullSegmentCount + 1)
                : Mathf.Max(1, fullSegmentCount);
        }

        private static float CalculateFirstSegmentLength(float totalLength, int segmentCount, float spacing)
        {
            totalLength = Mathf.Max(0.001f, totalLength);
            spacing = Mathf.Max(0.001f, spacing);

            if (segmentCount <= 1)
                return totalLength;

            var firstSegmentLength = totalLength - spacing * (segmentCount - 1);
            return firstSegmentLength > 0.0001f ? firstSegmentLength : spacing;
        }

        private float GetConstraintLength(int segmentIndex)
        {
            if (segmentIndex <= 0)
                return _firstSegmentLength > 0f ? _firstSegmentLength : _segmentLength;

            return _segmentLength;
        }

        /// <summary>
        /// 根据参数从零创建绳索。
        /// </summary>
        public void InitializeRope()
        {
            ResolveRenderer();
            _ropeRenderer?.Clear();
            _ropeRenderer?.Bind(this);
            Nodes.Clear();
            _ignoreColliders.Clear();

            var startPos = ropeStartPoint;
            var endPos = ropeEndPoint;

            if (Vector3.Distance(startPos, endPos) < 0.01f)
                endPos = startPos + Vector3.forward * 5f;

            var totalDistance = Vector3.Distance(startPos, endPos);
            var segmentCount = ConfigureConstraintLayout(totalDistance);

            var direction = (endPos - startPos).normalized;
            Nodes.Add(new RopeNode(startPos));

            var traversedDistance = 0f;
            for (var i = 0; i < segmentCount; i++)
            {
                traversedDistance += GetConstraintLength(i);
                var pos = i == segmentCount - 1 ? endPos : startPos + direction * traversedDistance;
                Nodes.Add(new RopeNode(pos));
            }

            // 应用初始固定点配置
            ApplyInitialPins();

            if (connectedBodyStart)
                _ignoreColliders.AddRange(connectedBodyStart.GetComponentsInChildren<Collider>());
            if (connectedBodyEnd)
                _ignoreColliders.AddRange(connectedBodyEnd.GetComponentsInChildren<Collider>());

            _ropeRenderer?.Rebuild();
        }

        private void ApplyInitialPins()
        {
            if (initialPinPoints == null) return;

            foreach (var pin in initialPinPoints)
            {
                var index = pin.nodeIndex;
                if (index < 0)
                    index = Nodes.Count + index;

                if (index >= 0 && index < Nodes.Count && pin.targetTransform)
                    PinNode(index, pin.targetTransform, pin.localPos);
            }
        }

        #endregion

        #region --- 核心物理循环 ---

        private void LateUpdate()
        {
            if (!enabled) return;
            _ropeRenderer?.Render();
        }

        private void FixedUpdate()
        {
            if (!enabled) return;
            if (Nodes.Count < 2) return;

            SimulateVerlet();

            var solverIterations = GetConstraintSolverIterations();
            for (var i = 0; i < solverIterations; i++)
            {
                ApplyConstraints();

                ApplyInterRopeConnection();
                // 如需强制同步连接节点，可启用下一行

                // 每次迭代都检查碰撞
                if (enableCollisions)
                    ResolveCollisions();
            }

            ApplyInterRopeConnection();
            ApplyConstraints();
            ClampOverstretchedSegments();
            SyncConnectedEndRigidbodyToRope();

            ApplyCouplingForces();
        }

        /// <summary>
        /// 跟随已连接节点并进行双向位置修正。
        /// </summary>
        private void ApplyInterRopeConnection()
        {
            for (var i = 0; i < Nodes.Count; i++)
            {
                var myNode = Nodes[i];
                if (myNode.connectedNode == null) continue;

                var otherNode = myNode.connectedNode;

                var currentVector = myNode.position - otherNode.position;
                var targetVector = myNode.connectionOffset;

                var diff = currentVector - targetVector;

                var moveFactorA = 0.5f;
                var moveFactorB = 0.5f;

                if (myNode.isPinned)
                {
                    moveFactorA = 0f;
                    moveFactorB = 1f;
                }
                else if (otherNode.isPinned)
                {
                    moveFactorA = 1f;
                    moveFactorB = 0f;
                }

                if (moveFactorA > 0f)
                    myNode.position -= diff * moveFactorA;
                if (moveFactorB > 0f)
                    otherNode.position += diff * moveFactorB;
            }
        }

        /// <summary>
        /// 在约束迭代过程中防止连接节点发生漂移。
        /// </summary>
        private void SyncConnectedNodes()
        {
            foreach (var node in Nodes)
                if (node.connectedNode != null)
                    node.position = node.connectedNode.position + node.connectionOffset;
        }


        /// <summary>
        /// 基础 Verlet 积分：x(t+dt) = 2x(t) - x(t-dt) + a*dt*dt
        /// </summary>
        private void SimulateVerlet()
        {
            var dt = Time.fixedDeltaTime;
            var gravityStep = gravity * (dt * dt);

            foreach (var node in Nodes)
            {
                if (node.isPinned)
                {
                    if (node.pinnedTransform)
                    {
                        node.position = node.pinnedTransform.TransformPoint(node.pinLocalPos);
                        node.prevPosition = node.position;
                    }

                    continue;
                }

                var velocity = (node.position - node.prevPosition) * (1f - airFriction);
                var newPos = node.position + velocity + gravityStep;

                node.prevPosition = node.position;
                node.position = newPos;
            }
        }

        private void ApplyConstraints()
        {
            SolveConstraints(0, Nodes.Count - 1, 1);
            SolveConstraints(Nodes.Count - 2, -1, -1);
        }

        private int GetConstraintSolverIterations()
        {
            var segmentCount = Mathf.Max(1, Nodes.Count - 1);
            return Mathf.Max(Mathf.Max(1, constraintIterations), segmentCount);
        }

        private void SolveConstraints(int startIndex, int endExclusive, int step)
        {
            for (var i = startIndex; i != endExclusive; i += step)
            {
                var nodeA = Nodes[i];
                var nodeB = Nodes[i + 1];

                var diff = nodeA.position - nodeB.position;
                var dist = diff.magnitude;
                if (dist < 0.0001f) continue;

                var movableA = nodeA.isPinned ? 0f : 1f;
                var movableB = nodeB.isPinned ? 0f : 1f;
                var movableWeight = movableA + movableB;
                if (movableWeight <= 0f) continue;

                var targetLength = GetConstraintLength(i);
                var difference = (dist - targetLength) / dist;
                var correction = diff * (difference * stiffness / movableWeight);

                if (movableA > 0f)
                    nodeA.position -= correction * movableA;

                if (movableB > 0f)
                    nodeB.position += correction * movableB;
            }
        }

        private void ClampOverstretchedSegments()
        {
            ClampOverstretchedSegments(0, Nodes.Count - 1, 1);
            ClampOverstretchedSegments(Nodes.Count - 2, -1, -1);
        }

        private void ClampOverstretchedSegments(int startIndex, int endExclusive, int step)
        {
            for (var i = startIndex; i != endExclusive; i += step)
            {
                var nodeA = Nodes[i];
                var nodeB = Nodes[i + 1];

                var delta = nodeB.position - nodeA.position;
                var distance = delta.magnitude;
                if (distance < 0.0001f) continue;

                var targetLength = GetConstraintLength(i);
                if (distance <= targetLength) continue;

                if (nodeA.isPinned && nodeB.isPinned) continue;

                var direction = delta / distance;
                if (nodeA.isPinned)
                {
                    nodeB.position = nodeA.position + direction * targetLength;
                    continue;
                }

                if (nodeB.isPinned)
                {
                    nodeA.position = nodeB.position - direction * targetLength;
                    continue;
                }

                var excess = (distance - targetLength) * 0.5f;
                var offset = direction * excess;
                nodeA.position += offset;
                nodeB.position -= offset;
            }
        }

        #endregion

        #region --- 双向耦合 ---

        /// <summary>
        /// 绳索与两端刚体相互施力。
        /// </summary>
        private void ApplyCouplingForces()
        {
            if (connectedBodyStart && Nodes.Count > 0)
            {
                var firstNode = Nodes[0];

                if (Nodes.Count > 1)
                {
                    var pullDir = (Nodes[1].position - firstNode.position).normalized;
                    var dist = Vector3.Distance(Nodes[1].position, firstNode.position);
                    var targetLength = GetConstraintLength(0);
                    if (dist > targetLength)
                    {
                        var force = pullDir * ((dist - targetLength) * rigidbodyInteractionForce);
                        connectedBodyStart.AddForceAtPosition(force, firstNode.position);
                    }
                }
            }

            if (connectedBodyEnd && Nodes.Count > 0 && Nodes[^1].isPinned)
            {
                var lastNode = Nodes[^1];
                var prevNode = Nodes[^2];

                var pullDir = (prevNode.position - lastNode.position).normalized;
                var dist = Vector3.Distance(prevNode.position, lastNode.position);
                var targetLength = GetConstraintLength(Nodes.Count - 2);

                if (dist > targetLength)
                {
                    var force = pullDir * ((dist - targetLength) * rigidbodyInteractionForce);
                    connectedBodyEnd.AddForceAtPosition(force, lastNode.position);
                }
            }
        }

        private void SyncConnectedEndRigidbodyToRope()
        {
            if (!connectedBodyEnd || Nodes.Count == 0) return;
            if (Nodes[^1].isPinned) return;

            var desiredAttachmentPoint = Nodes[^1].position;
            var currentAttachmentPoint = connectedBodyEnd.transform.TransformPoint(_connectedBodyEndLocalPos);
            var correction = desiredAttachmentPoint - currentAttachmentPoint;
            if (correction.sqrMagnitude <= 0.000001f) return;

            connectedBodyEnd.position += correction;
            ropeEndPoint = desiredAttachmentPoint;

            var correctionDir = correction.normalized;
            var velocityAlongCorrection = Vector3.Dot(connectedBodyEnd.linearVelocity, correctionDir);
            if (velocityAlongCorrection < 0f)
                connectedBodyEnd.linearVelocity -= correctionDir * velocityAlongCorrection;
        }

        #endregion

        #region --- 碰撞 ---

        /// <summary>
        /// 处理所有碰撞类型。
        /// </summary>
        private void ResolveCollisions()
        {
            for (var i = 0; i < Nodes.Count; i++)
            {
                if (Nodes[i].isPinned) continue;

                HandleTerrainCollision(Nodes[i]);

                HandleStandardCollisions(Nodes[i]);

                if (enableSelfCollision)
                    HandleSelfCollision(i);
            }
        }

        private void HandleTerrainCollision(RopeNode node)
        {
            var terrain = UnityEngine.Terrain.activeTerrain;
            if (!terrain) return;

            var terrainHeight = terrain.SampleHeight(node.position) + terrain.transform.position.y;
            if (node.position.y < terrainHeight + PhysicsRadius)
            {
                node.position = new Vector3(node.position.x, terrainHeight + PhysicsRadius, node.position.z);

                // 地面摩擦
                var vel = node.position - node.prevPosition;
                node.prevPosition = node.position - (vel * 0.5f);
            }
        }

        private void HandleStandardCollisions(RopeNode node)
        {
            var count = Physics.OverlapSphereNonAlloc(node.position, PhysicsRadius, _collisionBuffer, collisionLayer);

            for (var i = 0; i < count; i++)
            {
                var col = _collisionBuffer[i];
                if (col is TerrainCollider) continue;
                if (_ignoreColliders.Contains(col)) continue;

                if (col is MeshCollider { convex: false } meshCol)
                {
                    HandleNonConvexCollision(node, meshCol);
                    continue;
                }

                var closestPoint = col.ClosestPoint(node.position);
                var distToSurface = Vector3.Distance(node.position, closestPoint);

                if (distToSurface < PhysicsRadius)
                {
                    var pushDir = (node.position - closestPoint).normalized;

                    if (pushDir == Vector3.zero)
                        pushDir = (node.position - node.prevPosition).normalized;

                    var penetrationDepth = PhysicsRadius - distToSurface;
                    var pushVector = pushDir * penetrationDepth;

                    if (!node.isPinned)
                        node.position += pushVector;

                    // 对刚体施加反作用力
                    var targetRb = col.attachedRigidbody;
                    if (targetRb && !targetRb.isKinematic)
                    {
                        var scale = collisionPushPower / constraintIterations;
                        var reactionForce = -pushDir * (penetrationDepth * scale);

                        targetRb.AddForceAtPosition(reactionForce, node.position, ForceMode.Impulse);

                        // 摩擦效果

                        var nodeVel = (node.position - node.prevPosition) / Time.fixedDeltaTime;
                        var rbVel = targetRb.GetPointVelocity(node.position);
                        var relativeVel = rbVel - nodeVel;

                        var tangentVel = Vector3.ProjectOnPlane(relativeVel, pushDir);

                        var normalForceMag = reactionForce.magnitude;
                        var frictionMag = normalForceMag * contactFriction;

                        if (tangentVel.sqrMagnitude > 0.001f)
                        {
                            var frictionDir = -tangentVel.normalized;
                            var frictionForce = frictionDir * frictionMag;

                            targetRb.AddForceAtPosition(frictionForce, node.position, ForceMode.Impulse);

                            if (!node.isPinned)
                            {
                                var dragEffect = -frictionForce * (Time.fixedDeltaTime * 0.001f);
                                node.position += dragEffect;
                            }
                        }
                    }
                }
            }
        }

        private void HandleNonConvexCollision(RopeNode node, MeshCollider meshCol)
        {
            var travelDir = node.position - node.prevPosition;
            var travelDist = travelDir.magnitude;

            if (travelDist < 0.0001f) return;

            if (Physics.SphereCast(node.prevPosition, PhysicsRadius, travelDir.normalized, out var hit,
                    travelDist + PhysicsRadius, collisionLayer))
            {
                if (hit.collider == meshCol)
                    node.position = hit.point + hit.normal * (PhysicsRadius * 1.1f);
            }
        }

        private void HandleSelfCollision(int currentIndex)
        {
            var currentNode = Nodes[currentIndex];

            for (var i = 0; i < Nodes.Count; i++)
            {
                if (Mathf.Abs(currentIndex - i) <= 1) continue;

                var otherNode = Nodes[i];
                var distSqr = (currentNode.position - otherNode.position).sqrMagnitude;
                var minSpacing = PhysicsRadius * 2f;

                if (distSqr < minSpacing * minSpacing)
                {
                    var dist = Mathf.Sqrt(distSqr);
                    var pushDir = (currentNode.position - otherNode.position).normalized;
                    var pushAmount = (minSpacing - dist) * 0.5f;

                    if (!currentNode.isPinned)
                        currentNode.position += pushDir * pushAmount;
                    if (!otherNode.isPinned)
                        otherNode.position -= pushDir * pushAmount;
                }
            }
        }

        #endregion

        #region --- 公共 API ---

        public void Enable()
        {
            if (enabled) return;
            if (gameObject.activeInHierarchy)
                VerletPhysics.RegisterRopeGenerator(this);

            InitializeRope();
            enabled = true;
        }

        public void Disable()
        {
            VerletPhysics.UnregisterRopeGenerator(this);
            _ropeRenderer?.Clear();
            enabled = false;
        }

        public void Print()
        {
            var constraintLength = GetCurrentConstraintLength();
            var renderLength = GetCurrentRenderLength();
            var endpointDistance = GetEndpointDistance();
            var segmentDiagnostics = GetSegmentLengthDiagnostics();

            Debug.Log(
                $"CurrentConstraintLength={constraintLength}, " +
                $"CurrentRenderLength={renderLength}, " +
                $"RenderMinusConstraint={renderLength - constraintLength}, " +
                $"EndpointDistance={endpointDistance}, " +
                $"EndpointMinusConstraint={endpointDistance - constraintLength}, " +
                $"SegmentCount={Mathf.Max(0, Nodes.Count - 1)}, " +
                $"FirstSegmentConstraintLength={(Nodes.Count > 1 ? GetConstraintLength(0) : 0f)}, " +
                $"DefaultSegmentConstraintLength={_segmentLength}, " +
                $"OverstretchedSegmentCount={segmentDiagnostics.overstretchedSegmentCount}, " +
                $"TotalPositiveStretch={segmentDiagnostics.totalPositiveStretch}, " +
                $"MaxSegmentStretch={segmentDiagnostics.maxSegmentStretch}, " +
                $"MaxStretchSegmentIndex={segmentDiagnostics.maxStretchSegmentIndex}, " +
                $"MaxStretchSegmentActualLength={segmentDiagnostics.maxStretchSegmentActualLength}, " +
                $"MaxStretchSegmentTargetLength={segmentDiagnostics.maxStretchSegmentTargetLength}");
        }
        
        /// <summary>
        /// 增加 1 个基础节点间距长度。
        /// </summary>
        public void IncreaseLength()
        {
            if (Nodes.Count < 2) return;
            SetRopeLength(GetCurrentConstraintLength() + Mathf.Max(0.001f, nodeSpacing));
        }

        /// <summary>
        /// 减少 1 个基础节点间距长度。
        /// </summary>
        public void DecreaseLength()
        {
            if (Nodes.Count < 2) return;
            SetRopeLength(GetCurrentConstraintLength() - Mathf.Max(0.001f, nodeSpacing));
        }

        /// <summary>
        /// 按指定长度增加绳索长度。
        /// </summary>
        /// <param name="lengthDelta">要增加的世界单位长度</param>
        public void IncreaseLength(float lengthDelta)
        {
            if (lengthDelta <= 0f || Nodes.Count < 2) return;
            SetRopeLength(GetCurrentConstraintLength() + lengthDelta);
        }

        /// <summary>
        /// 按指定长度减少绳索长度。
        /// </summary>
        /// <param name="lengthDelta">要减少的世界单位长度</param>
        public void DecreaseLength(float lengthDelta)
        {
            if (lengthDelta <= 0f || Nodes.Count < 2) return;
            SetRopeLength(GetCurrentConstraintLength() - lengthDelta);
        }

        /// <summary>
        /// 直接设置绳索总约束长度。
        /// </summary>
        /// <param name="targetLength">目标总长度（世界单位）</param>
        public void SetRopeLength(float targetLength)
        {
            if (Nodes.Count < 2) return;

            targetLength = Mathf.Max(0.001f, targetLength);
            var desiredSegmentCount = CalculateSegmentCount(targetLength, Mathf.Max(0.001f, nodeSpacing));

            var topologyChanged = false;

            while (Nodes.Count - 1 < desiredSegmentCount)
            {
                if (!TryInsertSegmentAtStart())
                    break;
                topologyChanged = true;
            }

            while (Nodes.Count - 1 > desiredSegmentCount)
            {
                if (!TryRemoveFreeSegmentNode())
                    break;
                topologyChanged = true;
            }

            ConfigureConstraintLayout(targetLength);

            if (topologyChanged)
                _ropeRenderer?.Rebuild();
        }

        /// <summary>
        /// 由约束表示的当前绳索总长度。
        /// </summary>
        public float GetCurrentConstraintLength()
        {
            if (Nodes.Count < 2) return 0f;
            return GetConstraintLength(0) + (_segmentLength * Mathf.Max(0, Nodes.Count - 2));
        }

        /// <summary>
        /// 由当前节点位置形成的渲染中心线总长度。
        /// </summary>
        public float GetCurrentRenderLength()
        {
            if (Nodes.Count < 2) return 0f;

            var totalLength = 0f;
            for (var i = 0; i < Nodes.Count - 1; i++)
                totalLength += Vector3.Distance(Nodes[i].position, Nodes[i + 1].position);

            return totalLength;
        }

        public float GetEndpointDistance()
        {
            if (Nodes.Count < 2) return 0f;
            return Vector3.Distance(Nodes[0].position, Nodes[^1].position);
        }

        private (int overstretchedSegmentCount, float totalPositiveStretch, float maxSegmentStretch, int maxStretchSegmentIndex,
            float maxStretchSegmentActualLength, float maxStretchSegmentTargetLength) GetSegmentLengthDiagnostics()
        {
            var overstretchedSegmentCount = 0;
            var totalPositiveStretch = 0f;
            var maxSegmentStretch = 0f;
            var maxStretchSegmentIndex = -1;
            var maxStretchSegmentActualLength = 0f;
            var maxStretchSegmentTargetLength = 0f;

            for (var i = 0; i < Nodes.Count - 1; i++)
            {
                var actualLength = Vector3.Distance(Nodes[i].position, Nodes[i + 1].position);
                var targetLength = GetConstraintLength(i);
                var stretch = actualLength - targetLength;
                if (stretch <= 0f) continue;

                overstretchedSegmentCount++;
                totalPositiveStretch += stretch;

                if (stretch <= maxSegmentStretch) continue;

                maxSegmentStretch = stretch;
                maxStretchSegmentIndex = i;
                maxStretchSegmentActualLength = actualLength;
                maxStretchSegmentTargetLength = targetLength;
            }

            return (overstretchedSegmentCount, totalPositiveStretch, maxSegmentStretch, maxStretchSegmentIndex,
                maxStretchSegmentActualLength, maxStretchSegmentTargetLength);
        }

        private bool TryInsertSegmentAtStart()
        {
            if (Nodes.Count < 2) return false;
            var pos = (Nodes[0].position + Nodes[1].position) * 0.5f;
            Nodes.Insert(1, new RopeNode(pos));
            return true;
        }

        private bool TryRemoveFreeSegmentNode()
        {
            if (Nodes.Count <= 2) return false;

            for (var i = 1; i < Nodes.Count - 1; i++)
            {
                if (Nodes[i].isPinned || Nodes[i].connectedNode != null) continue;
                Nodes.RemoveAt(i);
                return true;
            }

            return false;
        }

        /// <summary>
        /// 将指定索引节点固定到目标变换组件，可选本地偏移。
        /// </summary>
        /// <param name="index">0 表示起点，-1 表示末尾节点</param>
        /// <param name="target"></param>
        /// <param name="localOffset"></param>
        public void PinNode(int index, Transform target, Vector3 localOffset = default)
        {
            if (index < 0)
                index = Nodes.Count + index;
            if (index >= 0 && index < Nodes.Count)
            {
                Nodes[index].PinTo(target, localOffset);
                if (index == 0)
                    ropeStartPoint = target.position;
                if (index == Nodes.Count - 1)
                    ropeEndPoint = target.position;
            }
        }

        /// <summary>
        /// 解除指定索引节点的固定。
        /// </summary>
        /// <param name="index">0 表示起点，-1 表示末尾节点</param>
        public void UnpinNode(int index)
        {
            if (index < 0)
                index = Nodes.Count + index;
            if (index >= 0 && index < Nodes.Count)
            {
                Nodes[index].Unpin();
                if (index == 0)
                    ropeStartPoint = transform.position;
                if (index == Nodes.Count - 1)
                    ropeEndPoint = transform.position + Vector3.forward * 1f;
            }
        }

        /// <summary>
        /// 将绳索起点在一段时间内连接到指定刚体。
        /// </summary>
        /// <param name="connectedRb"></param>
        /// <param name="localPos"></param>
        /// <param name="time"></param>
        public void ConnectStartRigidbody(Rigidbody connectedRb, Vector3 localPos, float time)
        {
            StopCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time));
            if (connectedBodyStart)
            {
                _ignoreColliders.RemoveAll(c =>
                    c && (c.gameObject == connectedBodyStart.gameObject) ||
                    c.transform.IsChildOf(connectedBodyStart.transform));
            }

            UnpinNode(0);
            connectedBodyStart = null;

            if (Nodes.Count == 0) return;

            Nodes[0].isPinned = true;

            StartCoroutine(RigibBodyConnectionRoute(connectedRb, localPos, time));
        }

        private IEnumerator RigibBodyConnectionRoute(Rigidbody connectedRb, Vector3 localPos, float time)
        {
            var dist = Vector3.Distance(Nodes[0].position,
                connectedRb.position + connectedRb.transform.TransformVector(localPos));
            var deltaTime = Time.fixedDeltaTime;
            var deltaDist = dist;
            if (time > 0.01f)
                deltaDist = deltaTime * (dist / time);
            var wait = new WaitForFixedUpdate();
            while (dist > 0.01f)
            {
                var dir = (connectedRb.position + connectedRb.transform.TransformVector(localPos) - Nodes[0].position)
                    .normalized;
                Nodes[0].position += dir * deltaDist;
                dist = Vector3.Distance(Nodes[0].position,
                    connectedRb.position + connectedRb.transform.TransformVector(localPos));
                yield return wait;
            }

            connectedBodyStart = connectedRb;
            _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());
            PinNode(0, connectedBodyStart.transform, localPos);
        }

        /// <summary>
        /// 将绳索起点连接到指定刚体。
        /// </summary>
        /// <param name="connectedRb"></param>
        /// <param name="localPos"></param>
        public void ConnectStartRigidbody(Rigidbody connectedRb, Vector3 localPos)
        {
            if (connectedBodyStart)
            {
                _ignoreColliders.RemoveAll(c =>
                    c && (c.gameObject == connectedBodyStart.gameObject) ||
                    c.transform.IsChildOf(connectedBodyStart.transform));
            }

            connectedBodyStart = connectedRb;
            _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());
            PinNode(0, connectedBodyStart.transform, localPos);
        }

        /// <summary>
        /// 将绳索末端连接到指定刚体。
        /// </summary>
        /// <param name="connectedRb"></param>
        /// <param name="localPos"></param>
        public void ConnectEndRigidbody(Rigidbody connectedRb, Vector3 localPos)
        {
            if (connectedBodyEnd)
            {
                _ignoreColliders.RemoveAll(c =>
                    c && (c.gameObject == connectedBodyEnd.gameObject) ||
                    c.transform.IsChildOf(connectedBodyEnd.transform));
            }

            connectedBodyEnd = connectedRb;
            _connectedBodyEndLocalPos = localPos;
            _ignoreColliders.AddRange(connectedRb.GetComponentsInChildren<Collider>());

            if (Nodes.Count == 0) return;

            var attachmentPoint = connectedBodyEnd.transform.TransformPoint(localPos);
            var endNode = Nodes[^1];
            endNode.Unpin();
            endNode.position = attachmentPoint;
            endNode.prevPosition = attachmentPoint;
            ropeEndPoint = attachmentPoint;
        }

        /// <summary>
        /// 断开绳索起点与刚体的连接。
        /// </summary>
        public void DisconnectStartRigidbody()
        {
            if (!connectedBodyStart) return;

            _ignoreColliders.RemoveAll(c =>
                c != null && (c.gameObject == connectedBodyStart.gameObject) ||
                c.transform.IsChildOf(connectedBodyStart.transform));

            connectedBodyStart = null;
            UnpinNode(0);
        }

        /// <summary>
        /// 断开绳索末端与刚体的连接。
        /// </summary>
        public void DisconnectEndRigidbody()
        {
            if (!connectedBodyEnd) return;

            _ignoreColliders.RemoveAll(c =>
                c != null && (c.gameObject == connectedBodyEnd.gameObject) ||
                c.transform.IsChildOf(connectedBodyEnd.transform));

            connectedBodyEnd = null;
            _connectedBodyEndLocalPos = default;
            UnpinNode(-1);
        }


        /// <summary>
        /// 将当前绳索末端连接到目标绳索的某个节点。
        /// </summary>
        /// <param name="targetRope"></param>
        /// <param name="targetNodeIndex"></param>
        /// <param name="connectionOffset"></param>
        public void ConnectEndToOtherRope(VerletRope targetRope, int targetNodeIndex,
            Vector3 connectionOffset = default)
        {
            ConnectToOtherRope(-1, targetRope, targetNodeIndex, connectionOffset);
        }

        /// <summary>
        /// 将当前绳索起点连接到目标绳索的某个节点。
        /// </summary>
        /// <param name="targetRope"></param>
        /// <param name="targetNodeIndex"></param>
        /// <param name="connectionOffset"></param>
        public void ConnectStartToOtherRope(VerletRope targetRope, int targetNodeIndex,
            Vector3 connectionOffset = default)
        {
            ConnectToOtherRope(0, targetRope, targetNodeIndex, connectionOffset);
        }

        /// <summary>
        /// 将当前绳索的某个节点连接到目标绳索的某个节点。
        /// </summary>
        /// <param name="myNodeIndex">0 表示起点，-1 表示末尾节点</param>
        /// <param name="targetRope"></param>
        /// <param name="targetNodeIndex">0 表示起点，-1 表示末尾节点</param>
        public void ConnectToOtherRope(int myNodeIndex, VerletRope targetRope, int targetNodeIndex,
            Vector3 connectionOffset = default)
        {
            if (myNodeIndex < 0)
                myNodeIndex = Nodes.Count + myNodeIndex;
            if (targetNodeIndex < 0)
                targetNodeIndex = targetRope.Nodes.Count + targetNodeIndex;
            if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return;
            if (!targetRope || targetNodeIndex < 0 || targetNodeIndex >= targetRope.Nodes.Count) return;

            var myNode = Nodes[myNodeIndex];
            var targetNode = targetRope.Nodes[targetNodeIndex];

            if (targetNodeIndex == 0)
                targetRope.DisconnectStartRigidbody();
            if (targetNodeIndex == targetRope.Nodes.Count - 1)
                targetRope.DisconnectEndRigidbody();

            myNode.ConnectToNode(targetNode, connectionOffset);
            targetNode.ConnectToNode(myNode, -connectionOffset);
        }

        /// <summary>
        /// 断开该节点与其他绳索节点的连接。
        /// </summary>
        /// <param name="myNodeIndex">0 表示起点，-1 表示末尾节点</param>
        public void DisconnectFromOtherRope(int myNodeIndex)
        {
            if (myNodeIndex < 0)
                myNodeIndex = Nodes.Count + myNodeIndex;
            if (myNodeIndex < 0 || myNodeIndex >= Nodes.Count) return;

            var myNode = Nodes[myNodeIndex];
            var targetNode = myNode.connectedNode;
            myNode.ConnectToNode(null);
            targetNode?.ConnectToNode(null);
        }

        /// <summary>
        /// 对绳索进行射线检测并返回命中信息。
        /// </summary>
        /// <param name="ray">来自某点的射线</param>
        /// <param name="hitInfo">命中结果</param>
        /// <param name="maxDistance">最大检测距离</param>
        /// <returns>若射线命中绳索则返回 true</returns>
        public bool Raycast(Ray ray, out RopeHitInfo hitInfo, float maxDistance = 100f)
        {
            hitInfo = new RopeHitInfo();
            var closestDistSqr = float.MaxValue;
            var hasHit = false;

            for (var i = 0; i < Nodes.Count - 1; i++)
            {
                var p1 = Nodes[i].position;
                var p2 = Nodes[i + 1].position;

                var distSqr = SqrDistanceRaySegment(ray, p1, p2, out var rayParam, out var segmentParam);

                if (distSqr <= (PhysicsRadius * PhysicsRadius) && rayParam < maxDistance && rayParam > 0)
                {
                    if (rayParam < closestDistSqr)
                    {
                        closestDistSqr = rayParam;

                        hitInfo.DidHit = true;
                        hitInfo.Rope = this;
                        hitInfo.NodeIndex = segmentParam < 0.5f ? i : i + 1;
                        hitInfo.NodePoint = segmentParam < 0.5f ? p1 : p2;
                        hitInfo.Distance = rayParam;
                        hitInfo.SegmentRatio = segmentParam;
                        hitInfo.Point = ray.GetPoint(rayParam);

                        hasHit = true;
                    }
                }
            }

            return hasHit;
        }

        private static float SqrDistanceRaySegment(Ray ray, Vector3 s1, Vector3 s2, out float rayParam,
            out float segmentParam)
        {
            var u = ray.direction;
            var v = s2 - s1;
            var w = ray.origin - s1;

            var a = Vector3.Dot(u, u);
            var b = Vector3.Dot(u, v);
            var c = Vector3.Dot(v, v);
            var d = Vector3.Dot(u, w);
            var e = Vector3.Dot(v, w);

            var denom = a * c - b * b;
            float sc, tc;

            if (denom < 1e-6f)
            {
                sc = 0f;
                tc = (b > c ? d / b : e / c);
            }
            else
            {
                sc = (b * e - c * d) / denom;
                tc = (a * e - b * d) / denom;
            }

            rayParam = sc;
            segmentParam = Mathf.Clamp01(tc);

            var closestPointOnSegment = s1 + v * segmentParam;

            var delta = closestPointOnSegment - ray.origin;
            rayParam = Vector3.Dot(delta, ray.direction);

            if (rayParam < 0f)
                rayParam = 0f;

            var closestPointOnRay = ray.GetPoint(rayParam);

            return (closestPointOnSegment - closestPointOnRay).sqrMagnitude;
        }

        #endregion

        #region --- 调试 / 场景可视化 ---

        private void OnDrawGizmos()
        {
            if (Nodes == null || Nodes.Count == 0) return;

            Gizmos.color = Color.green;
            for (var i = 0; i < Nodes.Count - 1; i++)
                Gizmos.DrawLine(Nodes[i].position, Nodes[i + 1].position);

            Gizmos.color = Color.red;
            foreach (var node in Nodes)
                if (node.isPinned)
                    Gizmos.DrawWireSphere(node.position, PhysicsRadius * 1.2f);
        }

        private void OnDrawGizmosSelected()
        {
            if (Application.isPlaying) return;
            var startPos = ropeStartPoint;
            var endPos = ropeEndPoint;

            if (Vector3.Distance(startPos, endPos) < 0.01f)
                endPos = startPos + Vector3.forward * 5f;

            var totalDistance = Vector3.Distance(startPos, endPos);
            var spacing = Mathf.Max(0.001f, nodeSpacing);
            var segmentCount = CalculateSegmentCount(totalDistance, spacing);
            var nodeCount = segmentCount + 1;

            var previewNodes = new Vector3[nodeCount];
            var direction = (endPos - startPos).normalized;
            var firstSegmentLength = CalculateFirstSegmentLength(totalDistance, segmentCount, spacing);

            previewNodes[0] = startPos;
            var traversedDistance = 0f;
            for (var i = 1; i < nodeCount; i++)
            {
                traversedDistance += i == 1 ? firstSegmentLength : spacing;
                previewNodes[i] = i == nodeCount - 1 ? endPos : startPos + direction * traversedDistance;
            }

            if (initialPinPoints != null)
            {
                foreach (var pin in initialPinPoints)
                {
                    if (pin.targetTransform == null) continue;

                    var idx = pin.nodeIndex;
                    if (idx < 0) idx = nodeCount + idx;

                    if (idx >= 0 && idx < nodeCount)
                    {
                        previewNodes[idx] = pin.targetTransform.TransformPoint(pin.localPos);
                    }
                }
            }

            for (var i = 0; i < nodeCount; i++)
            {
                var isPinned = false;
                if (initialPinPoints != null)
                {
                    foreach (var p in initialPinPoints)
                    {
                        if (p.targetTransform == null) continue;
                        var idx = p.nodeIndex < 0 ? nodeCount + p.nodeIndex : p.nodeIndex;
                        if (idx == i) isPinned = true;
                    }
                }

                if (isPinned)
                {
                    Gizmos.color = Color.red;
                    Gizmos.DrawSphere(previewNodes[i], PhysicsRadius * 1.2f);
                }
                else
                {
                    Gizmos.color = new Color(0f, 1f, 0f, 0.4f);
                    Gizmos.DrawWireSphere(previewNodes[i], PhysicsRadius);
                }

                if (i < nodeCount - 1)
                {
                    Gizmos.color = Color.green;
                    Gizmos.DrawLine(previewNodes[i], previewNodes[i + 1]);
                }
            }
        }

        #endregion
    }

    public struct RopeHitInfo
    {
        public bool DidHit;
        public VerletRope Rope;
        public Vector3 Point;
        public int NodeIndex;
        public Vector3 NodePoint;
        public float Distance;
        public float SegmentRatio;
    }
}
