using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Components;
using Radzen.Blazor.Rendering;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Radzen.Blazor
{
    /// <summary>
    /// Renders bar series in <see cref="RadzenChart" />.
    /// </summary>
    /// <typeparam name="TItem">The type of the series data item.</typeparam>
    public partial class RadzenBarSeries<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] TItem> : CartesianSeries<TItem>, IChartBarSeries
    {
        /// <summary>
        /// Specifies the fill (background color) of the bar series.
        /// </summary>
        /// <value>The fill.</value>
        [Parameter]
        public string? Fill { get; set; }

        /// <summary>
        /// Specifies a list of colors that will be used to set the individual bar backgrounds.
        /// </summary>
        /// <value>The fills.</value>
        [Parameter]
        public IEnumerable<string>? Fills { get; set; }

        /// <summary>
        /// Specifies the stroke (border color) of the bar series.
        /// </summary>
        /// <value>The stroke.</value>
        [Parameter]
        public string? Stroke { get; set; }

        /// <summary>
        /// Specifies a list of colors that will be used to set the individual bar borders.
        /// </summary>
        /// <value>The strokes.</value>
        [Parameter]
        public IEnumerable<string>? Strokes { get; set; }

        /// <summary>
        /// Gets or sets the width of the stroke (border).
        /// </summary>
        /// <value>The width of the stroke.</value>
        [Parameter]
        public double StrokeWidth { get; set; }

        /// <summary>
        /// Gets or sets the type of the line used to render the bar border.
        /// </summary>
        /// <value>The type of the line.</value>
        [Parameter]
        public LineType LineType { get; set; }

        /// <summary>
        /// Gets or sets the color range of the fill.
        /// </summary>
        /// <value>The color range of the fill.</value>
        [Parameter]
        public IList<SeriesColorRange>? FillRange { get; set; }

        /// <summary>
        /// Gets or sets the color range of the stroke.
        /// </summary>
        /// <value>The color range of the stroke.</value>
        [Parameter]
        public IList<SeriesColorRange>? StrokeRange { get; set; }

        /// <summary>
        /// Specifies how the series is filled. Set to <see cref="FillMode.Solid"/> by default.
        /// Use <see cref="FillMode.Gradient"/> for a fill that fades toward the axis baseline, or <see cref="FillMode.None"/> to render only the outline.
        /// </summary>
        /// <value>The fill mode. Default is <see cref="FillMode.Solid"/>.</value>
        [Parameter]
        public FillMode FillMode { get; set; } = FillMode.Solid;

        /// <summary>
        /// Specifies the opacity at the value end of the gradient fill. Used when <see cref="FillMode"/> is <see cref="FillMode.Gradient"/>.
        /// </summary>
        /// <value>The gradient start opacity. Default is <c>0.85</c>.</value>
        [Parameter]
        public double GradientStartOpacity { get; set; } = 0.85;

        /// <summary>
        /// Specifies the opacity at the baseline of the gradient fill. Used when <see cref="FillMode"/> is <see cref="FillMode.Gradient"/>.
        /// </summary>
        /// <value>The gradient end opacity. Default is <c>0.4</c>.</value>
        [Parameter]
        public double GradientEndOpacity { get; set; } = 0.4;

        /// <inheritdoc />
        public override string Color
        {
            get
            {
                return Fill ?? string.Empty;
            }
        }

        /// <inheritdoc />
        public override ScaleBase TransformCategoryScale(ScaleBase scale)
        {
            return base.TransformValueScale(scale);
        }

        /// <inheritdoc />
        public override ScaleBase TransformValueScale(ScaleBase scale)
        {
            return base.TransformCategoryScale(scale);
        }

        /// <inheritdoc />
        protected override IList<object> GetCategories()
        {
            return base.GetCategories().Reverse().ToList();
        }

        private IList<IChartSeries> BarSeries
        {
            get
            {
                var chart = Chart;
                if (chart == null)
                {
                    return new List<IChartSeries>();
                }

                return chart.Series.Where(series => series is IChartBarSeries).Cast<IChartSeries>().ToList();
            }
        }

        private IList<IChartSeries> VisibleBarSeries
        {
            get
            {
                return BarSeries.Where(series => series.Visible).ToList();
            }
        }

        /// <inheritdoc />
        protected override string TooltipStyle(TItem item)
        {
            var style = base.TooltipStyle(item);

            var index = Items.IndexOf(item);

            if (index >= 0)
            {
                var color = PickColor(index, Fills, Fill, FillRange, Value(item));

                if (color != null)
                {
                    style = $"{style}; border-color: {color};";
                }
            }

            return style;
        }

        private double BandHeight
        {
            get
            {
                var barSeries = VisibleBarSeries;
                if (barSeries.Count == 0)
                {
                    return 0;
                }

                var chart = Chart;
                if (chart == null)
                {
                    return 0;
                }

                var barOptions = chart.BarOptions;

                if (barOptions?.Height.HasValue == true)
                {
                    return barOptions.Height.Value * barSeries.Count;
                }
                else if (barOptions?.CategoryGap is double gap)
                {
                    var step = Math.Abs(chart.ValueScale.Scale(1, true) - chart.ValueScale.Scale(0, true));
                    return step * (1 - gap);
                }
                else
                {
                    var availableHeight = chart.ValueScale.OutputSize; // - (Chart.ValueAxis.Padding * 2);
                    var bands = barSeries.Cast<IChartBarSeries>().Max(series => series.Count) + 2;
                    return availableHeight / bands;
                }
            }
        }

        int IChartBarSeries.Count
        {
            get
            {
                return Items.Count;
            }
        }

        /// <inheritdoc />
        public override bool Contains(double x, double y, double tolerance)
        {
            return DataAt(x, y).Item1 != null;
        }

        /// <inheritdoc />
        internal override double TooltipX(TItem item)
        {
            var chart = Chart;
            if (chart == null)
            {
                return 0;
            }

            var value = chart.CategoryScale.Compose(Value);
            var x = value(item);

            return x;
        }

        /// <inheritdoc />
        protected override string TooltipValue(TItem item)
        {
            var chart = Chart;
            if (chart == null)
            {
                return string.Empty;
            }

            return chart.ValueAxis.Format(chart.CategoryScale, chart.CategoryScale.Value(Value(item)));
        }

        /// <inheritdoc />
        protected override string TooltipTitle(TItem item)
        {
            var chart = Chart;
            if (chart == null)
            {
                return string.Empty;
            }

            var category = Category(chart.ValueScale);
            return chart.CategoryAxis.Format(chart.ValueScale, chart.ValueScale.Value(category(item)));
        }

        /// <inheritdoc />
        public override (object, Point) DataAt(double x, double y)
        {
            var chart = Chart;
            if (chart == null)
            {
                return (default!, new Point());
            }

            var value = ComposeValue(chart.CategoryScale);
            var category = ComposeCategory(chart.ValueScale);
            var ticks = chart.CategoryScale.Ticks(chart.ValueAxis.TickDistance);
            var x0 = chart.CategoryScale.Scale(Math.Max(0, ticks.Start));

            var barSeries = VisibleBarSeries;
            var index = barSeries.IndexOf(this);
            if (barSeries.Count == 0 || index < 0)
            {
                return (default!, new Point());
            }

            var padding = chart.BarOptions?.Margin ?? 0;
            var bandHeight = BandHeight;
            var (height, groupHeight) = Rendering.BandLayout.Resolve(bandHeight, barSeries.Count, padding, chart.BarOptions?.EffectiveMaxHeight);

            foreach (var data in Items)
            {
                var startY = category(data) - groupHeight / 2 + index * (height + padding);
                var endY = startY + height;
                var dataX = value(data);
                var startX = Math.Min(dataX, x0);
                var endX = Math.Max(dataX, x0);

                if (startX <= x && x <= endX && startY <= y && y <= endY)
                {
                    return (data!, new Point() { X = x, Y = y });
                }
            }

            return (default!, new Point());
        }

        /// <inheritdoc />
        internal override double TooltipY(TItem item)
        {
            var chart = Chart;
            if (chart == null)
            {
                return 0;
            }

            var category = ComposeCategory(chart.ValueScale);
            var barSeries = VisibleBarSeries;
            var index = barSeries.IndexOf(this);
            if (barSeries.Count == 0 || index < 0)
            {
                return 0;
            }

            var padding = chart.BarOptions?.Margin ?? 0;
            var bandHeight = BandHeight;
            var (height, groupHeight) = Rendering.BandLayout.Resolve(bandHeight, barSeries.Count, padding, chart.BarOptions?.EffectiveMaxHeight);
            var y = category(item) - groupHeight / 2 + index * (height + padding);

            return y + height / 2;
        }

        /// <inheritdoc />
        public override IEnumerable<ChartDataLabel> GetDataLabels(double offsetX, double offsetY)
        {
            return GetDataLabels(offsetX, offsetY, DataLabelPosition.Auto);
        }

        /// <inheritdoc />
        public override IEnumerable<ChartDataLabel> GetDataLabels(double offsetX, double offsetY, DataLabelPosition position)
        {
            var list = new List<ChartDataLabel>();
            var chart = Chart;
            if (chart == null)
            {
                return list;
            }

            if (Data != null)
            {
                const double gap = 8;
                const double verticalGap = 16;
                var ticks = chart.CategoryScale.Ticks(chart.ValueAxis.TickDistance);
                var x0 = chart.CategoryScale.Scale(Math.Max(0, ticks.Start));

                foreach (var d in Data)
                {
                    var value = Value(d);
                    var sign = value < 0 ? -1 : value == 0 ? 0 : 1;
                    var end = TooltipX(d);
                    var anchorY = TooltipY(d);

                    var (x, y, textAnchor) = position switch
                    {
                        DataLabelPosition.Top => (end, anchorY - verticalGap, "middle"),
                        DataLabelPosition.Bottom => (end, anchorY + verticalGap, "middle"),
                        DataLabelPosition.Inside => (end - gap * sign, anchorY, sign < 0 ? "start" : sign > 0 ? "end" : "middle"),
                        DataLabelPosition.Center => (end + (x0 - end) / 2, anchorY, "middle"),
                        _ => (end + gap * sign, anchorY, sign < 0 ? "end" : sign > 0 ? "start" : "middle"),
                    };

                    list.Add(new ChartDataLabel
                    {
                        Position = new Point() { X = x + offsetX, Y = y + offsetY },
                        Anchor = new Point() { X = end, Y = anchorY },
                        Value = value,
                        TextAnchor = textAnchor,
                        Text = chart.ValueAxis.Format(chart.CategoryScale, value)
                    });
                }
            }

            return list;
        }
    }
}