using UnityEngine;

namespace NBF
{
    [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
    public class RopeMeshRenderer : MonoBehaviour, IRopeRenderer
    {
        [SerializeField] private int radialSegments = 8;
        [SerializeField] private float textureTiling = 1f;

        private VerletRope _rope;
        private MeshFilter _meshFilter;
        private Mesh _mesh;
        private Vector3[] _vertices;
        private int[] _triangles;
        private Vector2[] _uvs;
        private float _radius;

        private void Awake()
        {
            _meshFilter = GetComponent<MeshFilter>();
        }

        public void Bind(VerletRope rope)
        {
            _rope = rope;
        }

        public void Rebuild()
        {
            SetupRopeMesh();
        }

        public void Render()
        {
            UpdateMeshVertices();
        }

        public void Clear()
        {
            ClearMesh();
        }

        private void SetupRopeMesh()
        {
            if (_rope == null) return;
            if (!_meshFilter) _meshFilter = GetComponent<MeshFilter>();

            var segmentCount = Mathf.Max(3, radialSegments);

            if (_mesh == null)
            {
                _mesh = new Mesh
                {
                    name = "ProceduralRope"
                };
                _mesh.MarkDynamic();
            }
            else
            {
                _mesh.Clear();
            }

            _meshFilter.sharedMesh = _mesh;

            _radius = _rope.RenderRadius;
            var nodes = _rope.Nodes;
            var vertexCount = nodes.Count * (segmentCount + 1) + 2;
            var triCount = (nodes.Count - 1) * segmentCount * 6;
            var capTrisCount = (segmentCount * 3) * 2;

            _vertices = new Vector3[vertexCount];
            _uvs = new Vector2[vertexCount];
            _triangles = new int[triCount + capTrisCount];

            var triIndex = 0;

            for (var i = 0; i < nodes.Count - 1; i++)
            {
                for (var j = 0; j < segmentCount; j++)
                {
                    var current = i * (segmentCount + 1) + j;
                    var next = current + segmentCount + 1;

                    _triangles[triIndex++] = current;
                    _triangles[triIndex++] = current + 1;
                    _triangles[triIndex++] = next;

                    _triangles[triIndex++] = next;
                    _triangles[triIndex++] = current + 1;
                    _triangles[triIndex++] = next + 1;
                }
            }

            var startCenterIndex = vertexCount - 2;
            var endCenterIndex = vertexCount - 1;

            // Start Cap
            for (var j = 0; j < segmentCount; j++)
            {
                var current = j;
                var next = j + 1;

                _triangles[triIndex++] = startCenterIndex;
                _triangles[triIndex++] = next;
                _triangles[triIndex++] = current;
            }

            // End Cap
            var lastRingStart = (nodes.Count - 1) * (segmentCount + 1);
            for (var j = 0; j < segmentCount; j++)
            {
                var current = lastRingStart + j;
                var next = lastRingStart + j + 1;

                _triangles[triIndex++] = endCenterIndex;
                _triangles[triIndex++] = current;
                _triangles[triIndex++] = next;
            }

            _mesh.vertices = _vertices;
            _mesh.triangles = _triangles;
            _mesh.uv = _uvs;
            _mesh.RecalculateBounds();
        }

        private void ClearMesh()
        {
            if (_meshFilter)
                _meshFilter.sharedMesh = null;

            if (_mesh != null)
            {
                if (Application.isPlaying)
                    Destroy(_mesh);
                else
                    DestroyImmediate(_mesh);
            }

            _mesh = null;
        }

        private void UpdateMeshVertices()
        {
            if (_rope == null || _mesh == null) return;

            var segmentCount = Mathf.Max(3, radialSegments);
            var uvTiling = Mathf.Max(0.01f, textureTiling);

            var verletTransform = _rope.transform;
            var nodes = _rope.Nodes;
            if (nodes.Count < 2) return;

            var lastRotation = Quaternion.identity;
            var lastForward = Vector3.zero;

            for (var i = 0; i < nodes.Count; i++)
            {
                Vector3 forward;
                if (i < nodes.Count - 1)
                    forward = (nodes[i + 1].position - nodes[i].position).normalized;
                else
                    forward = (nodes[i].position - nodes[i - 1].position).normalized;

                if (forward == Vector3.zero) forward = Vector3.up;

                Quaternion rotation;

                if (i == 0)
                    rotation = Quaternion.LookRotation(forward, Vector3.forward);
                else
                {
                    var swing = Quaternion.FromToRotation(lastForward, forward);

                    rotation = swing * lastRotation;
                }

                lastRotation = rotation;
                lastForward = forward;

                for (var j = 0; j <= segmentCount; j++)
                {
                    var u = (float)j / segmentCount;
                    var angle = u * Mathf.PI * 2;

                    var x = Mathf.Cos(angle) * _radius;
                    var y = Mathf.Sin(angle) * _radius;

                    var localPos = new Vector3(x, y, 0);
                    var worldPos = nodes[i].position + (rotation * localPos);

                    var meshLocalPos = verletTransform.InverseTransformPoint(worldPos);

                    var vertIndex = i * (segmentCount + 1) + j;
                    _vertices[vertIndex] = meshLocalPos;

                    var v = (float)i / (nodes.Count - 1) * uvTiling;
                    _uvs[vertIndex] = new Vector2(u, v);
                }
            }

            var startCenterIndex = _vertices.Length - 2;
            var endCenterIndex = _vertices.Length - 1;

            _vertices[startCenterIndex] = verletTransform.InverseTransformPoint(nodes[0].position);
            _uvs[startCenterIndex] = new Vector2(0.5f, 0f);

            _vertices[endCenterIndex] = verletTransform.InverseTransformPoint(nodes[^1].position);
            _uvs[endCenterIndex] = new Vector2(0.5f, uvTiling);

            _mesh.vertices = _vertices;
            _mesh.uv = _uvs;

            _mesh.RecalculateNormals();
            _mesh.RecalculateBounds();
        }
    }
}
