using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

namespace eagle {
    /// <summary>
    /// Dynamically renders province name labels on the strategic map.
    /// Labels scale inversely with zoom level for readability.
    /// </summary>
    public class ProvinceLabelsController : MonoBehaviour {
        public MapPinchZoomHandler zoomHandler;
        public RectTransform mapContent;
        public TMP_FontAsset labelFont;
        public ProvinceIDLoader provinceIDLoader;

        [Header("Label Settings")]
        public float baseFontSize = 11f;
        public float minZoomToShow = 1.5f;
        public Color labelColor = Color.black;
        public Color outlineColor = Color.white;
        public float outlineWidth = 0.15f;
        public Color underlayColor = Color.white;
        public Vector2 underlayOffset = new Vector2(0f, 0f);
        public float underlayDilate = 0.7f;
        public float underlaySoftness = 0.2f;

        private readonly Dictionary<int, TextMeshProUGUI> _labels =
                new Dictionary<int, TextMeshProUGUI>();
        private readonly Dictionary<int, float> _sourceOrientations = new Dictionary<int, float>();
        private readonly Dictionary<int, float> _sourceCurvatures = new Dictionary<int, float>();
        private readonly Dictionary<int, float> _provinceAreas = new Dictionary<int, float>();
        private readonly Dictionary<int, float> _provinceLengths = new Dictionary<int, float>();
        private readonly Dictionary<int, int> _nameCharCounts = new Dictionary<int, int>();
        private float _avgArea;
        private float _mapWidth;
        private float _mapHeight;
        private GameObject _labelsContainer;
        private float _lastZoom = -1f;
        private float _lastAspectRatio = -1f;

        // Source image dimensions for aspect ratio correction
        private float SourceWidth => provinceIDLoader.mapWidth;
        private float SourceHeight => provinceIDLoader.mapHeight;
        private float SourceAspectRatio => SourceWidth / SourceHeight;

        [Serializable]
        private class ProvinceCentroid {
            public int id;
            public string name;
            public float centroid_x;
            public float centroid_y;
            public int area;
            public float orientation;          // Rotation angle in degrees
            public float principal_length;     // Length along text direction in pixels
            public float perpendicular_width;  // Width perpendicular to text
            public float curvature;            // Text curvature amount (0 = straight)
        }

        [Serializable]
        private class CentroidsData {
            public ProvinceCentroid[] provinces;
        }

        void Awake() { provinceIDLoader.OnMapLoaded += OnProvinceMapLoaded; }

        void OnDestroy() { provinceIDLoader.OnMapLoaded -= OnProvinceMapLoaded; }

        private void OnProvinceMapLoaded() {
            if (mapContent == null) {
                Debug.LogWarning("ProvinceLabelsController: No map content assigned");
                return;
            }

            _mapWidth = mapContent.rect.width;
            _mapHeight = mapContent.rect.height;

            CreateLabels();
        }

        private void CreateLabels() {
            var data = JsonUtility.FromJson<CentroidsData>(provinceIDLoader.CentroidsJsonText);
            if (data?.provinces == null) {
                Debug.LogWarning("ProvinceLabelsController: Failed to parse centroids JSON");
                return;
            }

            // Destroy previous labels if they exist
            if (_labelsContainer != null) {
                Destroy(_labelsContainer);
                _labels.Clear();
                _sourceOrientations.Clear();
                _sourceCurvatures.Clear();
                _provinceAreas.Clear();
                _provinceLengths.Clear();
                _nameCharCounts.Clear();
            }

            // Create a container for all labels
            _labelsContainer = new GameObject("ProvinceLabels");
            var labelsContainer = _labelsContainer;
            var containerRect = labelsContainer.AddComponent<RectTransform>();
            containerRect.SetParent(mapContent, false);
            containerRect.anchorMin = Vector2.zero;
            containerRect.anchorMax = Vector2.one;
            containerRect.offsetMin = Vector2.zero;
            containerRect.offsetMax = Vector2.zero;

            // Map pixel coordinates to normalized (0-1) coordinates
            // centroids.json uses pixel coords with origin at top-left
            // Unity UI uses bottom-left origin

            // Calculate average area for scaling font size by province size
            float totalArea = 0f;
            foreach (var p in data.provinces) { totalArea += p.area; }
            _avgArea = totalArea / data.provinces.Length;

            foreach (var province in data.provinces) {
                var labelObj = new GameObject($"Label_{province.id}_{province.name}");
                var rectTransform = labelObj.AddComponent<RectTransform>();
                rectTransform.SetParent(containerRect, false);

                // Convert from source pixel coords to normalized position
                // Image coords have y=0 at top, Unity anchors have y=0 at bottom
                // But the map RawImage displays with y=0 at top, so no flip needed
                float normX = province.centroid_x / SourceWidth;
                float normY = province.centroid_y / SourceHeight;

                // Position using anchors
                rectTransform.anchorMin = new Vector2(normX, normY);
                rectTransform.anchorMax = new Vector2(normX, normY);
                rectTransform.anchoredPosition = Vector2.zero;
                rectTransform.sizeDelta = new Vector2(200f, 50f);

                // Store source orientation and curvature for aspect ratio adjustment
                _sourceOrientations[province.id] = province.orientation;
                _sourceCurvatures[province.id] = province.curvature;

                // Apply initial rotation (will be adjusted for aspect ratio in Update)
                rectTransform.localRotation = Quaternion.Euler(0, 0, province.orientation);

                var tmp = labelObj.AddComponent<TextMeshProUGUI>();
                tmp.text = province.name;
                tmp.fontSize = baseFontSize;
                tmp.alignment = TextAlignmentOptions.Center;
                tmp.color = labelColor;
                tmp.raycastTarget = false;
                tmp.textWrappingMode = TextWrappingModes.NoWrap;  // Never wrap text
                tmp.overflowMode = TextOverflowModes.Overflow;    // Allow overflow

                if (labelFont != null) { tmp.font = labelFont; }

                // Add outline for readability
                tmp.outlineWidth = outlineWidth;
                tmp.outlineColor = outlineColor;

                // Add underlay (glow/shadow) for better visibility on dark backgrounds
                // This expands outward unlike outline which shrinks text
                tmp.fontMaterial.EnableKeyword("UNDERLAY_ON");
                tmp.fontMaterial.SetColor("_UnderlayColor", underlayColor);
                tmp.fontMaterial.SetFloat("_UnderlayOffsetX", underlayOffset.x);
                tmp.fontMaterial.SetFloat("_UnderlayOffsetY", underlayOffset.y);
                tmp.fontMaterial.SetFloat("_UnderlayDilate", underlayDilate);
                tmp.fontMaterial.SetFloat("_UnderlaySoftness", underlaySoftness);

                // Store province data for dynamic font size updates
                _provinceAreas[province.id] = province.area;
                _provinceLengths[province.id] = province.principal_length;
                _nameCharCounts[province.id] = province.name.Length;

                // Calculate and apply font size
                tmp.fontSize = CalculateFontSize(province.id);

                // Add curved text if curvature is specified
                if (!Mathf.Approximately(province.curvature, 0f)) {
                    var curvedText = labelObj.AddComponent<CurvedText>();
                    curvedText.curvature = province.curvature;
                }

                _labels[province.id] = tmp;
            }

            // Apply initial visibility (aspect ratio correction will happen in first Update
            // when rect dimensions are valid)
            UpdateLabelVisibility();
        }

        void Update() {
            if (_labels.Count == 0) { return; }

            // Check for aspect ratio changes (screen resize, orientation change)
            float currentAspectRatio = mapContent.rect.width / mapContent.rect.height;
            if (!Mathf.Approximately(currentAspectRatio, _lastAspectRatio)) {
                _lastAspectRatio = currentAspectRatio;
                UpdateLabelAngles();
            }

            if (zoomHandler == null) { return; }

            float currentZoom = zoomHandler.CurrentZoom;
            if (Mathf.Approximately(currentZoom, _lastZoom)) { return; }

            _lastZoom = currentZoom;
            UpdateLabelVisibility();
        }

        private void UpdateLabelAngles() {
            // Guard against zero dimensions (can happen during initialization)
            if (mapContent.rect.width <= 0 || mapContent.rect.height <= 0) { return; }

            float currentAspectRatio = mapContent.rect.width / mapContent.rect.height;

            // Calculate how much the image is stretched relative to source
            // stretchX > 1 means wider than source, stretchY > 1 means taller
            float stretchX = currentAspectRatio / SourceAspectRatio;
            float stretchY = 1f;  // We normalize to Y

            foreach (var kvp in _labels) {
                int id = kvp.Key;
                var label = kvp.Value;

                if (!_sourceOrientations.TryGetValue(id, out float sourceAngle)) { continue; }

                // Adjust angle for aspect ratio stretch
                // Original direction vector: (cos(θ), sin(θ))
                // Stretched: (cos(θ) * stretchX, sin(θ) * stretchY)
                // New angle: atan2(sin(θ) * stretchY, cos(θ) * stretchX)
                float rad = sourceAngle * Mathf.Deg2Rad;
                float adjustedRad =
                        Mathf.Atan2(Mathf.Sin(rad) * stretchY, Mathf.Cos(rad) * stretchX);
                float adjustedAngle = adjustedRad * Mathf.Rad2Deg;

                label.rectTransform.localRotation = Quaternion.Euler(0, 0, adjustedAngle);

                // Also adjust curvature - when stretched horizontally, curves appear flatter
                if (_sourceCurvatures.TryGetValue(id, out float sourceCurvature) &&
                    !Mathf.Approximately(sourceCurvature, 0f)) {
                    var curvedText = label.GetComponent<CurvedText>();
                    if (curvedText != null) {
                        // Scale curvature inversely with horizontal stretch
                        curvedText.curvature = sourceCurvature / stretchX;
                    }
                }
            }
        }

        private void UpdateLabelVisibility() {
            bool shouldShow = zoomHandler == null || zoomHandler.CurrentZoom >= minZoomToShow;
            float zoom = zoomHandler?.CurrentZoom ?? 1f;

            foreach (var label in _labels.Values) {
                label.gameObject.SetActive(shouldShow);

                if (shouldShow) {
                    // Scale labels inversely with sqrt of zoom - they grow at half the zoom rate
                    // At zoom 2x: labels are ~1.41x size instead of staying constant
                    float scale = 1f / Mathf.Sqrt(zoom);
                    label.rectTransform.localScale = new Vector3(scale, scale, 1f);
                }
            }
        }

        private float CalculateFontSize(int provinceId) {
            if (!_provinceAreas.TryGetValue(provinceId, out float area)) { return baseFontSize; }
            if (!_provinceLengths.TryGetValue(provinceId, out float length)) {
                return baseFontSize;
            }
            if (!_nameCharCounts.TryGetValue(provinceId, out int charCount)) {
                return baseFontSize;
            }

            // Scale font based on province area - larger provinces get larger text
            // Use sqrt for gentle scaling (14x area ratio -> ~3.7x font ratio)
            float areaScale = Mathf.Sqrt(area / _avgArea);
            // Clamp area scale to reasonable bounds (0.6x to 2.5x base size)
            areaScale = Mathf.Clamp(areaScale, 0.6f, 2.5f);

            // Calculate max font size that fits within province length
            // Estimate ~0.6 * fontSize per character width
            float availableWidth = length * 0.8f;  // 80% for margin
            float maxFontForWidth = availableWidth / (charCount * 0.6f);

            // Use area-scaled size, but cap to fit within province width
            float targetFontSize = baseFontSize * areaScale;
            return Mathf.Min(targetFontSize, maxFontForWidth);
        }

        public void RefreshLabels() {
            // Update font sizes for all labels
            foreach (var kvp in _labels) { kvp.Value.fontSize = CalculateFontSize(kvp.Key); }
            UpdateLabelVisibility();
        }
    }
}
