@page "/"
@rendermode InteractiveWebAssembly
@attribute [Authorize]
@inject ITimetrackerService TrackerService
@inject IHolidayService HolidayService
@inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
@inject IJSRuntime JSRuntime

<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>

@if (_loading)
{
    <MudStack AlignItems="AlignItems.Center" Class="mt-16" Spacing="3">
        <MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
        <MudText Color="Color.Secondary">Lade Wochendaten…</MudText>
    </MudStack>
}
else
{
    <MudStack Spacing="3">

        @* ── Wochen-Header ── *@
        <MudPaper Elevation="4" Class="pa-5 rounded-xl onboarding-week-header"
                  Style="background: #1E293B; color: white;">
            <MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
                <MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
                               Style="color:white" Size="Size.Large" OnClick="PrevWeek" />
                <MudStack Spacing="0" AlignItems="AlignItems.Center">
                    <MudText Typo="Typo.h5" Style="color:white; font-weight:700; letter-spacing:0.5px">
                        @_weekLabel
                    </MudText>
                    <MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">
                        @_weekSubLabel
                    </MudText>
                </MudStack>
                <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="0">
                    @if (!IsCurrentWeek)
                    {
                        <MudButton Variant="Variant.Text" Style="color:white"
                                   OnClick="GoToCurrentWeek" Size="Size.Small">
                            Heute
                        </MudButton>
                    }
                    <MudIconButton Icon="@Icons.Material.Filled.ChevronRight"
                                   Style="color:white" Size="Size.Large" OnClick="NextWeek" />
                </MudStack>
            </MudStack>
        </MudPaper>

        @* ── Tageskarten ── *@
        @foreach (var day in _days)
        {
            var isToday = day.Date == DateOnly.FromDateTime(DateTime.Today);
            var isWorkDay = _settings.IsWorkDay(day.Date.DayOfWeek);
            var hasData = day.Start.HasValue || day.End.HasValue;
            var overtime = GetOvertime(day);
            var errors = GetDayErrors(day).ToList();
            var borderColor = GetBorderColor(day, isWorkDay, errors);
            var progressPct = GetProgressPercent(day);
            var holidayName = _holidays.GetValueOrDefault(day.Date);

            if (!isWorkDay)
            {
                @* ── Nicht-Arbeitstag: kompakt ── *@
                <MudPaper @key="@day.Date" Elevation="1" Class="pa-3 rounded-lg"
                          Style="@($"border-left: 4px solid {(!string.IsNullOrEmpty(holidayName) ? "#009688" : "#CFD8DC")}; background:{(!string.IsNullOrEmpty(holidayName) ? "rgba(0,150,136,0.05)" : "#FAFAFA")};")">
                    <MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
                        <MudStack Spacing="0">
                            <MudText Typo="Typo.body1" Style="color:#90A4AE; font-weight:500">
                                @day.Date.ToString("dddd", _deCulture)
                            </MudText>
                            <MudText Typo="Typo.caption" Style="color:#B0BEC5">
                                @day.Date.ToString("dd. MMMM yyyy", _deCulture)
                            </MudText>
                        </MudStack>
                        @if (!string.IsNullOrEmpty(holidayName))
                        {
                            <MudChip T="string" Size="Size.Small" Variant="Variant.Filled"
                                     Style="background:#009688; color:white; font-weight:600">
                                @holidayName
                            </MudChip>
                        }
                        else
                        {
                            <MudChip T="string" Size="Size.Small" Variant="Variant.Outlined"
                                     Style="color:#90A4AE; border-color:#CFD8DC;">
                                Kein Arbeitstag
                            </MudChip>
                        }
                    </MudStack>
                </MudPaper>
            }
            else
            {
                @* ── Arbeitstag: vollständige Karte ── *@
                <MudCard @key="@day.Date" Elevation="@(isToday ? 6 : 2)" Class="rounded-xl onboarding-day-card"
                         Style="@($"border-left: 4px solid {borderColor};")">
                    <MudCardHeader Style="@(isToday ? "background: linear-gradient(90deg, rgba(14,165,233,0.07) 0%, transparent 100%);" : "")">
                        <CardHeaderContent>
                            <MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
                                <MudStack Spacing="0">
                                    <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
                                        <MudText Typo="Typo.h6" Style="font-weight:600">
                                            @day.Date.ToString("dddd", _deCulture)
                                        </MudText>
                                        @if (isToday)
                                        {
                                            <MudChip T="string" Size="Size.Small" Color="Color.Primary"
                                                     Variant="Variant.Filled"
                                                     Style="height:20px; font-size:10px; font-weight:700">
                                                HEUTE
                                            </MudChip>
                                        }
                                        @if (!string.IsNullOrEmpty(holidayName))
                                        {
                                            <MudChip T="string" Size="Size.Small" Variant="Variant.Filled"
                                                     Style="height:20px; font-size:10px; font-weight:700; background:#009688; color:white;">
                                                @holidayName
                                            </MudChip>
                                        }
                                    </MudStack>
                                    <MudText Typo="Typo.caption" Color="Color.Secondary">
                                        @day.Date.ToString("dd. MMMM yyyy", _deCulture)
                                    </MudText>
                                </MudStack>

                                @if (overtime.HasValue)
                                {
                                    <MudChip T="string"
                                             Color="@(overtime.Value > TimeSpan.Zero ? Color.Success : overtime.Value < TimeSpan.Zero ? Color.Warning : Color.Info)"
                                             Variant="Variant.Filled" Size="Size.Medium">
                                        <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
                                            <MudIcon Icon="@(overtime.Value >= TimeSpan.Zero ? Icons.Material.Filled.TrendingUp : Icons.Material.Filled.TrendingDown)"
                                                     Size="Size.Small" />
                                            <MudText Typo="Typo.body2"><b>@FormatTs(overtime.Value, sign: true)</b></MudText>
                                        </MudStack>
                                    </MudChip>
                                }
                                else if (!hasData)
                                {
                                    <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
                                        @if (day.IsVacation)
                                        {
                                            <MudChip T="string" Color="Color.Info" Variant="Variant.Filled" Size="Size.Small" Style="font-weight:700">
                                                URLAUB
                                            </MudChip>
                                            <MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Error"
                                                       OnClick="@(async () => await RemoveVacation(day))"
                                                       Style="text-transform:none; font-size: 0.75rem;">
                                                Urlaub entfernen
                                            </MudButton>
                                        }
                                        else
                                        {
                                            <MudChip T="string" Color="Color.Default" Variant="Variant.Outlined" Size="Size.Small">
                                                Nicht erfasst
                                            </MudChip>
                                            <MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Info"
                                                       OnClick="@(async () => await MarkAsVacation(day))"
                                                       Style="text-transform:none; font-size: 0.75rem;">
                                                Als Urlaub eintragen
                                            </MudButton>
                                        }
                                    </MudStack>
                                }
                            </MudStack>
                        </CardHeaderContent>
                    </MudCardHeader>

                    <MudCardContent Class="pt-2">
                        @* ── Zeit-Eingaben + Statistik ── *@
                        <MudGrid Spacing="3">
                            <MudItem xs="6" sm="3" md="2">
                                <MudTimePicker Label="Beginn"
                                               Time="@day.Start"
                                               TimeChanged="@(async v => await OnStartChanged(day, v))"
                                               AmPm="false" Variant="Variant.Outlined"
                                               Clearable="true" PickerVariant="PickerVariant.Dialog"
                                               Error="@(day.End.HasValue && day.Start.HasValue && day.Start >= day.End)" />
                            </MudItem>
                            <MudItem xs="6" sm="3" md="2">
                                <MudTimePicker Label="Ende"
                                               Time="@day.End"
                                               TimeChanged="@(async v => await OnEndChanged(day, v))"
                                               AmPm="false" Variant="Variant.Outlined"
                                               Clearable="true" PickerVariant="PickerVariant.Dialog"
                                               Error="@(day.End.HasValue && day.Start.HasValue && day.Start >= day.End)" />
                            </MudItem>

                            @if (day.GrossWork.HasValue)
                            {
                                <MudItem xs="12" md="8">
                                    <MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Wrap="Wrap.Wrap" Class="h-100 pl-1">
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Color="Color.Secondary">Brutto</MudText>
                                            <MudText Typo="Typo.body1">@FormatTs(day.GrossWork.Value)</MudText>
                                        </MudStack>
                                        <MudText Color="Color.Secondary" Class="mb-1">−</MudText>
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Color="Color.Secondary">Pausen</MudText>
                                            <MudText Typo="Typo.body1">@FormatTs(GetEffectiveBreakTime(day))</MudText>
                                        </MudStack>
                                        <MudText Color="Color.Secondary" Class="mb-1">=</MudText>
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Color="Color.Secondary">Netto</MudText>
                                            <MudText Typo="Typo.body1" Color="Color.Primary" Style="font-weight:700">
                                                @FormatTs(GetEffectiveNetWork(day)!.Value)
                                            </MudText>
                                        </MudStack>
                                        <MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Color="Color.Secondary">Soll</MudText>
                                            <MudText Typo="Typo.body1">@FormatTs(TimeSpan.FromHours(GetDailyTargetHours(day)))</MudText>
                                        </MudStack>
                                        <MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Color="Color.Secondary">Soll-Ende</MudText>
                                            <MudText Typo="Typo.body1">@FormatTimeSpan(CalculateEarliestDeparture(day)) Uhr</MudText>
                                        </MudStack>
                                    </MudStack>
                                </MudItem>
                            }
                            else if (day.Start.HasValue)
                            {
                                <MudItem xs="12" md="8">
                                    <MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="h-100 pl-1">
                                        <MudStack Spacing="0">
                                            <MudText Typo="Typo.overline" Style="color: var(--mud-palette-info);">Früheste Gehenszeit</MudText>
                                            <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
                                                <MudIcon Icon="@Icons.Material.Filled.ExitToApp" Color="Color.Info" Size="Size.Small" />
                                                <MudText Typo="Typo.body1" Color="Color.Info" Style="font-weight:700">
                                                    @FormatTimeSpan(CalculateEarliestDeparture(day)) Uhr
                                                </MudText>
                                                <MudText Typo="Typo.caption" Color="Color.Secondary" Class="ml-2">
                                                     (Bei Soll: @FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))h + @((int)GetRequiredBreakForTarget(day).TotalMinutes) Min. Pause)
                                                 </MudText>
                                            </MudStack>
                                        </MudStack>
                                    </MudStack>
                                </MudItem>
                            }
                        </MudGrid>

                        @* ── Fortschrittsbalken ── *@
                        @if (progressPct >= 0)
                        {
                            <MudTooltip Text="@($"Netto {FormatTs(GetEffectiveNetWork(day) ?? TimeSpan.Zero)} von {FormatTs(TimeSpan.FromHours(GetDailyTargetHours(day)))} Soll")">
                                <MudProgressLinear Value="@Math.Min(progressPct, 100)"
                                                   Color="@(progressPct >= 100 ? Color.Success : Color.Primary)"
                                                   Rounded="true" Class="mt-3" Style="height:6px" />
                            </MudTooltip>
                        }

                        @* ── Pausen-Sektion ── *@
                        <MudDivider Class="mt-3 mb-2" />
                        <MudStack Spacing="2">
                            @for (int i = 0; i < day.Breaks.Count; i++)
                            {
                                var brk = day.Breaks[i];
                                var idx = i;
                                var brkError = (day.Start.HasValue && brk.Start.HasValue && brk.Start < day.Start)
                                            || (day.End.HasValue && brk.End.HasValue && brk.End > day.End);

                                <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Wrap="Wrap.Wrap">
                                    <MudIcon Icon="@Icons.Material.Filled.FreeBreakfast"
                                             Size="Size.Small" Color="Color.Secondary" Style="opacity:0.6" />
                                    <MudText Typo="Typo.caption" Color="Color.Secondary" Style="min-width:55px">
                                        Pause @(idx + 1)
                                    </MudText>
                                    <MudTimePicker Time="@brk.Start"
                                                   TimeChanged="@(async v => await OnBreakStartChanged(day, idx, v))"
                                                   AmPm="false" Variant="Variant.Outlined"
                                                   Style="width:300px" Clearable="true"
                                                   PickerVariant="PickerVariant.Inline"
                                                   Error="@brkError" />
                                    <MudText Color="Color.Secondary">–</MudText>
                                    <MudTimePicker Time="@brk.End"
                                                   TimeChanged="@(async v => await OnBreakEndChanged(day, idx, v))"
                                                   AmPm="false" Variant="Variant.Outlined"
                                                   Style="width:300px" Clearable="true"
                                                   PickerVariant="PickerVariant.Inline"
                                                   Error="@brkError" />
                                    @if (brk.Duration.HasValue)
                                    {
                                        <MudChip T="string" Size="Size.Small" Variant="Variant.Text" Color="Color.Secondary">
                                            @FormatTs(brk.Duration.Value)
                                        </MudChip>
                                    }
                                    <MudIconButton Icon="@Icons.Material.Filled.RemoveCircleOutline"
                                                   Size="Size.Small" Color="Color.Error"
                                                   OnClick="@(async () => await RemoveBreak(day, idx))" />
                                </MudStack>
                            }
                            <MudButton StartIcon="@Icons.Material.Filled.AddCircleOutline"
                                       Variant="Variant.Text" Color="Color.Primary"
                                       Size="Size.Small" OnClick="@(() => AddBreak(day))">
                                Pause hinzufügen
                            </MudButton>
                        </MudStack>

                        @* ── Validierungsfehler ── *@
                        @if (errors.Count > 0)
                        {
                            <MudDivider Class="mt-2 mb-1" />
                            @foreach (var err in errors)
                            {
                                <MudAlert Severity="Severity.Warning" Dense="true" Class="mb-1">@err</MudAlert>
                            }
                        }

                    </MudCardContent>
                </MudCard>
            }
        }

        @* ── Wochensumme ── *@
        <MudPaper Elevation="4" Class="pa-5 rounded-xl onboarding-week-summary"
                  Style="background: #0F172A; color:white;">
            <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-4">
                <MudIcon Icon="@Icons.Material.Filled.Summarize" Style="color:rgba(255,255,255,0.8)" />
                <MudText Typo="Typo.h6" Style="color:white; font-weight:600">Wochensumme</MudText>
            </MudStack>
            <MudGrid Spacing="3">
                <MudItem xs="6" sm="3">
                    <MudStack Spacing="0">
                        <MudText Typo="Typo.overline" Style="color:rgba(255,255,255,0.6)">Brutto</MudText>
                        <MudText Typo="Typo.h5" Style="color:white">@FormatTs(WeekGross)</MudText>
                    </MudStack>
                </MudItem>
                <MudItem xs="6" sm="3">
                    <MudStack Spacing="0">
                        <MudText Typo="Typo.overline" Style="color:rgba(255,255,255,0.6)">Pausen</MudText>
                        <MudText Typo="Typo.h5" Style="color:white">@FormatTs(WeekBreaks)</MudText>
                    </MudStack>
                </MudItem>
                <MudItem xs="6" sm="3">
                    <MudStack Spacing="0">
                        <MudText Typo="Typo.overline" Style="color:rgba(255,255,255,0.6)">Netto</MudText>
                        <MudText Typo="Typo.h5" Style="color:#90CAF9; font-weight:700">@FormatTs(WeekNet)</MudText>
                    </MudStack>
                </MudItem>
                <MudItem xs="6" sm="3">
                    <MudStack Spacing="0">
                        <MudText Typo="Typo.overline" Style="color:rgba(255,255,255,0.6)">Gleitzeit</MudText>
                        <MudText Typo="Typo.h5" Style="@($"color:{(WeekOvertime >= TimeSpan.Zero ? "#A5D6A7" : "#FFCC80")}; font-weight:700")">
                            @FormatTs(WeekOvertime, sign: true)
                        </MudText>
                    </MudStack>
                </MudItem>
            </MudGrid>
        </MudPaper>

        @* ── Gleitzeitkonto ── *@
        <MudPaper Elevation="3" Class="pa-5 rounded-xl onboarding-overtime-balance"
                  Style="@($"border-left: 6px solid {(_totalOvertime >= TimeSpan.Zero ? "#4CAF50" : "#FF9800")};")">
            <MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap" Spacing="3">
                <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
                    <MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Primary" />
                    <MudStack Spacing="0">
                        <MudText Typo="Typo.h6" Style="font-weight:600">Gleitzeitkonto</MudText>
                        <MudText Typo="Typo.caption" Color="Color.Secondary">Gesamtsaldo aller erfassten Arbeitstage</MudText>
                    </MudStack>
                </MudStack>
                <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
                    <MudIcon Icon="@(_totalOvertime >= TimeSpan.Zero ? Icons.Material.Filled.TrendingUp : Icons.Material.Filled.TrendingDown)"
                             Style="@($"color:{(_totalOvertime >= TimeSpan.Zero ? "#4CAF50" : "#FF9800")}; font-size:2rem")" />
                    <MudText Typo="Typo.h4"
                             Style="@($"font-weight:700; color:{(_totalOvertime >= TimeSpan.Zero ? "#4CAF50" : "#FF9800")};")">
                        @FormatTs(_totalOvertime, sign: true)
                    </MudText>
                </MudStack>
            </MudStack>
        </MudPaper>

    </MudStack>

    @if (_showOnboarding)
    {
        <OnboardingTour OnFinished="HandleOnboardingFinished" />
    }
}

@code {
    private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");

    private bool _loading = true;
    private int _userId;
    private DateOnly _monday;
    private List<DayVm> _days = [];
    private AppSettings _settings = new();
    private string _weekLabel = "";
    private string _weekSubLabel = "";
    private int _kw => _monday == default ? 0 : _deCulture.Calendar.GetWeekOfYear(
        _monday.ToDateTime(TimeOnly.MinValue),
        System.Globalization.CalendarWeekRule.FirstFourDayWeek,
        DayOfWeek.Monday);
    private TimeSpan _totalOvertime;
    private Dictionary<DateOnly, string> _holidays = [];
    private int _holidayYear = -1;
    private bool _showOnboarding;
    private List<VacationDay> _vacations = [];

    private bool IsCurrentWeek => _monday == GetMonday(DateOnly.FromDateTime(DateTime.Today));

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthStateProvider.GetAuthenticationStateAsync();
        var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
        if (claim == null) return; // Prerender-Pass – Circuit noch nicht authentifiziert
        _userId = int.Parse(claim.Value);
        _monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
        _settings = await TrackerService.GetSettingsAsync(_userId);
        
        var loadWeekTask = LoadWeek();
        var overtimeTask = TrackerService.GetTotalOvertimeAsync(_userId, _settings);
        
        await Task.WhenAll(loadWeekTask, overtimeTask);
        _totalOvertime = await overtimeTask;

        try
        {
            var showOnb = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "showOnboarding");
            _showOnboarding = showOnb == "true";
        }
        catch
        {
            // Ignored during prerendering/SSR
        }

        _loading = false;
    }

    private async Task HandleOnboardingFinished()
    {
        _showOnboarding = false;
        await JSRuntime.InvokeVoidAsync("localStorage.setItem", "showOnboarding", "false");
    }

    private async Task LoadWeek()
    {
        Task<List<PublicHoliday>> holidaysTask;
        if (_monday.Year != _holidayYear)
        {
            holidaysTask = HolidayService.GetHolidaysAsync(_monday.Year, _settings.GermanState);
        }
        else
        {
            holidaysTask = Task.FromResult(new List<PublicHoliday>());
        }

        var dbDaysTask = TrackerService.GetWeekAsync(_userId, _monday);
        var vacationsTask = TrackerService.GetVacationDaysAsync(_userId, _monday.Year);

        await Task.WhenAll(holidaysTask, dbDaysTask, vacationsTask);

        if (_monday.Year != _holidayYear)
        {
            var list = await holidaysTask;
            _holidays = list.ToDictionary(h => h.Date, h => h.Name);
            _holidayYear = _monday.Year;
        }

        _vacations = await vacationsTask;
        var dbDays = await dbDaysTask;
        _days = Enumerable.Range(0, 7).Select(i =>
        {
            var date = _monday.AddDays(i);
            var dayVm = DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId);
            dayVm.IsVacation = _vacations.Any(v => v.Date == date);
            return dayVm;
        }).ToList();
        BuildWeekLabels();
    }

    private void BuildWeekLabels()
    {
        var sunday = _monday.AddDays(6);
        var kw = _deCulture.Calendar.GetWeekOfYear(
            _monday.ToDateTime(TimeOnly.MinValue),
            System.Globalization.CalendarWeekRule.FirstFourDayWeek,
            DayOfWeek.Monday);
        _weekLabel = $"KW {kw}  ·  {_monday:dd. MMM} – {sunday:dd. MMM yyyy}";
        var recorded = _days.Count(d => d.Start.HasValue || d.End.HasValue);
        var workDays = _days.Count(d => _settings.IsWorkDay(d.Date.DayOfWeek));
        _weekSubLabel = recorded == 0
            ? "Noch keine Einträge diese Woche"
            : $"{recorded} von {workDays} Arbeitstagen erfasst";
    }

    // ── Navigation ──────────────────────────────────────────────
    private async Task PrevWeek() { _monday = _monday.AddDays(-7); await LoadWeek(); }
    private async Task NextWeek() { _monday = _monday.AddDays(7); await LoadWeek(); }
    private async Task GoToCurrentWeek() { _monday = GetMonday(DateOnly.FromDateTime(DateTime.Today)); await LoadWeek(); }

    private static DateOnly GetMonday(DateOnly date)
    {
        int diff = ((int)date.DayOfWeek - (int)DayOfWeek.Monday + 7) % 7;
        return date.AddDays(-diff);
    }

    // ── Change Handlers ──────────────────────────────────────────
    private async Task OnStartChanged(DayVm day, TimeSpan? v) { day.Start = v; await SaveDay(day); }
    private async Task OnEndChanged(DayVm day, TimeSpan? v) { day.End = v; await SaveDay(day); }

    private async Task OnBreakStartChanged(DayVm day, int idx, TimeSpan? v)
    { day.Breaks[idx].Start = v; await SaveDay(day); }

    private async Task OnBreakEndChanged(DayVm day, int idx, TimeSpan? v)
    { day.Breaks[idx].End = v; await SaveDay(day); }

    private void AddBreak(DayVm day)
    {
        day.Breaks.Add(new BreakVm());
        StateHasChanged();
    }

    private async Task RemoveBreak(DayVm day, int idx)
    { day.Breaks.RemoveAt(idx); await SaveDay(day); }

    private async Task SaveDay(DayVm day)
    {
        await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
        _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
        BuildWeekLabels();
    }

    // ── Berechnungen ─────────────────────────────────────────────
    private TimeSpan GetEffectiveBreakTime(DayVm day)
    {
        var enteredBreak = day.Breaks
            .Where(b => b.Duration.HasValue)
            .Aggregate(TimeSpan.Zero, (s, b) => s + b.Duration!.Value);

        if (!day.Start.HasValue || !day.End.HasValue)
            return enteredBreak;

        var gross = day.End.Value - day.Start.Value;
        int minMinutes = gross.TotalHours >= 9.0 ? 45 : 30;
        int finalMin = Math.Max(minMinutes, _settings.MinimumBreakMinutes);
        var minBreak = TimeSpan.FromMinutes(finalMin);

        return enteredBreak > minBreak ? enteredBreak : minBreak;
    }

    private TimeSpan? GetEffectiveNetWork(DayVm day)
    {
        if (!day.GrossWork.HasValue) return null;
        return day.GrossWork.Value - GetEffectiveBreakTime(day);
    }

    private double GetDailyTargetHours(DayVm day)
    {
        var isHoliday = _holidays.ContainsKey(day.Date);
        var isWorkDay = _settings.IsWorkDay(day.Date.DayOfWeek);
        if (!isWorkDay || isHoliday || day.IsVacation)
        {
            return 0.0;
        }
        return _settings.DailyTargetHours;
    }

    private TimeSpan? GetOvertime(DayVm day)
    {
        var net = GetEffectiveNetWork(day);
        return net.HasValue ? net.Value - TimeSpan.FromHours(GetDailyTargetHours(day)) : null;
    }

    private int GetProgressPercent(DayVm day)
    {
        var net = GetEffectiveNetWork(day);
        var target = GetDailyTargetHours(day);
        if (!net.HasValue || target <= 0) return -1;
        return (int)(net.Value.TotalHours / target * 100);
    }

    private string GetBorderColor(DayVm day, bool isWorkDay, List<string> errors)
    {
        if (!isWorkDay) return "#CFD8DC";
        if (errors.Count > 0) return "#EF5350";
        if (day.IsVacation) return "#0EA5E9"; // Cool blue for vacation
        if (!day.Start.HasValue && !day.End.HasValue) return "#B0BEC5";
        if (!GetEffectiveNetWork(day).HasValue) return "#B0BEC5";
        return "#4CAF50";
    }

    private IEnumerable<string> GetDayErrors(DayVm day)
    {
        if (day.Start.HasValue && day.End.HasValue && day.Start >= day.End)
            yield return "Endzeit muss nach Beginn liegen.";

        for (int i = 0; i < day.Breaks.Count; i++)
        {
            var b = day.Breaks[i];
            if (b.Start.HasValue && b.End.HasValue && b.Start >= b.End)
                yield return $"Pause {i + 1}: Ende muss nach Start liegen.";
            if (day.Start.HasValue && b.Start.HasValue && b.Start < day.Start)
                yield return $"Pause {i + 1} beginnt vor Arbeitsbeginn.";
            if (day.End.HasValue && b.End.HasValue && b.End > day.End)
                yield return $"Pause {i + 1} endet nach Arbeitsende.";

            for (int j = i + 1; j < day.Breaks.Count; j++)
            {
                var c = day.Breaks[j];
                if (b.Start.HasValue && b.End.HasValue && c.Start.HasValue && c.End.HasValue
                    && b.Start < c.End && c.Start < b.End)
                    yield return $"Pause {i + 1} und Pause {j + 1} überschneiden sich.";
            }
        }

        var net = GetEffectiveNetWork(day);
        if (net.HasValue && net.Value.TotalHours > 10.0)
        {
            yield return "Die tägliche Arbeitszeit überschreitet das gesetzliche Maximum von 10 Stunden!";
        }
    }

    private TimeSpan WeekGross => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (d.GrossWork ?? TimeSpan.Zero));
    private TimeSpan WeekBreaks => _days.Aggregate(TimeSpan.Zero, (s, d) => s + GetEffectiveBreakTime(d));
    private TimeSpan WeekNet => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (GetEffectiveNetWork(d) ?? TimeSpan.Zero));
    private TimeSpan WeekOvertime => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (GetOvertime(d) ?? TimeSpan.Zero));

    private static string FormatTs(TimeSpan ts, bool sign = false)
    {
        if (ts == TimeSpan.Zero && sign) return "±0:00";
        var prefix = sign ? (ts >= TimeSpan.Zero ? "+" : "−") : (ts < TimeSpan.Zero ? "−" : "");
        var abs = ts.Duration();
        return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}";
    }

    private TimeSpan GetRequiredBreakForTarget(DayVm day)
    {
        var targetNet = _settings.DailyTargetHours;
        var minMinutes = targetNet >= 8.5 ? 45 : 30;
        var requiredBreak = TimeSpan.FromMinutes(Math.Max(minMinutes, _settings.MinimumBreakMinutes));

        var enteredBreak = day.Breaks
            .Where(b => b.Duration.HasValue)
            .Aggregate(TimeSpan.Zero, (s, b) => s + b.Duration!.Value);

        return enteredBreak > requiredBreak ? enteredBreak : requiredBreak;
    }

    private TimeSpan CalculateEarliestDeparture(DayVm day)
    {
        if (!day.Start.HasValue) return TimeSpan.Zero;
        var target = TimeSpan.FromHours(_settings.DailyTargetHours);
        var finalBreak = GetRequiredBreakForTarget(day);
        return day.Start.Value + target + finalBreak;
    }

    private async Task MarkAsVacation(DayVm day)
    {
        var vacation = new VacationDay
        {
            UserId = _userId,
            Date = day.Date,
            Note = "Über Wochenübersicht eingetragen"
        };
        await TrackerService.AddVacationDayAsync(vacation);

        await LoadWeek();
        _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
        Snackbar.Add($"{day.Date:dd.MM.yyyy} als Urlaub eingetragen.", Severity.Success);
    }

    private async Task RemoveVacation(DayVm day)
    {
        var item = _vacations.FirstOrDefault(v => v.Date == day.Date);
        if (item != null)
        {
            await TrackerService.RemoveVacationDayAsync(_userId, item.Id);

            await LoadWeek();
            _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
            Snackbar.Add($"Urlaub für {day.Date:dd.MM.yyyy} entfernt.", Severity.Info);
        }
    }

    private static string FormatTimeSpan(TimeSpan? ts)
    {
        if (!ts.HasValue) return "";
        var hours = ((int)ts.Value.TotalHours) % 24;
        return $"{hours:D2}:{ts.Value.Minutes:D2}";
    }

    // ── ViewModels ────────────────────────────────────────────────
    private sealed class DayVm
    {
        public int Id { get; set; }
        public int UserId { get; set; }
        public DateOnly Date { get; set; }
        public TimeSpan? Start { get; set; }
        public TimeSpan? End { get; set; }
        public List<BreakVm> Breaks { get; set; } = [];
        public bool IsVacation { get; set; }

        public TimeSpan? GrossWork =>
            Start.HasValue && End.HasValue && End > Start ? End.Value - Start.Value : null;

        public TimeSpan TotalBreakTime => Breaks
            .Where(b => b.Duration.HasValue)
            .Aggregate(TimeSpan.Zero, (s, b) => s + b.Duration!.Value);

        public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;

        public static DayVm From(WorkDay? wd, DateOnly date, int userId) => new()
        {
            Id = wd?.Id ?? 0,
            UserId = wd?.UserId ?? userId,
            Date = date,
            Start = wd?.StartTime?.ToTimeSpan(),
            End = wd?.EndTime?.ToTimeSpan(),
            Breaks = wd?.Breaks.Select(b => new BreakVm
            {
                Id = b.Id,
                Start = b.StartTime?.ToTimeSpan(),
                End = b.EndTime?.ToTimeSpan()
            }).ToList() ?? []
        };

        public WorkDay ToWorkDay() => new()
        {
            Id = Id,
            UserId = UserId,
            Date = Date,
            StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
            EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
            Breaks = Breaks.Select(b => new BreakEntry
            {
                Id = b.Id,
                StartTime = b.Start.HasValue ? TimeOnly.FromTimeSpan(b.Start.Value) : null,
                EndTime = b.End.HasValue ? TimeOnly.FromTimeSpan(b.End.Value) : null
            }).ToList()
        };
    }

    private sealed class BreakVm
    {
        public int Id { get; set; }
        public TimeSpan? Start { get; set; }
        public TimeSpan? End { get; set; }
        public TimeSpan? Duration =>
            Start.HasValue && End.HasValue && End > Start ? End.Value - Start.Value : null;
    }
}

