#nullable enable

using System;
using System.Collections.Generic;

namespace Radzen.Documents.Spreadsheet;

static class AggregationMethods
{
    public static CellData Sum(List<double> items)
    {
        double sum = 0;
        foreach (var d in items)
        {
            sum += d;
        }
        return CellData.FromNumber(sum);
    }

    public static CellData Average(List<double> items)
    {
        if (items.Count == 0)
        {
            return CellData.FromError(CellError.Div0);
        }

        double sum = 0;
        foreach (var d in items)
        {
            sum += d;
        }
        return CellData.FromNumber(sum / items.Count);
    }

    public static CellData Max(List<double> items)
    {
        if (items.Count == 0)
        {
            return CellData.FromNumber(0);
        }

        var max = items[0];
        for (int i = 1; i < items.Count; i++)
        {
            if (items[i] > max)
            {
                max = items[i];
            }
        }
        return CellData.FromNumber(max);
    }

    public static CellData Min(List<double> items)
    {
        if (items.Count == 0)
        {
            return CellData.FromNumber(0);
        }

        var min = items[0];
        for (int i = 1; i < items.Count; i++)
        {
            if (items[i] < min)
            {
                min = items[i];
            }
        }
        return CellData.FromNumber(min);
    }

    public static CellData Median(List<double> items)
    {
        if (items.Count == 0)
        {
            return CellData.FromError(CellError.Num);
        }
        var sorted = new List<double>(items);
        sorted.Sort();
        var n = sorted.Count;
        if (n % 2 == 1)
        {
            return CellData.FromNumber(sorted[n / 2]);
        }
        return CellData.FromNumber((sorted[n / 2 - 1] + sorted[n / 2]) / 2.0);
    }

    public static CellData Large(List<double> items, int k)
    {
        if (items.Count == 0 || k <= 0 || k > items.Count)
        {
            return CellData.FromError(CellError.Num);
        }
        var sorted = new List<double>(items);
        sorted.Sort((a, b) => b.CompareTo(a));
        return CellData.FromNumber(sorted[k - 1]);
    }

    public static CellData Small(List<double> items, int k)
    {
        if (items.Count == 0 || k <= 0 || k > items.Count)
        {
            return CellData.FromError(CellError.Num);
        }
        var sorted = new List<double>(items);
        sorted.Sort();
        return CellData.FromNumber(sorted[k - 1]);
    }

    public static CellData Product(List<double> items)
    {
        // Excel PRODUCT() with no numeric values returns 0.
        if (items.Count == 0)
        {
            return CellData.FromNumber(0);
        }
        double product = 1;
        foreach (var d in items)
        {
            product *= d;
        }
        return CellData.FromNumber(product);
    }

    public static CellData Mode(List<double> items)
    {
        var counts = new Dictionary<double, int>();
        foreach (var d in items)
        {
            counts[d] = counts.TryGetValue(d, out var c) ? c + 1 : 1;
        }

        // First value (in input order) reaching the highest frequency > 1; #N/A if all unique.
        double best = 0;
        var bestCount = 1;
        foreach (var d in items)
        {
            if (counts[d] > bestCount)
            {
                best = d;
                bestCount = counts[d];
            }
        }

        return bestCount > 1 ? CellData.FromNumber(best) : CellData.FromError(CellError.NA);
    }

    public static CellData VarS(List<double> items) => Variance(items, sample: true);

    public static CellData VarP(List<double> items) => Variance(items, sample: false);

    public static CellData StdevS(List<double> items) => Sqrt(Variance(items, sample: true));

    public static CellData StdevP(List<double> items) => Sqrt(Variance(items, sample: false));

    private static CellData Variance(List<double> items, bool sample)
    {
        var n = items.Count;
        if (sample ? n < 2 : n < 1)
        {
            return CellData.FromError(CellError.Div0);
        }

        double mean = 0;
        foreach (var d in items)
        {
            mean += d;
        }
        mean /= n;

        double sumSquares = 0;
        foreach (var d in items)
        {
            var diff = d - mean;
            sumSquares += diff * diff;
        }

        return CellData.FromNumber(sumSquares / (sample ? n - 1 : n));
    }

    private static CellData Sqrt(CellData variance)
    {
        return variance.IsError ? variance : CellData.FromNumber(Math.Sqrt(variance.GetValueOrDefault<double>()));
    }
}


