using ClaudeDo.Data.Models;

namespace ClaudeDo.Worker.Prime;

public sealed record NextDue(PrimeScheduleDto Schedule, DateTimeOffset At, bool FireImmediately);

public static class NextDueCalculator
{
    public static NextDue? Compute(
        IEnumerable<PrimeScheduleDto> schedules,
        DateTimeOffset now,
        TimeSpan catchUp)
    {
        NextDue? best = null;
        foreach (var s in schedules)
        {
            if (!s.Enabled) continue;
            var due = ComputeFor(s, now, catchUp);
            if (due is null) continue;
            if (best is null || due.At < best.At) best = due;
        }
        return best;
    }

    private static NextDue? ComputeFor(PrimeScheduleDto s, DateTimeOffset now, TimeSpan catchUp)
    {
        if ((PrimeDays)s.Days == PrimeDays.None) return null;

        var todayLocal = DateOnly.FromDateTime(now.LocalDateTime);
        var alreadyFiredToday = s.LastRunAt is { } last &&
                                DateOnly.FromDateTime(last.LocalDateTime) == todayLocal;

        if (!alreadyFiredToday && IsEligibleDay(s, todayLocal))
        {
            var todayTarget = ToOffset(todayLocal, s.TimeOfDay, now.Offset);
            if (todayTarget >= now)
                return new NextDue(s, todayTarget, false);
            if (now <= todayTarget + catchUp)
                return new NextDue(s, now, true);
        }

        var d = todayLocal.AddDays(1);
        for (int i = 0; i < 7; i++)
        {
            if (IsEligibleDay(s, d))
                return new NextDue(s, ToOffset(d, s.TimeOfDay, now.Offset), false);
            d = d.AddDays(1);
        }
        return null;
    }

    private static bool IsEligibleDay(PrimeScheduleDto s, DateOnly d) =>
        ((PrimeDays)s.Days & ToFlag(d.DayOfWeek)) != PrimeDays.None;

    private static PrimeDays ToFlag(DayOfWeek dow) => dow switch
    {
        DayOfWeek.Monday => PrimeDays.Monday,
        DayOfWeek.Tuesday => PrimeDays.Tuesday,
        DayOfWeek.Wednesday => PrimeDays.Wednesday,
        DayOfWeek.Thursday => PrimeDays.Thursday,
        DayOfWeek.Friday => PrimeDays.Friday,
        DayOfWeek.Saturday => PrimeDays.Saturday,
        DayOfWeek.Sunday => PrimeDays.Sunday,
        _ => PrimeDays.None,
    };

    private static DateTimeOffset ToOffset(DateOnly day, TimeSpan time, TimeSpan offset) =>
        new(day.Year, day.Month, day.Day, time.Hours, time.Minutes, time.Seconds, offset);
}
