@using System.Globalization
@using System.Linq
@using Radzen.Blazor.Rendering
@inherits RadzenChartComponentBase
@implements IChartSeriesOverlay
@implements IDisposable

@code {
    IChartSeries? series;

    [CascadingParameter]
    protected IChartSeries? Series
    {
        get
        {
            return series;
        }
        set
        {
            if (value == null)
            {
                series = null;
                return;
            }
            if (!value.Overlays.Contains(this))
            {
                series = value;
                series.Overlays.Add(this);
            }
        }
    }

    public RenderFragment Render(ScaleBase categoryScale, ScaleBase valueScale)
    {
        return builder =>
        {
            builder.OpenRegion(0);
            if (series != null && Chart != null)
            {
                var labels = series.GetDataLabels(OffsetX, OffsetY, Position)
                    .Where(l => !(double.IsNaN(l.Position.X) || double.IsNaN(l.Position.Y)))
                    .ToList();

                var seriesIndex = Chart.Series.IndexOf(series);
                var plotHeight = Chart.ValueScale.OutputSize;
                var plotWidth = Chart.CategoryScale.OutputSize;

                const double fontSize = 12;
                const double paddingX = 6;
                var chip = Appearance == DataLabelAppearance.Chip;
                var height = chip ? 18.0 : 14.0;

                foreach (var (label, index) in labels.Select((l, i) => (l, i)))
                {
                    if (!ShouldDisplay(label, index, labels))
                    {
                        continue;
                    }

                    var text = FormatLabel(label);
                    if (string.IsNullOrEmpty(text))
                    {
                        continue;
                    }

                    var x = label.Position.X;
                    var y = label.Position.Y;
                    var textAnchor = label.TextAnchor;
                    var width = TextMeasurer.TextWidth(text, fontSize) + (chip ? paddingX * 2 : 4);

                    double RectX(double textX, string anchor) => anchor switch
                    {
                        "start" => textX - (chip ? paddingX : 0),
                        "end" => textX - width + (chip ? paddingX : 0),
                        _ => textX - width / 2,
                    };

                    if (Position == DataLabelPosition.Auto && label.Anchor != null)
                    {
                        if (y - height / 2 < 0 || y + height / 2 > plotHeight)
                        {
                            var flipped = 2 * label.Anchor.Y - y;
                            if (flipped - height / 2 >= 0 && flipped + height / 2 <= plotHeight)
                            {
                                y = flipped;
                            }
                        }

                        var rect = RectX(x, textAnchor);
                        if (rect < 0 || rect + width > plotWidth)
                        {
                            var flippedX = 2 * label.Anchor.X - x;
                            var flippedAnchor = textAnchor == "start" ? "end" : textAnchor == "end" ? "start" : textAnchor;
                            var flippedRect = RectX(flippedX, flippedAnchor);
                            if (flippedRect >= 0 && flippedRect + width <= plotWidth)
                            {
                                x = flippedX;
                                textAnchor = flippedAnchor;
                            }
                        }
                    }

                    var rectX = RectX(x, textAnchor);
                    var rectY = y - height / 2;

                    if (!AllowOverlap && !Chart.ReserveDataLabelRect(rectX, rectY, width, height))
                    {
                        continue;
                    }

                    var position = new Point { X = x, Y = chip ? y + 1 : y };

                    builder.AddContent(1,
                            @<g class="rz-series-data-label-group" @key="@($"{label.Position}-{seriesIndex}")">
                                @if (chip)
                                {
                                    <rect class="rz-series-data-label-chip" x="@rectX.ToInvariantString()" y="@rectY.ToInvariantString()" width="@width.ToInvariantString()" height="@height.ToInvariantString()" rx="4" ry="4"></rect>
                                }
                                <Text Value="@text" Position="@position" Fill="@Fill" TextAnchor="@textAnchor" class="@GetSeriesDataLabelClass()" />
                            </g>
                    );
                }
            }
            builder.CloseRegion();
        };
    }

    bool ShouldDisplay(ChartDataLabel label, int index, System.Collections.Generic.List<ChartDataLabel> labels)
    {
        if (Display == DataLabelDisplay.FirstLast)
        {
            return index == 0 || index == labels.Count - 1;
        }

        if (Display == DataLabelDisplay.MinMax)
        {
            var values = labels.Where(l => l.Value is IConvertible).Select(l => Convert.ToDouble(l.Value, CultureInfo.InvariantCulture)).ToList();
            if (values.Count == 0)
            {
                return true;
            }
            if (label.Value is not IConvertible)
            {
                return false;
            }
            var value = Convert.ToDouble(label.Value, CultureInfo.InvariantCulture);
            return value == values.Min() || value == values.Max();
        }

        return Step <= 1 || index % Step == 0;
    }

    string FormatLabel(ChartDataLabel label)
    {
        if (Formatter != null && label.Value != null)
        {
            return Formatter(label.Value);
        }

        if (!string.IsNullOrEmpty(FormatString) && label.Value != null)
        {
            return string.Format(CultureInfo.CurrentCulture, FormatString, label.Value);
        }

        return label.Text;
    }

    protected override bool ShouldRefreshChart(ParameterView parameters)
    {
        return parameters.DidParameterChange(nameof(Visible), Visible)
            || parameters.DidParameterChange(nameof(Position), Position)
            || parameters.DidParameterChange(nameof(Appearance), Appearance)
            || parameters.DidParameterChange(nameof(AllowOverlap), AllowOverlap)
            || parameters.DidParameterChange(nameof(FormatString), FormatString)
            || parameters.DidParameterChange(nameof(Step), Step)
            || parameters.DidParameterChange(nameof(Display), Display);
    }

    public bool Contains(double mouseX, double mouseY, int tolerance)
    {
        return false;
    }

    public RenderFragment RenderTooltip(double mouseX, double mouseY)
    {
        return null!;
    }

    /// <inheritdoc />
    public Point GetTooltipPosition(double mouseX, double mouseY)
    {
        return new Point { X = mouseX, Y = mouseY };
    }

    public void Dispose() => series?.Overlays.Remove(this);
}
