@page "/calendar"
@page "/calendar/{YearMonth}"
@attribute [Authorize]
@rendermode InteractiveServer

@inject IWorkDayService WorkDayService
@inject IItalianFestivitySource FestivitySource
@inject IJSRuntime JS

<PageTitle>Calendar</PageTitle>

<div class="calendar-page">
<h1>Calendar</h1>

<div class="d-flex align-items-center gap-2 mb-3">
    <button class="btn btn-outline-secondary btn-sm" @onclick="PreviousMonth">&laquo; Prev</button>
    <h2 class="h5 mb-0">@firstOfMonth.ToString("MMMM yyyy")</h2>
    <button class="btn btn-outline-secondary btn-sm" @onclick="NextMonth">Next &raquo;</button>
    <button class="btn btn-primary btn-sm ms-auto" @onclick="GeneratePreviewWorkUnitsAsync">Generate Preview Units</button>
</div>

@if (!string.IsNullOrWhiteSpace(statusMessage))
{
    <div class="alert alert-info py-2">@statusMessage</div>
}

@if (loading)
{
    <p><em>Loading...</em></p>
}
else
{
    <div class="table-responsive">
        <table class="table table-bordered calendar-table">
            <thead class="table-dark">
                <tr>
                    <th>Mon</th>
                    <th>Tue</th>
                    <th>Wed</th>
                    <th>Thu</th>
                    <th>Fri</th>
                    <th>Sat</th>
                    <th>Sun</th>
                </tr>
            </thead>
            <tbody>
                @foreach (var week in weeks)
                {
                    <tr>
                        @foreach (var cell in week)
                        {
                            <td class="calendar-cell @GetCellClass(cell) @(cell.IsCurrentMonth ? string.Empty : "calendar-cell-outside-month") @(IsToday(cell.Date) ? "calendar-cell-today" : string.Empty) @(IsActiveCell(cell.Date) ? "calendar-cell-active" : string.Empty)" @onclick="() => TogglePopup(cell.Date)" role="button">
                                <div class="d-flex align-items-start justify-content-between gap-2">
                                    <div class="calendar-day-number @(cell.IsCurrentMonth ? string.Empty : "calendar-day-number-outside")">@cell.Date.Day</div>
                                    @if (!cell.IsCurrentMonth)
                                    {
                                        <div class="calendar-day-month-label">@cell.Date.ToString("MMM")</div>
                                    }
                                </div>

                                @foreach (var workUnit in cell.Entry?.WorkUnits ?? [])
                                {
                                    <button type="button" class="calendar-item calendar-item-work @GetWorkUnitClass(workUnit)" @onclick:stopPropagation="true" @onclick="() => OpenWorkUnit(cell.Date, workUnit.Id)">
                                        <span>@workUnit.Label</span>
                                        <span>@FormatWorkUnit(workUnit)</span>
                                    </button>
                                }

                                @foreach (var calendarEvent in cell.Entry?.CalendarEvents ?? [])
                                {
                                    <button type="button" class="calendar-item calendar-item-event @GetCalendarEventClass(calendarEvent)" @onclick:stopPropagation="true" @onclick="() => OpenCalendarEvent(GetCalendarEventOwnerDate(calendarEvent, cell.Date), calendarEvent.Id)">
                                        <span>@CalendarEventFormatter.GetDisplayDescription(calendarEvent)</span>
                                        @if (calendarEvent.StartTime.HasValue)
                                        {
                                            <span>@calendarEvent.StartTime.Value.ToString("HH:mm")</span>
                                        }
                                    </button>
                                }

                                <div class="calendar-day-total">@FormatHours(GetDayTotalHours(cell.Entry, includePreviewTotals))</div>

                                @if (IsActiveCell(cell.Date))
                                {
                                    <div class="calendar-popup @GetPopupClass(cell)" @onclick:stopPropagation="true">
                                        <div class="d-flex align-items-start justify-content-between gap-2 mb-2">
                                            <div>
                                                <div class="fw-semibold">@FormatDisplayDate(cell.Date)</div>
                                                <div class="small text-muted">Select an existing entry or create a new one.</div>
                                            </div>
                                            <button type="button" class="btn-close btn-sm" aria-label="Close" @onclick="ClosePopup"></button>
                                        </div>

                                        <div class="calendar-popup-section">
                                            @if ((cell.Entry?.WorkUnits.Count ?? 0) == 0 && (cell.Entry?.CalendarEvents.Count ?? 0) == 0)
                                            {
                                                <div class="small text-muted">No entries for this day.</div>
                                            }

                                            @foreach (var workUnit in cell.Entry?.WorkUnits ?? [])
                                            {
                                                <button type="button" class="calendar-popup-link" @onclick="() => OpenWorkUnit(cell.Date, workUnit.Id)">
                                                    Work unit: @workUnit.Label (@FormatWorkUnit(workUnit))
                                                </button>
                                            }

                                            @foreach (var calendarEvent in cell.Entry?.CalendarEvents ?? [])
                                            {
                                                <button type="button" class="calendar-popup-link" @onclick="() => OpenCalendarEvent(GetCalendarEventOwnerDate(calendarEvent, cell.Date), calendarEvent.Id)">
                                                    Calendar event: @CalendarEventFormatter.GetDisplayDescription(calendarEvent)
                                                </button>
                                            }
                                        </div>

                                        <div class="d-flex gap-2 mt-3">
                                            <button type="button" class="btn btn-sm btn-primary" @onclick="() => CreateWorkUnit(cell.Date)">New Work Unit</button>
                                            <button type="button" class="btn btn-sm btn-outline-primary" @onclick="() => CreateCalendarEvent(cell.Date)">New Calendar Event</button>
                                        </div>
                                    </div>
                                }
                            </td>
                        }
                    </tr>
                }
            </tbody>
        </table>
    </div>

    <div class="mt-3">
        <h3 class="h6">Legend</h3>
        <div class="d-flex flex-wrap gap-2">
            <span class="badge calendar-legend-work">Office work unit</span>
            <span class="badge calendar-legend-home">Home work unit</span>
            <span class="badge calendar-legend-preview">Preview work unit</span>
            <span class="badge bg-warning text-dark">Closure</span>
            <span class="badge bg-secondary">Day off</span>
            <span class="badge bg-danger">Holiday</span>
        </div>
    </div>

    @if (monthTotals is not null)
    {
        <div class="calendar-month-summary mt-4">
            <div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
                <h3 class="h6 mb-0">Month Totals</h3>
                <div class="form-check mb-0">
                    <input id="calendar-include-preview" type="checkbox" class="form-check-input" checked="@includePreviewTotals" @onchange="OnIncludePreviewTotalsChanged" />
                    <label class="form-check-label" for="calendar-include-preview">Include preview work units</label>
                </div>
            </div>
            <div class="row g-3">
                <div class="col-6 col-md-4 col-xl-2">
                    <div class="card text-center h-100">
                        <div class="card-body">
                            <div class="text-muted small">Worked Hours</div>
                            <div class="fs-4 fw-bold">@FormatHours(monthTotals.TotalWorkedHours)</div>
                        </div>
                    </div>
                </div>
                <div class="col-6 col-md-4 col-xl-2">
                    <div class="card text-center h-100">
                        <div class="card-body">
                            <div class="text-muted small">Counted Units</div>
                            <div class="fs-4 fw-bold">@monthTotals.CountedWorkUnits</div>
                        </div>
                    </div>
                </div>
                <div class="col-6 col-md-4 col-xl-2">
                    <div class="card text-center h-100">
                        <div class="card-body">
                            <div class="text-muted small">Hours Off</div>
                            <div class="fs-4 fw-bold">@FormatHours(monthTotals.TotalHoursOff)</div>
                        </div>
                    </div>
                </div>
                <div class="col-6 col-md-4 col-xl-3">
                    <div class="card text-center h-100 border-success">
                        <div class="card-body">
                            <div class="text-muted small">Gross Income</div>
                            <div class="fs-4 fw-bold text-success">€@monthTotals.TotalGrossIncome.ToString("N2")</div>
                        </div>
                    </div>
                </div>
                <div class="col-6 col-md-4 col-xl-3">
                    <div class="card text-center h-100 border-primary">
                        <div class="card-body">
                            <div class="text-muted small">Net Income</div>
                            <div class="fs-4 fw-bold text-primary">€@monthTotals.TotalNetIncome.ToString("N2")</div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    }
}

@if (workUnitModalDate.HasValue)
{
    <WorkUnitEditorModal Date="@workUnitModalDate.Value" UnitId="@workUnitModalId" OnSaved="HandleEditorSavedAsync" OnClosed="CloseWorkUnitModal" />
}

@if (calendarEventModalDate.HasValue)
{
    <CalendarEventEditorModal Date="@calendarEventModalDate.Value" EventId="@calendarEventModalId" OnSaved="HandleEditorSavedAsync" OnClosed="CloseCalendarEventModal" />
}
</div>

@code {
    [Parameter] public string? YearMonth { get; set; }

    private const string IncludePreviewPreferenceKey = "worktracker.includePreviewWorkUnits";

    private DateOnly firstOfMonth;
    private bool loading = true;
    private List<CalendarCell[]> weeks = [];
    private readonly Dictionary<int, IReadOnlyCollection<DateOnly>> festivitiesByYear = [];
    private DateOnly? activeDate;
    private bool includePreviewTotals;
    private MonthlySummaryModel? monthTotals;
    private string? statusMessage;
    private DateOnly? workUnitModalDate;
    private string? workUnitModalId;
    private DateOnly? calendarEventModalDate;
    private string? calendarEventModalId;

    protected override async Task OnInitializedAsync()
    {
        if (!string.IsNullOrEmpty(YearMonth) && DateTime.TryParseExact(YearMonth, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var parsed))
        {
            firstOfMonth = new DateOnly(parsed.Year, parsed.Month, 1);
        }
        else
        {
            firstOfMonth = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
        }

        await LoadMonth();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            return;
        }

        var savedIncludePreview = await JS.InvokeAsync<bool?>("workTrackerPreferences.getBool", IncludePreviewPreferenceKey);
        if (savedIncludePreview.HasValue && savedIncludePreview.Value != includePreviewTotals)
        {
            includePreviewTotals = savedIncludePreview.Value;
            await LoadMonth();
            await InvokeAsync(StateHasChanged);
        }
    }

    private async Task LoadMonth()
    {
        loading = true;
        activeDate = null;
        var lastDay = firstOfMonth.AddMonths(1).AddDays(-1);
        var gridStart = GetGridStart(firstOfMonth);
        var gridEnd = GetGridEnd(lastDay);
        var entries = await WorkDayService.GetRangeAsync(gridStart, gridEnd);
        var lookup = entries.ToDictionary(e => e.Date);
        monthTotals = await WorkDayService.GetMonthlySummaryAsync(firstOfMonth.Year, firstOfMonth.Month, includePreviewTotals);

        weeks = [];
        for (var weekStart = gridStart; weekStart <= gridEnd; weekStart = weekStart.AddDays(7))
        {
            var week = new CalendarCell[7];
            for (var columnIndex = 0; columnIndex < 7; columnIndex++)
            {
                var date = weekStart.AddDays(columnIndex);
                week[columnIndex] = new CalendarCell
                {
                    Date = date,
                    ColumnIndex = columnIndex,
                    IsCurrentMonth = date.Month == firstOfMonth.Month && date.Year == firstOfMonth.Year,
                    IsWeekend = date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday,
                    IsFestivity = GetFestivities(date.Year).Contains(date),
                    Entry = lookup.GetValueOrDefault(date)
                };
            }

            weeks.Add(week);
        }

        loading = false;
    }

    private async Task OnIncludePreviewTotalsChanged(ChangeEventArgs e)
    {
        includePreviewTotals = e.Value is bool value && value;
        await JS.InvokeVoidAsync("workTrackerPreferences.setBool", IncludePreviewPreferenceKey, includePreviewTotals);
        await LoadMonth();
    }

    private async Task PreviousMonth()
    {
        firstOfMonth = firstOfMonth.AddMonths(-1);
        statusMessage = null;
        await LoadMonth();
    }

    private async Task NextMonth()
    {
        firstOfMonth = firstOfMonth.AddMonths(1);
        statusMessage = null;
        await LoadMonth();
    }

    private void TogglePopup(DateOnly date)
    {
        activeDate = activeDate == date ? null : date;
    }

    private void ClosePopup()
    {
        activeDate = null;
    }

    private static string GetPopupClass(CalendarCell cell)
    {
        if (cell.ColumnIndex == 0)
        {
            return "calendar-popup-left";
        }

        return cell.ColumnIndex >= 5 ? "calendar-popup-right" : string.Empty;
    }

    private bool IsActiveCell(DateOnly date) => activeDate == date;

    private static bool IsToday(DateOnly date) => date == DateOnly.FromDateTime(DateTime.Today);

    private void CreateWorkUnit(DateOnly date)
    {
        OpenWorkUnit(date, null);
    }

    private void CreateCalendarEvent(DateOnly date)
    {
        OpenCalendarEvent(date, null);
    }

    private void OpenWorkUnit(DateOnly date, string? workUnitId)
    {
        activeDate = null;
        workUnitModalDate = date;
        workUnitModalId = workUnitId;
    }

    private void OpenCalendarEvent(DateOnly date, string? eventId)
    {
        activeDate = null;
        calendarEventModalDate = date;
        calendarEventModalId = eventId;
    }

    private async Task HandleEditorSavedAsync()
    {
        CloseWorkUnitModal();
        CloseCalendarEventModal();
        statusMessage = null;
        await LoadMonth();
    }

    private void CloseWorkUnitModal()
    {
        workUnitModalDate = null;
        workUnitModalId = null;
    }

    private void CloseCalendarEventModal()
    {
        calendarEventModalDate = null;
        calendarEventModalId = null;
    }

    private async Task GeneratePreviewWorkUnitsAsync()
    {
        var confirmed = await JS.InvokeAsync<bool>("confirm", $"Generate preview work units for {firstOfMonth:MMMM yyyy}? Existing work-unit days will be left unchanged.");
        if (!confirmed)
        {
            return;
        }

        var createdDays = await WorkDayService.GenerateMonthlyPreviewWorkUnitsAsync(firstOfMonth.Year, firstOfMonth.Month);
        statusMessage = createdDays == 0
            ? "No preview work units were created. Every eligible day already had work units or a blocking calendar event."
            : $"Created preview work units for {createdDays} day(s).";
        await LoadMonth();
    }

    private string GetCellClass(CalendarCell cell)
    {
        if (cell.IsWeekend || cell.IsFestivity)
        {
            return "calendar-weekend";
        }

        var eventType = GetDominantEventType(cell.Entry);
        if (eventType.HasValue)
        {
            return eventType.Value switch
            {
                CalendarEventType.Closure => "calendar-closure",
                CalendarEventType.Illness => "calendar-illness",
                CalendarEventType.DayOff => "calendar-dayoff",
                CalendarEventType.Holiday => "calendar-holiday",
                _ => string.Empty
            };
        }

        return string.Empty;
    }

    private static CalendarEventType? GetDominantEventType(WorkDayDocument? day)
    {
        if (day is null || day.CalendarEvents.Count == 0)
        {
            return null;
        }

        if (day.CalendarEvents.Any(entry => entry.EventType == CalendarEventType.Holiday))
        {
            return CalendarEventType.Holiday;
        }

        if (day.CalendarEvents.Any(entry => entry.EventType == CalendarEventType.Closure))
        {
            return CalendarEventType.Closure;
        }

        if (day.CalendarEvents.Any(entry => entry.EventType == CalendarEventType.DayOff))
        {
            return CalendarEventType.DayOff;
        }

        if (day.CalendarEvents.Any(entry => entry.EventType == CalendarEventType.Illness))
        {
            return CalendarEventType.Illness;
        }

        return null;
    }

    private static string GetWorkUnitClass(WorkUnitDocument workUnit)
    {
        if (workUnit.IsPreview)
        {
            return workUnit.Location == WorkUnitLocation.Home
                ? "calendar-item-preview-home"
                : "calendar-item-preview-office";
        }

        return workUnit.Location == WorkUnitLocation.Home ? "calendar-item-home" : "calendar-item-office";
    }

    private static string GetCalendarEventClass(CalendarEventDocument calendarEvent) => calendarEvent.EventType switch
    {
        CalendarEventType.Holiday => "calendar-item-holiday",
        CalendarEventType.Closure => "calendar-item-closure",
        CalendarEventType.DayOff => "calendar-item-dayoff",
        CalendarEventType.Illness => "calendar-item-illness",
        _ => "calendar-item-generic"
    };

    private static string FormatWorkUnit(WorkUnitDocument workUnit)
    {
        var hours = FormatHours(workUnit.ManualWorkedHours);
        var timeRange = workUnit.StartTime.HasValue && workUnit.EndTime.HasValue
            ? $"{workUnit.StartTime:HH:mm}-{workUnit.EndTime:HH:mm}"
            : hours;

        return workUnit.IsPreview ? $"{timeRange} preview" : timeRange;
    }

    private static decimal GetDayTotalHours(WorkDayDocument? day, bool includePreview)
    {
        if (day is null)
        {
            return 0m;
        }

        return day.WorkUnits
            .Where(unit => includePreview || !unit.IsPreview)
            .Sum(unit => unit.ManualWorkedHours);
    }

    private static string FormatHours(decimal value)
    {
        return DurationFormatter.FormatHours(value);
    }

    private static string FormatDisplayDate(DateOnly date)
    {
        return date.ToString("dddd dd/MM/yyyy");
    }

    private IReadOnlyCollection<DateOnly> GetFestivities(int year)
    {
        if (!festivitiesByYear.TryGetValue(year, out var festivities))
        {
            festivities = FestivitySource.GetFestivities(year);
            festivitiesByYear[year] = festivities;
        }

        return festivities;
    }

    private static DateOnly GetGridStart(DateOnly firstDayOfMonth)
    {
        var offset = ((int)firstDayOfMonth.DayOfWeek + 6) % 7;
        return firstDayOfMonth.AddDays(-offset);
    }

    private static DateOnly GetGridEnd(DateOnly lastDayOfMonth)
    {
        var offset = (7 - (((int)lastDayOfMonth.DayOfWeek + 6) % 7) - 1 + 7) % 7;
        return lastDayOfMonth.AddDays(offset);
    }

    private static DateOnly GetCalendarEventOwnerDate(CalendarEventDocument calendarEvent, DateOnly fallbackDate)
    {
        return calendarEvent.StartDate == default ? fallbackDate : calendarEvent.StartDate;
    }

    private sealed class CalendarCell
    {
        public DateOnly Date { get; set; }
        public int ColumnIndex { get; set; }
        public bool IsCurrentMonth { get; set; }
        public bool IsWeekend { get; set; }
        public bool IsFestivity { get; set; }
        public WorkDayDocument? Entry { get; set; }
    }
}
