using TMPro;
using UnityEngine;

namespace eagle {
    /// <summary>
    /// Curves TextMeshPro text along an arc.
    /// Attach to a GameObject with TextMeshProUGUI component.
    /// </summary>
    [RequireComponent(typeof(TextMeshProUGUI))]
    public class CurvedText : MonoBehaviour {
        [Tooltip(
                "Curvature amount. Positive curves upward (convex), negative curves downward (concave). 0 = no curve.")]
        public float curvature = 0f;

        private TextMeshProUGUI _textComponent;
        private bool _needsUpdate = true;

        void Awake() { _textComponent = GetComponent<TextMeshProUGUI>(); }

        void OnEnable() {
            TMPro_EventManager.TEXT_CHANGED_EVENT.Add(OnTextChanged);
            _needsUpdate = true;
        }

        void OnDisable() { TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(OnTextChanged); }

        void OnTextChanged(Object obj) {
            if (obj == _textComponent) { _needsUpdate = true; }
        }

        void LateUpdate() {
            if (_needsUpdate && _textComponent != null) {
                _needsUpdate = false;
                ApplyCurve();
            }
        }

        public void SetCurvature(float newCurvature) {
            curvature = newCurvature;
            _needsUpdate = true;
        }

        private void ApplyCurve() {
            if (Mathf.Approximately(curvature, 0f)) { return; }

            _textComponent.ForceMeshUpdate();

            TMP_TextInfo textInfo = _textComponent.textInfo;
            if (textInfo == null || textInfo.characterCount == 0) { return; }

            // Find the bounds of all characters
            float minX = float.MaxValue;
            float maxX = float.MinValue;

            for (int i = 0; i < textInfo.characterCount; i++) {
                if (!textInfo.characterInfo[i].isVisible) { continue; }

                var charInfo = textInfo.characterInfo[i];
                minX = Mathf.Min(minX, charInfo.bottomLeft.x);
                maxX = Mathf.Max(maxX, charInfo.bottomRight.x);
            }

            float textWidth = maxX - minX;
            if (textWidth <= 0) { return; }

            float centerX = (minX + maxX) / 2f;

            // Apply curve to each character
            for (int i = 0; i < textInfo.characterCount; i++) {
                if (!textInfo.characterInfo[i].isVisible) { continue; }

                var charInfo = textInfo.characterInfo[i];
                int materialIndex = charInfo.materialReferenceIndex;
                int vertexIndex = charInfo.vertexIndex;

                Vector3[] vertices = textInfo.meshInfo[materialIndex].vertices;

                // Get character center X position
                float charCenterX = (vertices[vertexIndex].x + vertices[vertexIndex + 2].x) / 2f;

                // Calculate normalized position (-1 to 1) from center
                float normalizedX = (charCenterX - centerX) / (textWidth / 2f);

                // Calculate Y offset using parabola: y = -curvature * (1 - x^2)
                // Negated because Unity Y is up, but our curvature convention has positive = convex
                // down. Scale by 0.25 to reduce intensity.
                float scaledCurvature = curvature * 0.25f;
                float yOffset = -scaledCurvature * (1f - normalizedX * normalizedX);

                // Calculate rotation angle based on tangent of the curve
                // Derivative of y = -c * (1 - x^2) is dy/dx = 2cx
                float tangentSlope = 2f * scaledCurvature * normalizedX / (textWidth / 2f);
                float angle = Mathf.Atan(tangentSlope) * Mathf.Rad2Deg;

                // Apply transformation to all 4 vertices of the character
                Vector3 charCenter = new Vector3(
                        charCenterX,
                        (vertices[vertexIndex].y + vertices[vertexIndex + 2].y) / 2f,
                        0);

                for (int j = 0; j < 4; j++) {
                    Vector3 offset = vertices[vertexIndex + j] - charCenter;

                    // Rotate around character center
                    float rad = angle * Mathf.Deg2Rad;
                    float rotatedX = offset.x * Mathf.Cos(rad) - offset.y * Mathf.Sin(rad);
                    float rotatedY = offset.x * Mathf.Sin(rad) + offset.y * Mathf.Cos(rad);

                    vertices[vertexIndex + j] =
                            charCenter + new Vector3(rotatedX, rotatedY + yOffset, offset.z);
                }
            }

            // Update the mesh
            for (int i = 0; i < textInfo.meshInfo.Length; i++) {
                textInfo.meshInfo[i].mesh.vertices = textInfo.meshInfo[i].vertices;
                _textComponent.UpdateGeometry(textInfo.meshInfo[i].mesh, i);
            }
        }
    }
}
