using TeleWave.Domain.Programming;

namespace TeleWave.Application.Programming.Templates;

/// <summary>
/// Покрытие вещательных суток слотами. Общая часть проверок сетки и автогенерации: считать дыры
/// они обязаны одинаково, иначе генератор «закроет всё», а проверки продолжат ругаться на то же
/// самое место.
///
/// Всё меряется смещением от начала вещательных суток канала, а не часами на стене: сутки канала
/// начинаются в <c>DayStartTime</c>, и ночной блок с 00:00 до 06:00 относится к предыдущему дню.
/// </summary>
public static class GridCoverage
{
    public const int MinutesInDay = 24 * 60;

    /// <summary>Дни недели в порядке показа: неделя начинается с понедельника, воскресенье последнее.</summary>
    public static readonly int[] Week = [1, 2, 3, 4, 5, 6, 0];

    /// <summary>Смещение времени от начала вещательных суток, минуты.</summary>
    public static int OffsetInDay(TimeOnly time, TimeOnly dayStart)
    {
        var diff = (int)(time.ToTimeSpan() - dayStart.ToTimeSpan()).TotalMinutes;
        return diff >= 0 ? diff : diff + MinutesInDay;
    }

    /// <summary>Время в сутках канала по смещению от начала вещательных суток.</summary>
    public static TimeOnly AtOffset(int offsetMinutes, TimeOnly dayStart)
    {
        var minutes = ((int)dayStart.ToTimeSpan().TotalMinutes + offsetMinutes) % MinutesInDay;
        return new TimeOnly(minutes / 60, minutes % 60);
    }

    /// <summary>Слот действует в указанный день: без дня недели — каждый день.</summary>
    public static bool AppliesTo(Slot slot, int weekday) =>
        slot.Weekday is null || slot.Weekday == weekday;

    /// <summary>
    /// Интервалы суток указанного дня, не покрытые ни одним из слотов. Границы — смещения от начала
    /// вещательных суток; интервал, вылезающий за сутки, обрезается по ним.
    /// </summary>
    public static List<(int From, int To)> Gaps(
        IEnumerable<Slot> slots,
        int weekday,
        TimeOnly dayStart
    )
    {
        var intervals = slots
            .Where(s => AppliesTo(s, weekday))
            .Select(s =>
            {
                var from = OffsetInDay(s.TargetStart, dayStart);
                return (From: from, To: from + s.TargetDurationMinutes);
            })
            .OrderBy(i => i.From)
            .ToList();

        var gaps = new List<(int From, int To)>();
        var cursor = 0;
        foreach (var interval in intervals)
        {
            if (interval.From > cursor)
                gaps.Add((cursor, interval.From));
            cursor = Math.Max(cursor, Math.Min(interval.To, MinutesInDay));
        }

        if (cursor < MinutesInDay)
            gaps.Add((cursor, MinutesInDay));

        return gaps;
    }
}
