// Asset Naming Manager — the dashboard's vector charts, drawn with Painter2D.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

namespace AssetNamingManager.UI
{
    /// <summary>
    /// The big circular health score on the dashboard. Draws a background track, a coloured arc for
    /// the score, and the number in the middle. Everything is vector, so it stays crisp on any DPI.
    /// </summary>
    public class GaugeElement : VisualElement
    {
        const float StartAngle = 135f;
        const float SweepAngle = 270f;

        float _value;
        string _caption = "";
        readonly Label _number;
        readonly Label _label;

        // Read back out of USS so a theme override repaints the arc without touching C#.
        readonly NamingManagerPalette _palette = new NamingManagerPalette();

        /// <summary>Score 0-100.</summary>
        public float Value
        {
            get { return _value; }
            set
            {
                _value = Mathf.Clamp(value, 0f, 100f);
                _number.text = Mathf.RoundToInt(_value).ToString();
                _number.style.color = _palette.ForScore(_value);
                MarkDirtyRepaint();
            }
        }

        /// <summary>Word under the number, e.g. "Healthy".</summary>
        public string Caption
        {
            get { return _caption; }
            set { _caption = value; _label.text = value; }
        }

        /// <summary>Creates the gauge at a fixed square size.</summary>
        public GaugeElement(float size = 180f)
        {
            AddToClassList("anm-gauge");
            style.width = size;
            style.height = size;

            _number = new Label("0");
            _number.AddToClassList("anm-gauge__number");
            _label = new Label("");
            _label.AddToClassList("anm-gauge__caption");

            var centre = new VisualElement();
            centre.AddToClassList("anm-gauge__centre");
            centre.pickingMode = PickingMode.Ignore;
            centre.Add(_number);
            centre.Add(_label);
            Add(centre);

            generateVisualContent += Draw;
            RegisterCallback<CustomStyleResolvedEvent>(OnCustomStyleResolved);
        }

        void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
        {
            _palette.ResolveFrom(evt.customStyle);
            _number.style.color = _palette.ForScore(_value);
            MarkDirtyRepaint();
        }

        void Draw(MeshGenerationContext ctx)
        {
            var rect = contentRect;
            if (rect.width < 8f || rect.height < 8f) return;

            var painter = ctx.painter2D;
            float thickness = Mathf.Max(8f, rect.width * 0.105f);
            float radius = Mathf.Min(rect.width, rect.height) * 0.5f - thickness * 0.5f - 2f;
            var centre = new Vector2(rect.width * 0.5f, rect.height * 0.5f);

            painter.lineWidth = thickness;
            painter.lineCap = LineCap.Round;

            // A round cap paints half a stroke width past each endpoint. Left uncorrected that is
            // ~5.5 degrees at each end, so a score of 0.6 would draw a 12-degree lozenge that reads
            // like 5/100. Inset both ends by the cap angle and the ink matches the number.
            float capAngle = Mathf.Atan2(thickness * 0.5f, radius) * Mathf.Rad2Deg;

            painter.strokeColor = _palette.track;
            painter.BeginPath();
            painter.Arc(centre, radius, new Angle(StartAngle + capAngle, AngleUnit.Degree),
                        new Angle(StartAngle + SweepAngle - capAngle, AngleUnit.Degree));
            painter.Stroke();

            float sweep = SweepAngle * (_value / 100f);
            if (sweep > capAngle * 2f)
            {
                painter.strokeColor = _palette.ForScore(_value);
                painter.BeginPath();
                painter.Arc(centre, radius, new Angle(StartAngle + capAngle, AngleUnit.Degree),
                            new Angle(StartAngle + sweep - capAngle, AngleUnit.Degree));
                painter.Stroke();
            }
        }
    }

    /// <summary>One slice of a <see cref="DonutElement"/>.</summary>
    public struct DonutSlice
    {
        /// <summary>Legend text.</summary>
        public string label;

        /// <summary>Slice size. Zero slices are skipped.</summary>
        public float value;

        /// <summary>Slice colour.</summary>
        public Color color;

        /// <summary>Creates a slice.</summary>
        public DonutSlice(string label, float value, Color color)
        {
            this.label = label;
            this.value = value;
            this.color = color;
        }
    }

    /// <summary>A ring chart with a total in the middle. Used for the severity split.</summary>
    public class DonutElement : VisualElement
    {
        readonly List<DonutSlice> _slices = new List<DonutSlice>();
        readonly Label _total;
        readonly Label _caption;
        readonly NamingManagerPalette _palette = new NamingManagerPalette();

        /// <summary>Creates the donut at a fixed square size.</summary>
        public DonutElement(float size = 152f)
        {
            AddToClassList("anm-donut");
            style.width = size;
            style.height = size;

            _total = new Label("0");
            _total.AddToClassList("anm-donut__total");
            _caption = new Label("findings");
            _caption.AddToClassList("anm-donut__caption");

            var centre = new VisualElement();
            centre.AddToClassList("anm-gauge__centre");
            centre.pickingMode = PickingMode.Ignore;
            centre.Add(_total);
            centre.Add(_caption);
            Add(centre);

            generateVisualContent += Draw;
            RegisterCallback<CustomStyleResolvedEvent>(evt =>
            {
                _palette.ResolveFrom(evt.customStyle);
                MarkDirtyRepaint();
            });
        }

        /// <summary>Replaces the slices and the middle number.</summary>
        public void SetSlices(IEnumerable<DonutSlice> slices, string caption)
        {
            _slices.Clear();
            float sum = 0f;
            foreach (var slice in slices)
            {
                if (slice.value <= 0f) continue;
                _slices.Add(slice);
                sum += slice.value;
            }

            _total.text = Mathf.RoundToInt(sum).ToString();
            _caption.text = caption;
            MarkDirtyRepaint();
        }

        void Draw(MeshGenerationContext ctx)
        {
            var rect = contentRect;
            if (rect.width < 8f || rect.height < 8f) return;

            var painter = ctx.painter2D;
            float thickness = Mathf.Max(10f, rect.width * 0.125f);
            float radius = Mathf.Min(rect.width, rect.height) * 0.5f - thickness * 0.5f - 2f;
            var centre = new Vector2(rect.width * 0.5f, rect.height * 0.5f);

            painter.lineWidth = thickness;
            painter.lineCap = LineCap.Butt;

            float total = 0f;
            foreach (var slice in _slices) total += slice.value;

            if (total <= 0f)
            {
                painter.strokeColor = _palette.track;
                painter.BeginPath();
                painter.Arc(centre, radius, new Angle(0f, AngleUnit.Degree), new Angle(360f, AngleUnit.Degree));
                painter.Stroke();
                return;
            }

            // Only separate slices when there is something to separate. A single slice with a gap
            // draws 357.5 degrees and shows a phantom notch at twelve o'clock that looks like a bug.
            float gap = _slices.Count > 1 ? 2.5f : 0f;
            float cursor = -90f;

            foreach (var slice in _slices)
            {
                float sweep = 360f * (slice.value / total);

                // Never draw more than the slice is worth: a 1-of-900 slice must stay hairline
                // rather than being inflated to a visible minimum by a Max() floor.
                float drawn = sweep > gap ? sweep - gap : sweep * 0.6f;

                painter.strokeColor = slice.color;
                painter.BeginPath();
                painter.Arc(centre, radius, new Angle(cursor, AngleUnit.Degree),
                            new Angle(cursor + drawn, AngleUnit.Degree));
                painter.Stroke();

                cursor += sweep;
            }
        }
    }

    /// <summary>
    /// A horizontal bar row: label, track, value. Plain layout rather than Painter2D, because
    /// flexbox already does proportional widths and stays selectable.
    /// </summary>
    public class BarRow : VisualElement
    {
        readonly Label _key;
        readonly VisualElement _fill;
        readonly Label _value;

        /// <summary>Creates one bar. The fill colour comes from <paramref name="fillClass"/>, not C#.</summary>
        public BarRow(string key, int value, int max, string fillClass, string tooltipText = null)
        {
            AddToClassList("anm-bar");

            _key = new Label(key);
            _key.AddToClassList("anm-bar__key");
            _key.tooltip = tooltipText ?? key;

            var track = new VisualElement();
            track.AddToClassList("anm-bar__track");

            _fill = new VisualElement();
            _fill.AddToClassList("anm-bar__fill");
            if (!string.IsNullOrEmpty(fillClass)) _fill.AddToClassList(fillClass);
            _fill.style.width = Length.Percent(max <= 0 ? 0f : Mathf.Max(2f, 100f * value / max));
            track.Add(_fill);

            _value = new Label(value.ToString());
            _value.AddToClassList("anm-bar__value");

            Add(_key);
            Add(track);
            Add(_value);
        }
    }

    /// <summary>A rounded panel with a title, used everywhere on the dashboard.</summary>
    public class CardElement : VisualElement
    {
        /// <summary>Where children should be added.</summary>
        public readonly VisualElement body;

        /// <summary>Creates a card with a heading.</summary>
        public CardElement(string title, string subtitle = null)
        {
            AddToClassList("anm-card");

            if (!string.IsNullOrEmpty(title))
            {
                var heading = new Label(title);
                heading.AddToClassList("anm-card__title");
                Add(heading);
            }

            if (!string.IsNullOrEmpty(subtitle))
            {
                var sub = new Label(subtitle);
                sub.AddToClassList("anm-card__subtitle");
                Add(sub);
            }

            body = new VisualElement();
            body.AddToClassList("anm-card__body");
            Add(body);
        }
    }

    /// <summary>A big number with a caption under it, for the stat strip.</summary>
    public class StatTile : VisualElement
    {
        readonly Label _value;

        /// <summary>The number shown.</summary>
        public string Value
        {
            get { return _value.text; }
            set { _value.text = value; }
        }

        /// <summary>Creates a tile. Pass a USS class in <paramref name="valueClass"/> to recolour the number.</summary>
        public StatTile(string label, string value, string valueClass = null, string tooltipText = null)
        {
            AddToClassList("anm-stat");
            if (!string.IsNullOrEmpty(tooltipText)) tooltip = tooltipText;

            _value = new Label(value);
            _value.AddToClassList("anm-stat__value");
            if (!string.IsNullOrEmpty(valueClass)) _value.AddToClassList(valueClass);

            var caption = new Label(label);
            caption.AddToClassList("anm-stat__label");

            Add(_value);
            Add(caption);
        }
    }
}
